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