Files
client-web/src/modules/first-leg/store/firstLegWorkbench.test.mjs
T

173 lines
9.2 KiB
JavaScript

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)
})