Files
Luncher/server/src/tests/groups.test.ts
T
mates b6834cb7aa
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 26s
CI / Build server (push) Successful in 28s
CI / Build client (push) Successful in 42s
CI / Playwright E2E tests (push) Successful in 1m43s
CI / Build and push Docker image (push) Successful in 48s
CI / Notify (push) Successful in 1s
feat: nový stav "Doručeno" pro objednávky
2026-09-07 11:30:40 +02:00

347 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { resetMemoryStorage } from '../storage/memory';
import { getStores, addStore } from '../stores';
import { createGroup, deleteGroup, addGroupMember, removeGroupMember, updateGroupMember, setGroupState, setGroupTracking, markGroupMemberPaid, markGroupQrGenerated } from '../groups';
import { GroupState } from '../../../types/gen/types.gen';
const CREATOR = 'tomas';
const USER = 'petr';
const ADMIN_PW = 'testadmin';
const STORE = 'McDonald\'s';
const TODAY = new Date('2025-01-10');
beforeEach(async () => {
resetMemoryStorage();
process.env.ADMIN_PASSWORD = ADMIN_PW;
await addStore(STORE, ADMIN_PW);
});
afterEach(() => {
delete process.env.ADMIN_PASSWORD;
});
describe('createGroup', () => {
test('vytvoří skupinu, creator je člen', async () => {
const data = await createGroup(CREATOR, STORE, TODAY);
expect(data.groups).toHaveLength(1);
const group = data.groups![0];
expect(group.name).toBe(STORE);
expect(group.creatorLogin).toBe(CREATOR);
expect(group.state).toBe(GroupState.OPEN);
expect(group.members[CREATOR]).toBeDefined();
});
test('odmítne název mimo seznam obchodů', async () => {
await expect(createGroup(CREATOR, 'Neznámý obchod', TODAY)).rejects.toThrow('seznam');
});
test('vygeneruje unikátní ID', async () => {
const d1 = await createGroup(CREATOR, STORE, TODAY);
const d2 = await createGroup(USER, STORE, TODAY);
expect(d2.groups).toHaveLength(2);
expect(d2.groups![1].id).not.toBe(d2.groups![0].id);
});
describe('výběr URL na nabídku', () => {
const MULTI_STORE = 'Bistro';
const WOLT_URL = 'https://wolt.com/bistro';
const BOLT_URL = 'https://food.bolt.eu/bistro';
beforeEach(async () => {
await addStore(MULTI_STORE, ADMIN_PW, [WOLT_URL, BOLT_URL]);
});
test('uloží vybranou URL do skupiny', async () => {
const data = await createGroup(CREATOR, MULTI_STORE, TODAY, BOLT_URL);
expect(data.groups![0].url).toBe(BOLT_URL);
});
test('odmítne URL, která k obchodu nepatří', async () => {
await expect(createGroup(CREATOR, MULTI_STORE, TODAY, 'https://wolt.com/jine'))
.rejects.toThrow('nepatří');
});
test('bez výběru zůstane skupina bez URL, když má obchod více nabídek', async () => {
const data = await createGroup(CREATOR, MULTI_STORE, TODAY);
expect(data.groups![0].url).toBeUndefined();
});
test('dosadí jedinou URL obchodu automaticky', async () => {
await addStore('Pizzerie', ADMIN_PW, [WOLT_URL]);
const data = await createGroup(CREATOR, 'Pizzerie', TODAY);
expect(data.groups![0].url).toBe(WOLT_URL);
});
test('obchod bez URL vytvoří skupinu bez URL', async () => {
const data = await createGroup(CREATOR, STORE, TODAY);
expect(data.groups![0].url).toBeUndefined();
});
});
});
describe('deleteGroup', () => {
test('creator může smazat skupinu', async () => {
const d = await createGroup(CREATOR, STORE, TODAY);
const groupId = d.groups![0].id;
const result = await deleteGroup(CREATOR, groupId, TODAY);
expect(result.groups).toHaveLength(0);
});
test('nečlen nemůže smazat skupinu', async () => {
const d = await createGroup(CREATOR, STORE, TODAY);
const groupId = d.groups![0].id;
await expect(deleteGroup(USER, groupId, TODAY)).rejects.toThrow('zakladatel');
});
test('smazání neexistující skupiny vyhodí chybu', async () => {
await expect(deleteGroup(CREATOR, 'nonexistent', TODAY)).rejects.toThrow('nalezena');
});
});
describe('addGroupMember', () => {
let groupId: string;
beforeEach(async () => {
const d = await createGroup(CREATOR, STORE, TODAY);
groupId = d.groups![0].id;
});
test('uživatel se může přidat sám (open)', async () => {
const d = await addGroupMember(USER, groupId, USER, TODAY);
expect(d.groups![0].members[USER]).toBeDefined();
});
test('creator může přidat jiného uživatele', async () => {
const d = await addGroupMember(CREATOR, groupId, USER, TODAY);
expect(d.groups![0].members[USER]).toBeDefined();
});
test('nečlen nemůže přidat jiného uživatele', async () => {
await expect(addGroupMember(USER, groupId, 'dalsi', TODAY)).rejects.toThrow('zakladatel');
});
test('nelze přidat do skupiny ve stavu ordered', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
await expect(addGroupMember(USER, groupId, USER, TODAY)).rejects.toThrow('objednáno');
});
test('nelze přidat existujícího člena', async () => {
await expect(addGroupMember(CREATOR, groupId, CREATOR, TODAY)).rejects.toThrow('již');
});
});
describe('removeGroupMember', () => {
let groupId: string;
beforeEach(async () => {
const d = await createGroup(CREATOR, STORE, TODAY);
groupId = d.groups![0].id;
await addGroupMember(CREATOR, groupId, USER, TODAY);
});
test('člen se může odhlásit sám', async () => {
const d = await removeGroupMember(USER, groupId, USER, TODAY);
expect(d.groups![0].members[USER]).toBeUndefined();
});
test('creator může odebrat jiného člena', async () => {
const d = await removeGroupMember(CREATOR, groupId, USER, TODAY);
expect(d.groups![0].members[USER]).toBeUndefined();
});
test('nelze odebrat zakladatele', async () => {
await expect(removeGroupMember(CREATOR, groupId, CREATOR, TODAY)).rejects.toThrow('Zakladatel');
});
test('nečlen nemůže odebrat jiného', async () => {
await expect(removeGroupMember(USER, groupId, CREATOR, TODAY)).rejects.toThrow('zakladatel');
});
});
describe('updateGroupMember', () => {
let groupId: string;
beforeEach(async () => {
const d = await createGroup(CREATOR, STORE, TODAY);
groupId = d.groups![0].id;
await addGroupMember(CREATOR, groupId, USER, TODAY);
});
test('člen může upravit svá data (open)', async () => {
const d = await updateGroupMember(USER, groupId, USER, { amount: 150, note: 'Big Mac' }, TODAY);
expect(d.groups![0].members[USER].amount).toBe(150);
expect(d.groups![0].members[USER].note).toBe('Big Mac');
});
test('creator může upravit data jiného člena', async () => {
const d = await updateGroupMember(CREATOR, groupId, USER, { amount: 200 }, TODAY);
expect(d.groups![0].members[USER].amount).toBe(200);
});
test('člen nemůže upravit data jiného (locked)', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
await expect(updateGroupMember(USER, groupId, USER, { amount: 100 }, TODAY)).rejects.toThrow('uzamčeno');
});
test('nikdo nemůže upravit při stavu ordered', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
await expect(updateGroupMember(CREATOR, groupId, USER, { amount: 100 }, TODAY)).rejects.toThrow('objednáno');
});
});
describe('setGroupState', () => {
let groupId: string;
beforeEach(async () => {
const d = await createGroup(CREATOR, STORE, TODAY);
groupId = d.groups![0].id;
});
test('open → locked', async () => {
const d = await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
expect(d.groups![0].state).toBe(GroupState.LOCKED);
});
test('locked → open (odemčení)', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
const d = await setGroupState(CREATOR, groupId, GroupState.OPEN, TODAY);
expect(d.groups![0].state).toBe(GroupState.OPEN);
});
test('locked → ordered', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
const d = await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
expect(d.groups![0].state).toBe(GroupState.ORDERED);
});
test('open → ordered není povoleno', async () => {
await expect(setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY)).rejects.toThrow('Nelze přejít');
});
test('ordered → open není povoleno', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
await expect(setGroupState(CREATOR, groupId, GroupState.OPEN, TODAY)).rejects.toThrow('Nelze přejít');
});
test('ordered → delivered', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
const d = await setGroupState(CREATOR, groupId, GroupState.DELIVERED, TODAY);
expect(d.groups![0].state).toBe(GroupState.DELIVERED);
});
test('delivered → ordered (vrácení)', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.DELIVERED, TODAY);
const d = await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
expect(d.groups![0].state).toBe(GroupState.ORDERED);
});
test('locked → delivered není povoleno', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
await expect(setGroupState(CREATOR, groupId, GroupState.DELIVERED, TODAY)).rejects.toThrow('Nelze přejít');
});
test('delivered → locked není povoleno', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.DELIVERED, TODAY);
await expect(setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY)).rejects.toThrow('Nelze přejít');
});
test('skupinu ve stavu delivered nelze upravovat', async () => {
await addGroupMember(CREATOR, groupId, USER, TODAY);
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.DELIVERED, TODAY);
await expect(updateGroupMember(CREATOR, groupId, USER, { amount: 100 }, TODAY)).rejects.toThrow('doručeno');
await expect(addGroupMember(CREATOR, groupId, 'dalsi', TODAY)).rejects.toThrow('doručeno');
});
test('nečlen nemůže měnit stav', async () => {
await expect(setGroupState(USER, groupId, GroupState.LOCKED, TODAY)).rejects.toThrow('zakladatel');
});
test('ordered → locked smaže sledování objednávky', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
const token = 'a'.repeat(64);
await setGroupTracking(CREATOR, groupId, `https://food.bolt.eu/sharedActiveOrder/${token}`, TODAY);
const d = await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
expect(d.groups![0].trackingCode).toBeUndefined();
expect(d.groups![0].trackingProvider).toBeUndefined();
});
});
describe('markGroupQrGenerated', () => {
let groupId: string;
beforeEach(async () => {
const d = await createGroup(CREATOR, STORE, TODAY);
groupId = d.groups![0].id;
});
test('ze stavu ordered přepne skupinu na delivered', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
const d = await markGroupQrGenerated(CREATOR, groupId, TODAY);
expect(d.groups![0].state).toBe(GroupState.DELIVERED);
expect(d.groups![0].qrGenerated).toBe(true);
});
test('funguje i ve stavu delivered', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.DELIVERED, TODAY);
const d = await markGroupQrGenerated(CREATOR, groupId, TODAY);
expect(d.groups![0].state).toBe(GroupState.DELIVERED);
});
test('ve stavu locked selže', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
await expect(markGroupQrGenerated(CREATOR, groupId, TODAY)).rejects.toThrow('objednáno');
});
test('QR může generovat pouze zakladatel', async () => {
await setGroupState(CREATOR, groupId, GroupState.LOCKED, TODAY);
await setGroupState(CREATOR, groupId, GroupState.ORDERED, TODAY);
await expect(markGroupQrGenerated(USER, groupId, TODAY)).rejects.toThrow('zakladatel');
});
});
describe('markGroupMemberPaid', () => {
test('označí člena jako zaplaceného pro daný den', async () => {
const d = await createGroup(CREATOR, STORE, TODAY);
const groupId = d.groups![0].id;
const result = await markGroupMemberPaid(CREATOR, groupId, TODAY);
expect(result).not.toBeNull();
expect(result!.groups![0].members[CREATOR].paid).toBe(true);
});
test('vrátí null pro neexistující skupinu', async () => {
const result = await markGroupMemberPaid(CREATOR, 'nonexistent', TODAY);
expect(result).toBeNull();
});
// Regrese: potvrzení platby jiný den, než vznikla objednávka, se musí zapsat
// ke dni objednávky – jinak se příznak ztratí a zelené zatržítko se nezobrazí.
test('zapíše platbu ke dni objednávky i při potvrzení jiný den', async () => {
const orderDay = new Date('2025-01-10');
const d = await createGroup(CREATOR, STORE, orderDay);
const groupId = d.groups![0].id;
// Uživatel potvrzuje platbu jiný den, ale předáváme datum objednávky.
const result = await markGroupMemberPaid(CREATOR, groupId, orderDay);
expect(result).not.toBeNull();
expect(result!.groups![0].members[CREATOR].paid).toBe(true);
// Skupina pod jiným dnem neexistuje – ověříme, že tam příznak omylem nevznikl.
const otherDay = new Date('2025-01-15');
const onOtherDay = await markGroupMemberPaid(CREATOR, groupId, otherDay);
expect(onOtherDay).toBeNull();
});
});