This commit is contained in:
2026-08-24 19:58:35 +08:00
parent 9e0f41e78b
commit c93fdc44d5
23 changed files with 965 additions and 313 deletions
@@ -9,41 +9,79 @@ import { run } from './phase4-overseas-pda.mjs'
const here = path.dirname(fileURLToPath(import.meta.url))
const source = fs.readFileSync(path.join(here, 'phase4-overseas-pda.mjs'), 'utf8')
test('PDA module uses only injected development acceptance requests', () => {
test('PDA module is injected, token-scoped, traceable, and contains no direct network or secret source', () => {
assert.match(source, /export async function run\(context\)/)
assert.match(source, /context\.requestOverseas\(/)
assert.match(source, /context\.requestOverseasMultipart\(/)
assert.doesNotMatch(source, /https?:\/\//)
assert.doesNotMatch(source, /process\.env|fetch\(/)
})
test('PDA module covers the complete receiving lifecycle and excludes outbound domains', () => {
for (const token of ['/destination', '/inbound-plans/convert', '/pallets/', '/push', '/cartons/scan', '/allocations/',
'/count', '/pick', '/unload-photos/files', '/unload-photos', '/complete', '/inventory/balances']) {
assert.ok(source.includes(token), token)
}
for (const forbidden of ['/outbound', '/dispatch', '/delivery', '/internal/']) assert.equal(source.includes(forbidden), false, forbidden)
assert.match(source, /requestOverseas\(/)
assert.match(source, /requestOverseasMultipart\(/)
assert.match(source, /requestId=/)
assert.match(source, /traceId=/)
assert.match(source, /PENDING_ASSIGNMENT/)
assert.match(source, /nonBlank\(item\?\.containerNo\)/)
assert.match(source, /expectedCartons/)
assert.doesNotMatch(source, /https?:\/\//)
assert.doesNotMatch(source, /fetch\(|process\.env/)
assert.doesNotMatch(source, /planNo|allocationId/)
})
test('missing source order is reported as a fixture prerequisite without business writes', async () => {
test('PDA module covers the container-number receiving contract and excludes outbound domains', () => {
for (const token of ['/candidates', '/prepare', '/current/unload-photos/files', '/current/sign',
'/current/skus/resolve', '/current/cartons/resolve', '/current/pallets/resolve',
'/current/receipts/bulk', '/current/receipts/carton', '/current/complete']) assert.ok(source.includes(token), token)
for (const role of ['LICENSE_PLATE', 'CONTAINER_SEAL', 'DOOR_OPEN']) assert.ok(source.includes(role), role)
for (const forbidden of ['/outbound', '/dispatch', '/delivery', '/internal/']) assert.equal(source.includes(forbidden), false, forbidden)
assert.match(source, /同键同载荷重放/)
assert.match(source, /同键异载荷冲突/)
assert.match(source, /跨柜切换锁返回409/)
assert.match(source, /超收被拒绝/)
assert.match(source, /重复箱新键被拒绝/)
assert.match(source, /模式硬拒绝/)
assert.match(source, /current返回404正常空态/)
assert.match(source, /安全扫描被拒绝/)
assert.match(source, /retained as acceptance evidence/)
})
test('missing customer fixture is a deployment blocker and performs no HTTP request', async () => {
const calls = []; const gaps = []; const steps = []
const context = {
marker: 'PDA-CONTRACT', createdResources: {},
async request(method, requestPath) { calls.push({ channel: 'domestic', method, requestPath }); return { http: 200, body: { code: '200', data: { records: [], page: 1, size: 200, total: 0, pages: 0 } } } },
async requestOverseas(method, requestPath) { calls.push({ channel: 'overseas', method, requestPath }); throw new Error('overseas should not be called without customers') },
marker: 'PDA-CONTRACT', context: {}, createdResources: {},
async request(...args) { calls.push(args); throw new Error('must not request without credentials') },
async requestOverseas(...args) { calls.push(args); throw new Error('must not request without credentials') },
async requestOverseasMultipart(...args) { calls.push(args); throw new Error('must not request without credentials') },
addStep(...args) { steps.push(args) },
assert(...args) { steps.push(args) },
assert(module, name, response, passed, evidence, options) { steps.push({ module, name, passed, evidence, options }) },
addCoverageGap(...args) { gaps.push(args) },
}
await run(context)
assert.equal(calls.length, 1)
assert.equal(calls[0].method, 'GET')
assert.equal(calls.length, 0)
assert.equal(gaps.length, 1)
assert.match(gaps[0][3], /三期装柜完成事件链/)
assert.deepEqual(context.createdResources.phase4OverseasPda.finalStatus, null)
assert.equal(gaps[0][5].deploymentBlocker, true)
assert.equal(steps.length, 1)
assert.equal(steps[0].passed, false)
assert.equal(steps[0].options.deploymentBlocker, true)
})
test('empty candidate response cannot become a zero-business-request pass', async () => {
const calls = []; const gaps = []; const steps = []
const response = (data, requestPath, method = 'GET') => ({ method, path: requestPath, http: 200,
body: { code: '200', data }, requestId: 'req-contract', traceId: 'trace-contract' })
const context = {
marker: 'PDA-CONTRACT', overseasToken: '', createdResources: {},
context: { merchantQualificationFixture: { tenantId: '9', username: 'merchant', password: 'generated' } },
async request(method, requestPath) {
calls.push({ channel: 'domestic', method, requestPath })
return response({ overseasAccessToken: 'pda-token' }, requestPath, method)
},
async requestOverseas(method, requestPath) {
calls.push({ channel: 'overseas', method, requestPath })
if (requestPath.endsWith('/current')) return { method, path: requestPath, http: 200,
body: { code: 'A0404', message: 'not found' }, requestId: 'req-current', traceId: 'trace-current' }
return response([], requestPath, method)
},
addStep(module, name, result, passed, evidence, options) { steps.push({ module, name, result, passed, evidence, options }) },
assert(module, name, result, passed, evidence, options) { steps.push({ module, name, result, passed, evidence, options }) },
addCoverageGap(...args) { gaps.push(args) },
}
await run(context)
assert.ok(calls.some((call) => call.requestPath.endsWith('/candidates')))
assert.ok(steps.some((step) => step.name.includes('正常空态') && step.passed))
assert.ok(steps.some((step) => step.name.includes('PDA柜号候选覆盖前置') && !step.passed && step.options.deploymentBlocker))
assert.equal(gaps.length, 1)
assert.equal(context.createdResources.phase4OverseasPda.finalStatus, null)
})