import assert from 'node:assert/strict' import test from 'node:test' import { readFileSync } from 'node:fs' import ts from 'typescript' import * as vue from 'vue' import * as pinia from 'pinia' import { requireOrderProgress } from '../utils/orderProgress.ts' const source = readFileSync(new URL('./firstLegOrders.ts', import.meta.url), 'utf8') const code = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText const row = (id, mainStatus = 'LOADED') => ({ orderId: String(id), orderNo: `FL-${id}`, mainStatus, orderVersion: 7 }) function result(page = 1, size = 10, total = 215) { return { page: { page, size, total, pages: Math.ceil(total / size), records: Array.from({ length: Math.min(size, Math.max(0, total - (page - 1) * size)) }, (_, i) => row((page - 1) * size + i + 1)) }, queues: { all: total, fee: 2, customs: 1, draft: 3, loaded: 0, declared: 0, cleared: 0, scheduled: 0, delivering: 0, delivered: 0 }, channelNames: ['AIR', 'SEA'], progress: Array.from({ length: Math.min(size, Math.max(0, total - (page - 1) * size)) }, (_, i) => ({ orderId: String((page - 1) * size + i + 1), stage: 'loaded', partial: false })), } } function deferred() { let resolve, reject; const promise = new Promise((a, b) => { resolve = a; reject = b }); return { promise, resolve, reject } } function harness(t, read) { const context = pinia.createPinia(); pinia.setActivePinia(context) t.after(() => pinia.disposePinia(context)) const calls = [], details = [], warehouseCalls = [] const deps = { vue, pinia, '@/modules/first-leg/api/firstLeg': {}, '@/modules/first-leg/api/unifiedOrders': { getMerchantOrderWorkbench: query => { calls.push(structuredClone(query)); return read(query) }, getMerchantUnifiedOrder: async id => { details.push(id) return { progress: { orderId: id, stage: 'none', partial: false }, summary: row(id, 'DRAFT'), order: { order: { id, status: 'DRAFT', version: 7 } }, timeline: [] } }, }, '@/modules/first-leg/utils/firstLegOrder': { getFirstLegStatusLabel: value => value }, '@/modules/first-leg/utils/warehousePagination': { loadAllWarehouseRecords: async type => { warehouseCalls.push(type); return [] } }, '@/modules/first-leg/utils/orderProgress': { requireOrderProgress }, } const exports = {} new Function('require', 'exports', code)(id => { assert.ok(id in deps, id); return deps[id] }, exports) return { store: exports.useFirstLegOrdersStore(), calls, details, warehouseCalls } } test('loads beyond 200 with server totals and forwards every filter using one request per page', async t => { const h = harness(t, q => result(q.page, q.size)) const query = { page: 21, size: 10, cargoKeyword: 'SKU', channelName: 'SEA', receivingWarehouseId: '42', destinationType: 'FBA', trackingNumber: 'TRACK', mainStatus: 'LOADED', queue: 'all', createdFrom: '2026-10-01T00:00:00', createdTo: '2026-10-03T23:59:59.999' } assert.equal(await h.store.load(query), 10) assert.equal(h.store.orders[0].id, '201') assert.equal(h.store.total, 215) assert.deepEqual(h.calls, [query]) assert.deepEqual(h.store.queueCounts, { all: 215, fee: 2, customs: 1, draft: 3, loaded: 0, declared: 0, cleared: 0, scheduled: 0, delivering: 0, delivered: 0 }) assert.deepEqual(h.store.queryChannelNames, ['AIR', 'SEA']) await h.store.load({ page: 22, size: 10 }) assert.deepEqual(h.store.orders.map(item => item.id), ['211', '212', '213', '214', '215']) assert.equal(h.calls.length, 2) assert.deepEqual(h.warehouseCalls, ['DOMESTIC']) }) test('late success cannot replace newer rows, counts, channels or loading state', async t => { const a = deferred(), b = deferred() const h = harness(t, q => q.page === 1 ? a.promise : b.promise) const first = h.store.load({ page: 1 }), second = h.store.load({ page: 2 }) b.resolve(result(2)); await second a.resolve(result(1, 10, 999)); await first assert.equal(h.store.orders[0].id, '11'); assert.equal(h.store.total, 215) assert.equal(h.store.queueCounts.all, 215); assert.equal(h.store.loading, false) }) test('late failure cannot clear the current page and an older completion cannot stop loading a newer page', async t => { const a = deferred(), b = deferred(), c = deferred() const h = harness(t, q => [a, b, c][q.page - 1].promise) const first = h.store.load({ page: 1 }), second = h.store.load({ page: 2 }) a.resolve(result(1)); await first assert.equal(h.store.loading, true); assert.equal(h.store.orders.length, 0) const third = h.store.load({ page: 3 }) c.resolve(result(3)); await third b.reject(new Error('old failure')); await second assert.equal(h.store.orders[0].id, '21'); assert.equal(h.store.loadError, '') }) test('current failure clears stale records and totals, never falls back, and retry restores the page', async t => { let fail = false const h = harness(t, () => fail ? Promise.reject(new Error('查询失败')) : result()) await h.store.load(); fail = true; await h.store.load() assert.equal(h.store.orders.length, 0); assert.equal(h.store.total, 0) assert.equal(h.store.queueCounts.all, 0); assert.equal(h.store.loadError, '查询失败') assert.equal(h.store.loading, false) fail = false; await h.store.load(); assert.equal(h.store.orders.length, 10) assert.equal(h.calls.length, 3); assert.equal(h.store.loadError, '') }) test('rejects inconsistent metadata, truncated pages and duplicate rows without displaying partial data', async t => { let response const h = harness(t, () => response) const corruptions = [r => { r.page.total = -1 }, r => { r.page.pages = 1 }, r => { r.page.page = 2 }, r => { r.page.records.pop() }, r => { r.page.records[1] = r.page.records[0] }, r => { r.queues.all = 99 }, r => { delete r.queues }, r => { r.channelNames = null }] for (const corrupt of corruptions) { response = result(); corrupt(response); await h.store.load() assert.equal(h.store.orders.length, 0); assert.equal(h.store.total, 0) assert.match(h.store.loadError, /分页数据不完整/); assert.equal(h.store.loading, false) } }) test('draft and cancelled rows retain authoritative versions; off-page detail cannot inflate list or totals', async t => { const response = result(1, 10, 2) response.page.records = [row(1, 'DRAFT'), row(2, 'CANCELLED')] const h = harness(t, () => response) await h.store.load() assert.deepEqual(h.store.orders.map(item => [item.status, item.version]), [['DRAFT', 7], ['CANCELLED', 7]]) await h.store.loadDetail('900') assert.equal(h.store.orders.length, 2); assert.equal(h.store.total, 2) assert.equal(h.store.getById('900').status, 'DRAFT') }) test('queue total is checked against the selected queue rather than all orders', async t => { const response = result(1, 10, 2) response.queues = { all: 215, fee: 2, customs: 1, draft: 3, loaded: 0, declared: 0, cleared: 0, scheduled: 0, delivering: 0, delivered: 0 } const h = harness(t, () => response) await h.store.load({ queue: 'fee' }) assert.equal(h.store.total, 2); assert.equal(h.store.queueCounts.all, 215) assert.equal(h.store.loadError, '') }) test('retains server progress and rejects missing or foreign progress without inferring milestones', async t => { let response = result() response.progress[0] = { orderId: '1', stage: 'scheduled', partial: true } const h = harness(t, () => response) await h.store.load() assert.deepEqual(h.store.orderProgress['1'], response.progress[0]) response = result(); response.progress[0].orderId = 'foreign' await h.store.load() assert.match(h.store.loadError, /分页数据不完整/) assert.deepEqual(h.store.orderProgress, {}) response = result(); delete response.queues.cleared await h.store.load(); assert.match(h.store.loadError, /分页数据不完整/) }) test('category counts remain visible throughout refresh and update together after the response is validated', async t => { const pending = deferred() let refreshing = false const h = harness(t, () => refreshing ? pending.promise : result()) await h.store.load() const counts = h.store.queueCounts refreshing = true const request = h.store.load({ queue: 'fee' }) assert.equal(h.store.loading, true) assert.equal(h.store.queueCounts, counts) assert.equal(h.store.queueCounts.fee, 2) const updated = result(1, 10, 3) updated.queues = { ...updated.queues, all: 216, fee: 3 } pending.resolve(updated) await request assert.equal(h.store.queueCounts.all, 216) assert.equal(h.store.queueCounts.fee, 3) assert.equal(h.store.loading, false) }) test('rapid category switches preserve counts and ignore an older response while the newest request is pending', async t => { const older = deferred(), latest = deferred() const h = harness(t, q => q.queue === 'fee' ? older.promise : q.queue === 'customs' ? latest.promise : result()) await h.store.load() const oldRequest = h.store.load({ queue: 'fee' }) const newRequest = h.store.load({ queue: 'customs' }) const oldResult = result(1, 10, 2); oldResult.queues.all = 999 older.resolve(oldResult); await oldRequest assert.equal(h.store.queueCounts.all, 215) assert.equal(h.store.loading, true) const newResult = result(1, 10, 1); newResult.queues.all = 216 latest.resolve(newResult); await newRequest assert.equal(h.store.queueCounts.all, 216) assert.equal(h.store.total, 1) })