import { describe, it, expect } from 'vitest'; import { resolveSalesState, publicSalesFields, isOnlineSalesClosed } from './salesState.js'; const START = '2030-01-01T23:00:00.000Z'; // 20:00 in Asunción const END = '2030-01-02T02:00:00.000Z'; const startMs = new Date(START).getTime(); const minutes = (n: number) => n * 60_000; // Pre-sale closes 120 minutes before the start (the site default). const event = { status: 'published', startDatetime: START, endDatetime: END, externalBookingEnabled: false, price: 21000, walkInPrice: 30000 as number | null, }; const settings = { presaleClosureEnabled: true, presaleCloseMinutesBefore: 120 }; const beforeClose = startMs - minutes(121); const afterClose = startMs - minutes(60); describe('resolveSalesState', () => { it('is online while pre-sale is open and seats are left', () => { expect(resolveSalesState(event, settings, 10, beforeClose)).toBe('online'); }); it('is door after pre-sale closes, until the event ends', () => { expect(resolveSalesState(event, settings, 10, afterClose)).toBe('door'); expect(resolveSalesState(event, settings, 10, startMs + minutes(30))).toBe('door'); expect(resolveSalesState(event, settings, 10, new Date(END).getTime() - 1)).toBe('door'); }); it('is sold_out with no seats left, online or at the door', () => { expect(resolveSalesState(event, settings, 0, beforeClose)).toBe('sold_out'); expect(resolveSalesState(event, settings, 0, afterClose)).toBe('sold_out'); }); it('is ended once the end time passes, or the start time when there is no end', () => { expect(resolveSalesState(event, settings, 10, new Date(END).getTime())).toBe('ended'); expect(resolveSalesState(event, settings, 0, new Date(END).getTime())).toBe('ended'); expect(resolveSalesState({ ...event, endDatetime: null }, settings, 10, startMs)).toBe('ended'); expect(resolveSalesState({ ...event, status: 'completed' }, settings, 10, beforeClose)).toBe('ended'); }); it('is external for external booking events, with no door state', () => { const external = { ...event, externalBookingEnabled: true }; expect(resolveSalesState(external, settings, 10, beforeClose)).toBe('external'); expect(resolveSalesState(external, settings, 10, afterClose)).toBe('external'); expect(resolveSalesState({ ...event, externalBookingEnabled: 1 }, settings, 10, afterClose)).toBe('external'); }); it('keeps cancelled events cancelled', () => { expect(resolveSalesState({ ...event, status: 'cancelled' }, settings, 10, afterClose)).toBe('cancelled'); }); it('closes online sales at the start when pre-sale closure is off', () => { const noClosure = { ...event, presaleClosureEnabled: false }; expect(resolveSalesState(noClosure, settings, 10, startMs - 1)).toBe('online'); expect(resolveSalesState(noClosure, settings, 10, startMs)).toBe('door'); expect(isOnlineSalesClosed(noClosure, settings, startMs - 1)).toBe(false); expect(isOnlineSalesClosed(noClosure, settings, startMs)).toBe(true); }); }); describe('publicSalesFields', () => { it('door with walk_in_price set: doorPrice is the walk-in price', () => { expect(publicSalesFields(event, settings, 48, afterClose)).toEqual({ salesState: 'door', doorPrice: 30000 }); }); it('door with walk_in_price null: doorPrice falls back to the ticket price', () => { expect(publicSalesFields({ ...event, walkInPrice: null }, settings, 48, afterClose)) .toEqual({ salesState: 'door', doorPrice: 21000 }); // Postgres decimals arrive as strings expect(publicSalesFields({ ...event, price: '21000.00', walkInPrice: null }, settings, 48, afterClose).doorPrice) .toBe(21000); }); it('door with a free walk-in keeps 0, not the ticket price', () => { expect(publicSalesFields({ ...event, walkInPrice: 0 }, settings, 48, afterClose).doorPrice).toBe(0); }); it('omits doorPrice in every other state', () => { const cases: Array<[number, number, any]> = [ [10, beforeClose, event], // online [0, afterClose, event], // sold_out [10, new Date(END).getTime(), event], // ended [10, afterClose, { ...event, externalBookingEnabled: true }], // external [10, afterClose, { ...event, status: 'cancelled' }], // cancelled ]; for (const [spots, now, e] of cases) { const fields = publicSalesFields(e, settings, spots, now); expect(fields.salesState).not.toBe('door'); expect(fields).not.toHaveProperty('doorPrice'); } }); });