Files
baiyue-firstleg/scripts/admin-test-env-modules/appointment-work-contract.test.mjs
2026-10-21 15:53:33 +08:00

112 lines
5.7 KiB
JavaScript

import assert from 'node:assert/strict'
import test from 'node:test'
import { readFileSync } from 'node:fs'
import { run, readAfterSynchronization } from './appointment-work.mjs'
test('同步就绪只重试明确协调状态,超限及其他错误原样保留', async () => {
const coordinating = {http:200,body:{code:'A0409',message:'预约批次或运输关联已变化,服务端正在协调完整工作清单'}}
const success = {http:200,body:{code:'200',data:{}}}
let calls = 0
assert.equal(await readAfterSynchronization({request:async()=>++calls === 1 ? coordinating : success}, '/summary', {attempts:3,delay:async()=>{}}),success)
assert.equal(calls,2)
const scopeChanged = {http:200,body:{code:'A0409',message:'已开船工作范围变化,请先完成服务端同步'}}
calls=0
assert.equal(await readAfterSynchronization({request:async()=>++calls===1?scopeChanged:success}, '/summary', {attempts:3,delay:async()=>{}}),success)
assert.equal(calls,2)
calls=0
assert.equal(await readAfterSynchronization({request:async()=>{calls++;return coordinating}}, '/summary', {attempts:3,delay:async()=>{}}),coordinating)
assert.equal(calls,3)
const other={http:500,body:{code:'S0500'}}
assert.equal(await readAfterSynchronization({request:async()=>other}, '/summary', {delay:async()=>assert.fail('must not retry')}),other)
const conflict={http:200,body:{code:'A0409',message:'预约版本冲突'}}
assert.equal(await readAfterSynchronization({request:async()=>conflict}, '/summary', {delay:async()=>assert.fail('must not retry unrelated conflict')}),conflict)
})
const entry = readFileSync(new URL('../admin-test-env-acceptance.mjs', import.meta.url), 'utf8')
const moduleSource = readFileSync(new URL('./appointment-work.mjs', import.meta.url), 'utf8')
function fakeContext(overrides = {}) {
const calls = []
const gaps = []
const steps = []
const context = {
marker: 'ADMIN-API-OFFLINE',
calls,
gaps,
steps,
async request(method, path, body) {
calls.push({ method, path, body })
const url = new URL(path, 'https://offline.invalid')
if (url.pathname.endsWith('/calendar')) {
const from = url.searchParams.get('from')
const to = url.searchParams.get('to')
return { http: 200, body: { code: '200', data: { from, to, total: 2, days: [
{ date: from, total: 1, typeCounts: { DELIVERY: 1 } },
{ date: to, total: 1, typeCounts: { PICKUP: 1 } },
] } } }
}
if (url.pathname.endsWith('/summary')) {
return { http: 200, body: { code: '200', data: { all: 2, followToday: 1, overdue: 0, undated: 1, review: 0 } } }
}
if (url.pathname.endsWith('/items')) {
return { http: 200, body: { code: '200', data: overrides.emptyPage
? { records: [], page: 1, size: 20, total: 0, pages: 0 }
: { records: [{ workItemKey: 'WORK-1', type: 'DELIVERY', flow: 'TRANSFER' }], page: 1, size: 20, total: 1, pages: 1 } } }
}
if (url.pathname.includes('/items/WORK-1')) {
return { http: 200, body: { code: '200', data: {
item: { workItemKey: 'WORK-1', type: 'DELIVERY', flow: 'TRANSFER', containers: [{ containerBookingId: 'C-1' }], allowedActions: [] },
references: [], history: [],
} } }
}
if (url.pathname.endsWith('/commands')) {
return { http: 200, body: { code: 'A0404', message: '工作事项不存在' } }
}
throw new Error(`unexpected offline request ${method} ${path}`)
},
apiSuccess(module, name, response) {
assert.equal(response.http, 200)
assert.equal(String(response.body?.code), '200')
steps.push({ module, name, passed: true })
return response.body.data
},
assert(module, name, response, condition, evidence) {
assert.equal(Boolean(condition), true, `${name}: ${evidence}`)
steps.push({ module, name, passed: true })
return true
},
addCoverageGap(module, scenario, name, reason, category, metadata) {
gaps.push({ module, scenario, name, reason, category, metadata })
},
}
return context
}
test('预约工作模块在默认全量中注册且只读查询官方工作事项接口', () => {
assert.match(entry, /const CONFIGURED_MODULES = \[[^\n]*'appointment-work'/)
assert.match(moduleSource, /\/api\/platform\/v1\/last-leg\/appointment-work/)
for (const path of ['/calendar', '/summary', '/items', '/commands']) assert.match(moduleSource, new RegExp(path.replace('/', '\\/')))
assert.match(moduleSource, /command: 'SAVE_FOLLOWUP'/)
assert.doesNotMatch(moduleSource, /this\.addStep|writeFileSync|execSync/)
})
test('预约工作只读链按日历、汇总、分页、详情顺序调用,唯一写入是不存在事项的拒绝命令', async () => {
const context = fakeContext()
await run(context)
assert.deepEqual(context.calls.map((row) => row.method), ['GET', 'GET', 'GET', 'GET', 'POST'])
assert.ok(context.calls[0].path.includes('/calendar?'))
assert.ok(context.calls[1].path.endsWith('/summary'))
assert.ok(context.calls[2].path.includes('/items?view=ALL&page=1&size=20'))
assert.ok(context.calls[3].path.includes('/items/WORK-1'))
assert.ok(context.calls[4].path.includes('/items/FULL-ACCEPTANCE-MISSING-WORK-ITEM/commands'))
assert.equal(context.calls[4].body.command, 'SAVE_FOLLOWUP')
assert.ok(context.gaps.some((gap) => gap.name === '预约写命令正向链路' && gap.metadata.blocking === false))
})
test('无本轮工作事项时记录非阻断覆盖缺口而不是虚构详情或命令结果', async () => {
const context = fakeContext({ emptyPage: true })
await run(context)
assert.equal(context.calls.length, 3)
assert.ok(context.gaps.some((gap) => gap.name === '预约工作项详情与命令' && gap.metadata.blocking === false))
})