import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import test from 'node:test'; import type { Vehicle, VehicleRow } from '../../types.js'; import { FLOW_STATS_SQL, MAIN_SQL, VehicleRepository, WEEKLY_DETAIL_SQL, WEEKLY_TRUCK_ID_SQLS, } from './repository.js'; class FakeDatabase { readonly calls: Array<{ sql: string; values?: any[] }> = []; constructor(private readonly responses: any[][]) {} async query(sql: string, values?: any[]): Promise<[T, ...any[]]> { this.calls.push({ sql, values }); return [this.responses.shift() as T]; } } function vehicle(id: string): Vehicle { return { id } as Vehicle; } test('keeps all vehicle GET endpoints in their established order', () => { const source = readFileSync(new URL('./routes.ts', import.meta.url), 'utf8'); const paths = Array.from( source.matchAll(/app\.get\('([^']+)'|app\.on\(\[([^\]]+)\],\s*'([^']+)'/g), ).flatMap((match) => match[1] ? [match[1]] : /'GET'/.test(match[2]) ? [match[3]] : []); assert.deepEqual(paths, [ '/summary', '/by-type', '/by-batch', '/inventory-analysis', '/dept-stats', '/region-stats', '/customer-stats', '/analysis', '/list', '/inventory-stats', '/weekly-detail', '/flow-stats', '/subjects', '/refresh', '/debug', '/region-chart', ]); assert.doesNotMatch(source, /\bSELECT\b|pool\.query|MAIN_SQL/); }); test('keeps the non-empty vehicle cache for less than 5 seconds', async () => { let now = 100_000; const db = new FakeDatabase([[{ id: 'first' }], [{ id: 'second' }]]); const repository = new VehicleRepository( db, () => now, (row: VehicleRow) => vehicle(row.id), ); assert.deepEqual(await repository.getVehicles(), [vehicle('first')]); now += 4_999; assert.deepEqual(await repository.getVehicles(), [vehicle('first')]); now += 1; assert.deepEqual(await repository.getVehicles(), [vehicle('second')]); assert.deepEqual(db.calls.map((call) => call.sql), [MAIN_SQL, MAIN_SQL]); }); test('keeps empty vehicle results uncached', async () => { const db = new FakeDatabase([[], []]); const repository = new VehicleRepository(db, () => 100_000); await repository.getVehicles(); await repository.getVehicles(); assert.equal(db.calls.length, 2); }); test('refresh invalidates vehicles but retains the weekly truck-id cache', async () => { const db = new FakeDatabase([ [{ id: 'first' }], [{ truck_id: 'pending' }], [{ truck_id: 'delivered' }], [{ truck_id: 'returned' }], [{ truck_id: 'replaced' }, { truck_id: null }], [{ id: 'second' }], ]); const repository = new VehicleRepository( db, () => 100_000, (row: VehicleRow) => vehicle(row.id), ); await repository.getVehicles(); const weeklyBefore = await repository.getWeeklyTruckIds(); repository.invalidateRefreshCaches(); assert.deepEqual(await repository.getVehicles(), [vehicle('second')]); assert.equal(await repository.getWeeklyTruckIds(), weeklyBefore); assert.deepEqual(db.calls.map((call) => call.sql), [MAIN_SQL, ...WEEKLY_TRUCK_ID_SQLS, MAIN_SQL]); assert.deepEqual([...weeklyBefore.replaced], ['replaced']); }); test('keeps weekly detail SQL selection and flow date parameters unchanged', async () => { const db = new FakeDatabase([[{ truck_id: '1' }], [{ id: 'delivered-1' }]]); const repository = new VehicleRepository(db); assert.deepEqual(await repository.getWeeklyDetailRows('new'), [{ truck_id: '1' }]); assert.deepEqual(await repository.getFlowDetailRows('2026-08-01', '2026-08-13'), [{ id: 'delivered-1' }]); assert.deepEqual(db.calls, [ { sql: WEEKLY_DETAIL_SQL.new, values: undefined }, { sql: FLOW_STATS_SQL, values: ['2026-08-01', '2026-08-13', '2026-08-01', '2026-08-13'], }, ]); }); test('flow dates and inclusive day boundaries use operations signing times, retaining creation time only for display', () => { for (const [alias, field] of [['dv', 'delivery_completion_processed_time'], ['r', 'return_completion_processed_time']]) { assert.ok(FLOW_STATS_SQL.includes(`DATE_FORMAT(${alias}.${field}, '%Y-%m-%d') AS stat_date`)); assert.ok(FLOW_STATS_SQL.includes(`AND ${alias}.${field} IS NOT NULL`)); assert.ok(FLOW_STATS_SQL.includes(`AND ${alias}.${field} >= ?`)); assert.ok(FLOW_STATS_SQL.includes(`AND ${alias}.${field} < DATE_ADD(?, INTERVAL 1 DAY)`)); assert.ok(FLOW_STATS_SQL.includes(`DATE_FORMAT(${alias}.create_time, '%Y-%m-%d %H:%i:%s') AS submit_time`)); } assert.doesNotMatch(FLOW_STATS_SQL, /AND \w+\.create_time/); assert.ok(FLOW_STATS_SQL.includes('ORDER BY flow.event_time DESC, flow.id DESC')); }); test('flow retains multiple business events for the same vehicle', async () => { const events = [ { id: 'delivered-1', truck_id: '1', type: 'delivered', stat_date: '2026-09-16' }, { id: 'delivered-2', truck_id: '1', type: 'delivered', stat_date: '2026-09-16' }, { id: 'returned-1', truck_id: '1', type: 'returned', stat_date: '2026-09-16' }, ]; const repository = new VehicleRepository(new FakeDatabase([events])); assert.deepEqual(await repository.getFlowDetailRows('2026-09-16', '2026-09-16'), events); }); test('replacement trips come exclusively from linked delivery records', () => { assert.ok(FLOW_STATS_SQL.includes("CASE WHEN dv.vehicle_replacement_id IS NOT NULL THEN 'replaced' ELSE 'delivered' END AS type")); assert.ok(FLOW_STATS_SQL.includes("CASE WHEN dv.vehicle_replacement_id IS NOT NULL THEN '替换交车' ELSE '交车' END AS type_label")); assert.equal((FLOW_STATS_SQL.match(/FROM delivery_vehicle/g) || []).length, 1); assert.doesNotMatch(FLOW_STATS_SQL, /FROM vehicle_replacement|dv\.delivery_time|r\.arrival_time|vr\.replace_time/); }); test('delivery and replacement trips include awaiting customer signature and exclude suspended subjects', () => { const [deliveries, returns] = FLOW_STATS_SQL.split('UNION ALL'); assert.ok(deliveries.includes('AND dv.delivery_status IN (2,3,5,20)')); assert.ok(deliveries.includes('AND (dts.status IS NULL OR dts.status <> 10)')); assert.doesNotMatch(deliveries, /dts\.status\s*(?:=|IN)|dv\.delivery_status IN \(2,3,5\)/); assert.ok(returns.includes('AND r.status IN (2,3,5)')); }); test('return history includes completed, signed and awaiting-signature statuses without arrival or settlement requirements', () => { const returns = FLOW_STATS_SQL.split('UNION ALL')[1]; assert.ok(returns.includes('AND r.status IN (2,3,5)')); assert.ok(returns.includes('AND r.return_completion_processed_time >= ?')); assert.doesNotMatch(returns, /r\.is_arrived|r\.arrival_time|settlement|AND dts\.status|AND \(dts\.status/); });