173 lines
9.2 KiB
JavaScript
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)
|
|
})
|