91 lines
4.5 KiB
JavaScript
91 lines
4.5 KiB
JavaScript
import assert from 'node:assert/strict'
|
|||
|
|
import test from 'node:test'
|
||
|
|
import { readFileSync } from 'node:fs'
|
||
|
|
import { run as runFleet } from './fleet-management.mjs'
|
||
|
|
import { run as runAnalytics } from './self-operated-analytics.mjs'
|
||
|
|
|
||
|
|
const entry = readFileSync(new URL('../admin-test-env-acceptance.mjs', import.meta.url), 'utf8')
|
||
|
|
|
||
|
|
function context(handler) {
|
||
|
|
const calls = []
|
||
|
|
const gaps = []
|
||
|
|
return {
|
||
|
|
marker: 'ADMIN-API-OFFLINE',
|
||
|
|
calls,
|
||
|
|
gaps,
|
||
|
|
async request(method, path) {
|
||
|
|
calls.push({ method, path })
|
||
|
|
return handler(method, path)
|
||
|
|
},
|
||
|
|
apiSuccess(_module, _name, response) {
|
||
|
|
assert.equal(response.http, 200)
|
||
|
|
assert.equal(String(response.body?.code), '200')
|
||
|
|
return response.body.data
|
||
|
|
},
|
||
|
|
assert(_module, name, _response, condition, evidence) {
|
||
|
|
assert.equal(Boolean(condition), true, `${name}: ${evidence}`)
|
||
|
|
return true
|
||
|
|
},
|
||
|
|
addCoverageGap(module, scenario, name, reason, category, metadata) {
|
||
|
|
gaps.push({ module, scenario, name, reason, category, metadata })
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
test('车队管理模块注册为默认模块并只读核对车次、出车、报价与拼车需求', async () => {
|
||
|
|
assert.match(entry, /const CONFIGURED_MODULES = \[[^\n]*'fleet-management'/)
|
||
|
|
const tripRow = { tripNo: 'TRIP-1', status: 'PLANNED', recordVersion: 0 }
|
||
|
|
const ctx = context((method, path) => {
|
||
|
|
if (path.endsWith('/fleet/trips/FLEET-MISSING-TRIP')) {
|
||
|
|
return { http: 200, body: { code: 'A0404', message: '车次不存在' } }
|
||
|
|
}
|
||
|
|
if (path.includes('/fleet/trips/TRIP-1')) {
|
||
|
|
return { http: 200, body: { code: '200', data: {
|
||
|
|
trip: tripRow, dispatches: [], loadingReview: { recordVersion: null }, previousDispatches: [] } } }
|
||
|
|
}
|
||
|
|
if (path.includes('/fleet/trips')) {
|
||
|
|
return { http: 200, body: { code: '200', data: { records: [tripRow], page: 1, size: 20, total: 1, pages: 1 } } }
|
||
|
|
}
|
||
|
|
if (path.includes('/fleet/operations')) {
|
||
|
|
return { http: 200, body: { code: '200', data: {
|
||
|
|
page: { records: [], page: 1, size: 20, total: 0, pages: 0 },
|
||
|
|
summary: { trips: 0, inTransit: 0, completed: 0, physicalPallets: 0, palletSpaces: 0, ownPackages: 0,
|
||
|
|
externalPackages: 0, externalAgreedAmounts: [], costs: { visible: true, unrecordedTrips: null, unallocatedTrips: null, totals: [] } },
|
||
|
|
charts: { physicalPallets: [], destinationTrips: [], externalAgreedAmounts: [], externalPalletRates: [] },
|
||
|
|
} } }
|
||
|
|
}
|
||
|
|
if (path.includes('/fleet/quotes')) {
|
||
|
|
return { http: 200, body: { code: '200', data: { records: [{ quoteNo: 'Q-1', status: 'ACTIVE', recordVersion: '0' }], page: 1, size: 20, total: 1, pages: 1 } } }
|
||
|
|
}
|
||
|
|
if (path.includes('/fleet/capacity-offers')) {
|
||
|
|
return { http: 200, body: { code: '200', data: { records: [{ status: 'OPEN' }], page: 1, size: 20, total: 1, pages: 1 } } }
|
||
|
|
}
|
||
|
|
if (path.endsWith('/fleet/trips/FLEET-MISSING-TRIP')) {
|
||
|
|
return { http: 200, body: { code: 'A0404', message: '车次不存在' } }
|
||
|
|
}
|
||
|
|
throw new Error(`unexpected ${method} ${path}`)
|
||
|
|
})
|
||
|
|
await runFleet(ctx)
|
||
|
|
assert.deepEqual(ctx.calls.map((row) => row.method), ['GET', 'GET', 'GET', 'GET', 'GET', 'GET'])
|
||
|
|
assert.ok(ctx.calls[0].path.includes('/fleet/trips?page=1&size=20'))
|
||
|
|
assert.ok(ctx.calls[2].path.includes('/fleet/operations?page=1&size=20&from='))
|
||
|
|
assert.ok(ctx.calls[4].path.includes('/fleet/capacity-offers'))
|
||
|
|
assert.ok(ctx.gaps.some((gap) => gap.name === '车次写命令' && gap.metadata.blocking === false))
|
||
|
|
})
|
||
|
|
|
||
|
|
test('自营分析模块注册为默认模块,空参不可用时记录缺口而不是虚构看板', async () => {
|
||
|
|
assert.match(entry, /const CONFIGURED_MODULES = \[[^\n]*'self-operated-analytics'/)
|
||
|
|
const unavailable = context(() => ({ http: 200, body: { code: 'A0400', message: '缺少采集账号' } }))
|
||
|
|
await runAnalytics(unavailable)
|
||
|
|
assert.equal(unavailable.calls.length, 2)
|
||
|
|
assert.ok(unavailable.gaps.some((gap) => gap.name === '经营看板' && gap.metadata.blocking === false))
|
||
|
|
assert.ok(unavailable.gaps.some((gap) => gap.name === '货主目录' && gap.metadata.blocking === false))
|
||
|
|
|
||
|
|
const available = context((_method, path) => path.endsWith('/dashboard')
|
||
|
|
? { http: 200, body: { code: '200', data: { metrics: [], trend: [] } } }
|
||
|
|
: { http: 200, body: { code: '200', data: { records: [], page: 1, size: 20, total: 0, pages: 0 } } })
|
||
|
|
await runAnalytics(available)
|
||
|
|
assert.equal(available.gaps.some((gap) => gap.name === '经营看板'), false)
|
||
|
|
assert.ok(available.gaps.some((gap) => gap.category === 'external-third-party' && gap.metadata.blocking === false))
|
||
|
|
})
|