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(); }); });