fix: sync issue
CI / build-and-test (push) Canceled after 0s

This commit is contained in:
2026-10-21 00:24:06 +02:00
parent e7f59f7583
commit 40ba5f9564
@@ -2,6 +2,7 @@ import { v4 as uuidv4 } from 'uuid';
import { DevicesService } from './devices.service';
import { SyncDto } from './dto/sync.dto';
import { createFakePrisma, FakePrisma } from '../testing/fake-prisma';
import { StockService } from '../stock/stock.service';
type Device = { id: string; venueId: string; revokedAt: null; lastSeenAt: Date };
type SyncResult = Awaited<ReturnType<DevicesService['sync']>>;
@@ -416,3 +417,117 @@ describe('DevicesService.sync live update signals', () => {
expect(realtime.notify).toHaveBeenCalledWith('v1', 'device');
});
});
describe('DevicesService.sync: website product changes reach the tablets', () => {
const admin = { id: 'u1', email: 'admin@example.com', name: 'Admin' };
function setupWithWebsite() {
const venue = setup();
const stock = new StockService(venue.prisma as any, { log: jest.fn() } as any, { notify: jest.fn() } as any);
return { ...venue, stock };
}
it('sends a new price to every tablet in its next sync', async () => {
const { sync, tabletA, tabletB, stock, prisma } = setupWithWebsite();
addProduct(prisma);
await stock.updateProduct('v1', 'cola', { priceInCents: 270 }, admin);
const a = await sync(tabletA);
const b = await sync(tabletB);
for (const result of [a, b]) {
expect(result.catalog).toEqual([expect.objectContaining({ id: 'cola', priceInCents: 270 })]);
}
});
it('keeps old drinks at their price and puts new ones on a new line', async () => {
const { sync, tabletA, tabletB, stock, prisma } = setupWithWebsite();
addProduct(prisma);
await sync(tabletA, {
events: [events.opened('t1', 'Alice'), events.line('line_added', 't1', 2, { lineId: 'old-line' })],
});
await stock.updateProduct('v1', 'cola', { priceInCents: 270 }, admin);
// The tablet charges the new price on a line of its own
const result = await sync(tabletB, {
events: [events.line('line_added', 't1', 1, { lineId: 'new-line', price: 270, minutes: 5 })],
});
expect(tab(result, 't1')!.lines.map((l) => [l.id, l.unitPriceInCents, l.quantity])).toEqual([
['old-line', 250, 2],
['new-line', 270, 1],
]);
});
it('settles every line at its own price', async () => {
const { sync, tabletA, prisma } = setupWithWebsite();
addProduct(prisma);
const settled = {
...events.settled('t1', 0, 10),
amountInCents: 2 * 250 + 270,
items: [
{ productId: 'cola', productName: 'Cola', quantity: 2, unitPriceInCents: 250 },
{ productId: 'cola', productName: 'Cola', quantity: 1, unitPriceInCents: 270 },
],
};
const result = await sync(tabletA, {
events: [
events.opened('t1', 'Alice'),
events.line('line_added', 't1', 2, { lineId: 'old-line' }),
events.line('line_added', 't1', 1, { lineId: 'new-line', price: 270, minutes: 5 }),
settled,
],
});
expect(tab(result, 't1')!.lines).toEqual([]);
expect(result.settlements[0].lines.map((l) => [l.unitPriceInCents, l.quantity])).toEqual([[250, 2], [270, 1]]);
});
it('counts both price lines down from the one product stock', async () => {
const { sync, tabletA, stock, prisma } = setupWithWebsite();
addProduct(prisma, 'cola', { stock: 20, countedAt: new Date('2026-01-01') });
await sync(tabletA, { events: [events.opened('t1', 'Alice'), events.line('line_added', 't1', 2)] });
await stock.updateProduct('v1', 'cola', { priceInCents: 270 }, admin);
const result = await sync(tabletA, {
events: [events.line('line_added', 't1', 1, { lineId: 'new-line', price: 270, minutes: 5 })],
});
expect(colaStock(result)).toBe(17);
});
it('sends a product added on the website with its starting stock', async () => {
const { sync, tabletA, stock } = setupWithWebsite();
const created = await stock.createProduct('v1', { name: 'Ice Tea', category: 'Frisdrank', priceInCents: 280, stock: 24 }, admin);
const result = await sync(tabletA);
expect(result.catalog).toEqual([expect.objectContaining({ id: created.productId, name: 'Ice Tea', priceInCents: 280 })]);
expect(result.stock).toEqual([expect.objectContaining({ productId: created.productId, stock: 24 })]);
});
it('marks an archived product, and still counts a sale from a tablet that had not synced yet', async () => {
const { sync, tabletA, stock, prisma } = setupWithWebsite();
addProduct(prisma, 'cola', { stock: 20, countedAt: new Date('2026-01-01') });
await stock.updateProduct('v1', 'cola', { archived: true }, admin);
const result = await sync(tabletA, {
events: [events.opened('t1', 'Alice'), events.line('line_added', 't1', 1, { minutes: 1 })],
});
expect(result.catalog).toEqual([expect.objectContaining({ id: 'cola', archived: true })]);
expect(tab(result, 't1')!.lines).toMatchObject([{ productId: 'cola', quantity: 1 }]);
expect(colaStock(result)).toBe(19);
});
it('a renamed product keeps its id, so tablets update it instead of adding one', async () => {
const { sync, tabletA, stock, prisma } = setupWithWebsite();
addProduct(prisma);
await stock.updateProduct('v1', 'cola', { name: 'Coca-Cola' }, admin);
const result = await sync(tabletA);
expect(result.catalog).toEqual([expect.objectContaining({ id: 'cola', name: 'Coca-Cola' })]);
});
});