import { beforeEach, describe, expect, it } from 'vitest'; import type { Db } from '../db'; import { makeDb, seedFixture, seedOrder, type Fixture } from '../testing/helpers'; import { RuleError } from './accountRules'; import { cancelOrder, removeLine, setLineQty } from './orderEdit'; import { getUserById } from './users'; let db: Db; let f: Fixture; let orderId: number; const shopUser = () => getUserById(db, f.shopUser.id)!; const lines = () => db.prepare('SELECT ending, qty FROM order_lines WHERE order_id = ? ORDER BY ending').all(orderId) as Array<{ ending: string; qty: number }>; const order = () => db.prepare('SELECT status, edited_at FROM orders WHERE id = ?').get(orderId) as { status: string; edited_at: string | null }; beforeEach(() => { db = makeDb(); f = seedFixture(db); orderId = seedOrder(db, { shopId: f.shops.a, userId: f.shopUser.id, date: '2026-11-02', lines: [['1234', 2], ['1234', 3], ['5678', 1]] }); }); describe('changing a quantity', () => { it('replaces every line of that ending with one and marks the order edited', () => { setLineQty(db, shopUser(), orderId, '1234', 7); expect(lines()).toEqual([{ ending: '1234', qty: 7 }, { ending: '5678', qty: 1 }]); expect(order().edited_at).not.toBeNull(); }); it('refuses a quantity outside 1 to 999', () => { for (const qty of [0, -1, 1000, 1.5]) expect(() => setLineQty(db, shopUser(), orderId, '1234', qty)).toThrow(RuleError); expect(order().edited_at).toBeNull(); }); it('refuses an ending that is not in the order', () => { expect(() => setLineQty(db, shopUser(), orderId, '9999', 1)).toThrow(RuleError); }); }); describe('removing an ending', () => { it('removes every line of it', () => { removeLine(db, shopUser(), orderId, '1234'); expect(lines()).toEqual([{ ending: '5678', qty: 1 }]); expect(order()).toMatchObject({ status: 'confirmed' }); }); it('cancels the order when its last ending goes', () => { removeLine(db, shopUser(), orderId, '1234'); removeLine(db, shopUser(), orderId, '5678'); expect(order().status).toBe('cancelled'); }); }); describe('cancelling an order', () => { it('cancels it and keeps the record', () => { cancelOrder(db, shopUser(), orderId); expect(order().status).toBe('cancelled'); expect(lines()).toHaveLength(3); }); it('cannot be done twice', () => { cancelOrder(db, shopUser(), orderId); expect(() => cancelOrder(db, shopUser(), orderId)).toThrow(RuleError); }); }); describe('who may edit', () => { it('lets an admin edit any order', () => { cancelOrder(db, getUserById(db, f.admin.id)!, orderId); expect(order().status).toBe('cancelled'); }); it('refuses a user of another shop, as if the order did not exist', () => { const other = seedOrder(db, { shopId: f.shops.b, userId: f.multiUser.id, date: '2026-11-02', lines: [['0001', 1]] }); expect(() => cancelOrder(db, shopUser(), other)).toThrow(/order not found/); }); it('refuses an inactive user', () => { expect(() => cancelOrder(db, getUserById(db, f.disabledUser.id)!, orderId)).toThrow(RuleError); }); it('still lets a user edit an order of a shop that was switched off', () => { db.prepare('UPDATE shops SET active = 0 WHERE id = ?').run(f.shops.a); setLineQty(db, shopUser(), orderId, '5678', 4); expect(lines()).toContainEqual({ ending: '5678', qty: 4 }); }); });