import express from 'express'; import request from 'supertest'; import bodyParser from 'body-parser'; import { generateToken } from '../auth'; import { resetMemoryStorage } from '../storage/memory'; import getStorage from '../storage'; import groupRouter from '../routes/groupRoutes'; import { DEFAULT_GROUP_LIMITS, getGroupLimits, resetGroupLimits } from '../limits'; const MAX_NOTE_LENGTH = DEFAULT_GROUP_LIMITS.maxNoteLength; const MAX_SURCHARGE_TEXT_LENGTH = DEFAULT_GROUP_LIMITS.maxSurchargeTextLength; /** Maximum částky v haléřích */ const MAX_AMOUNT = DEFAULT_GROUP_LIMITS.maxAmountKc * 100; jest.mock('../websocket', () => ({ getWebsocket: () => ({ emit: jest.fn() }) })); const CREATOR = 'kreator'; const TOKEN = `Bearer ${generateToken(CREATOR)}`; function buildApp() { const app = express(); app.use(bodyParser.json()); app.use('/api/groups', groupRouter); app.use((err: any, _req: any, res: any, _next: any) => { res.status(500).json({ error: err.message }); }); return app; } /** Založí skupinu a vrátí její ID. */ async function createTestGroup(app: express.Express): Promise { const res = await request(app).post('/api/groups/create').set('Authorization', TOKEN).send({ name: 'Bistro' }); expect(res.status).toBe(200); return res.body.groups[0].id; } beforeEach(async () => { resetMemoryStorage(); resetGroupLimits(); await getStorage().setData('stores', [{ name: 'Bistro' }]); }); describe('limity vstupů skupinových objednávek', () => { test('poznámka a popis příplatku mají maximální délku', async () => { const app = buildApp(); const id = await createTestGroup(app); const post = (body: object) => request(app).post('/api/groups/updateMember').set('Authorization', TOKEN).send({ id, login: CREATOR, ...body }); expect((await post({ note: 'x'.repeat(MAX_NOTE_LENGTH) })).status).toBe(200); const tooLong = await post({ note: 'x'.repeat(MAX_NOTE_LENGTH + 1) }); expect(tooLong.status).toBe(400); expect(tooLong.body.error).toContain(`${MAX_NOTE_LENGTH} znaků`); expect((await post({ surchargeText: 'x'.repeat(MAX_SURCHARGE_TEXT_LENGTH + 1) })).status).toBe(400); }); test('částka a příplatek mají maximální výši', async () => { const app = buildApp(); const id = await createTestGroup(app); const post = (body: object) => request(app).post('/api/groups/updateMember').set('Authorization', TOKEN).send({ id, login: CREATOR, ...body }); expect((await post({ amount: MAX_AMOUNT })).status).toBe(200); const tooMuch = await post({ amount: MAX_AMOUNT + 1 }); expect(tooMuch.status).toBe(400); expect(tooMuch.body.error).toContain('10000 Kč'); expect((await post({ surchargeAmount: MAX_AMOUNT + 1 })).status).toBe(400); expect((await post({ amount: -1 })).status).toBe(400); }); test('poplatky mají maximum a procentní sleva nesmí přesáhnout 100 %', async () => { const app = buildApp(); const id = await createTestGroup(app); const post = (body: object) => request(app).post('/api/groups/updateFees').set('Authorization', TOKEN).send({ id, ...body }); expect((await post({ shipping: MAX_AMOUNT + 1 })).status).toBe(400); expect((await post({ discountType: 'percent', discountValue: 101 })).status).toBe(400); expect((await post({ discountType: 'percent', discountValue: 100 })).status).toBe(200); // Jen hodnota při již nastavené procentní slevě — pojistka v groups.ts const onlyValue = await post({ discountValue: 150 }); expect(onlyValue.status).toBe(500); expect(onlyValue.body.error).toContain('100 %'); expect((await post({ discountType: 'fixed', discountValue: MAX_AMOUNT + 1 })).status).toBe(400); }); test('název skupiny a odkaz na sledování mají maximální délku', async () => { const app = buildApp(); expect((await request(app).post('/api/groups/create').set('Authorization', TOKEN).send({ name: 'x'.repeat(101) })).status).toBe(400); const id = await createTestGroup(app); const res = await request(app).post('/api/groups/setTracking').set('Authorization', TOKEN).send({ id, shareUrl: 'https://x.cz/' + 'a'.repeat(600) }); expect(res.status).toBe(400); }); }); describe('limity z proměnných prostředí', () => { const ENV_KEYS = ['GROUP_MAX_NOTE_LENGTH', 'GROUP_MAX_AMOUNT_KC', 'GROUP_MAX_ITEMS']; afterEach(() => { ENV_KEYS.forEach(k => delete process.env[k]); resetGroupLimits(); }); test('env přepíše výchozí hodnoty a limity se uplatní v API', async () => { process.env.GROUP_MAX_NOTE_LENGTH = '20'; process.env.GROUP_MAX_AMOUNT_KC = '500'; process.env.GROUP_MAX_ITEMS = '5'; resetGroupLimits(); expect(getGroupLimits()).toEqual({ ...DEFAULT_GROUP_LIMITS, maxNoteLength: 20, maxAmountKc: 500, maxItems: 5 }); const app = buildApp(); const id = await createTestGroup(app); const post = (body: object) => request(app).post('/api/groups/updateMember').set('Authorization', TOKEN).send({ id, login: CREATOR, ...body }); expect((await post({ note: 'x'.repeat(21) })).body.error).toContain('20 znaků'); expect((await post({ amount: 50001 })).body.error).toContain('500 Kč'); expect((await post({ amount: 50000 })).status).toBe(200); }); test('neplatná hodnota v env se ignoruje (zůstane výchozí)', () => { const warn = jest.spyOn(console, 'warn').mockImplementation(() => { }); process.env.GROUP_MAX_NOTE_LENGTH = 'hodně'; process.env.GROUP_MAX_AMOUNT_KC = '-5'; process.env.GROUP_MAX_ITEMS = '999999'; resetGroupLimits(); expect(getGroupLimits()).toEqual(DEFAULT_GROUP_LIMITS); expect(warn).toHaveBeenCalledTimes(3); warn.mockRestore(); }); });