test(fleet): persist independent acceptance requests and current-trip evidence
This commit is contained in:
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict'
|
||||
import { mkdtempSync, mkdirSync, readFileSync, existsSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, dirname, resolve } from 'node:path'
|
||||
import { parseFleetArguments, fleetExecutionPlan, runFleetAcceptance, reviewedFullCheckpoint, verifyFleetTerminalState } from './fleet-test-env-acceptance.mjs'
|
||||
import { parseFleetArguments, fleetExecutionPlan, runFleetAcceptance, reviewedFullCheckpoint, verifyFleetTerminalState, runFleetHandoverStage, fleetCostShares, prepareFleetCostActors } from './fleet-test-env-acceptance.mjs'
|
||||
|
||||
test('fleet entry defaults to a no-side-effect plan and requires explicit TEST and unique run ID', () => {
|
||||
const p = parseFleetArguments([])
|
||||
@@ -13,6 +13,93 @@ test('fleet entry defaults to a no-side-effect plan and requires explicit TEST a
|
||||
assert.throws(() => parseFleetArguments(args))
|
||||
}
|
||||
assert.equal(parseFleetArguments(['--execute', '--environment', 'TEST', '--run-id', 'new']).execute, true)
|
||||
const handover = parseFleetArguments(['--plan', '--environment', 'TEST', '--run-id', 'fleet-handover-20261003', '--output', 'reports/fleet-handover-live-20261003'])
|
||||
assert.equal(fleetExecutionPlan(handover).outputDir, 'reports/fleet-handover-live-20261003')
|
||||
for (const output of ['reports/../outside', 'C:/temp/output', 'reports', 'reports/nested/path']) assert.throws(() => parseFleetArguments(['--output', output]))
|
||||
})
|
||||
|
||||
test('handover stage exports only a verified same-run departure fixture before the optional UI checkpoint', async () => {
|
||||
const order = [], departure = { tripNo: 'TR-NEW', status: 'IN_TRANSIT' }
|
||||
const context = { marker: 'CURRENT', context: { saveFleetHandoverFixture(value) { order.push('save'); assert.equal(value.marker, 'CURRENT') } },
|
||||
createdResources: { fleetAcceptance: { departure } } }
|
||||
const trip = { tripNo: 'TR-NEW', warehouseId: '92', costAcceptance: { status: 'VERIFIED', marker: 'CURRENT', expected: { finalCny: '100.03', usd: '13.07' } } }, details = [{ dispatchNo: 'PC-NEW' }]
|
||||
const verified = { status: 'VERIFIED', marker: 'CURRENT', originalDeparture: { snapshot: { confirmedLoad: { pallets: [] } } },
|
||||
browser: { tripNo: 'TR-NEW' }, handoverNo: 'HO-NEW' }
|
||||
const result = await runFleetHandoverStage(context, trip, 'secret-picker-token', details, {
|
||||
verify: async (ctx, module, proof, token, dispatches) => { order.push('verify'); assert.equal(ctx, context); assert.equal(proof, trip); assert.equal(token, 'secret-picker-token'); assert.equal(dispatches, details); return verified },
|
||||
onReady: async fixture => { order.push('ui'); assert.equal(fixture.status, 'DEPART_AND_HANDOVER_VERIFIED'); assert.equal(fixture.departure, departure) },
|
||||
})
|
||||
assert.deepEqual(order, ['verify', 'save', 'ui']); assert.ok(!JSON.stringify(result).includes('secret-picker-token'))
|
||||
assert.deepEqual(result.costs, trip.costAcceptance)
|
||||
for (const invalid of [{ ...verified, status: 'FAILED' }, { ...verified, marker: 'OLD' }, { ...verified, originalDeparture: null }]) {
|
||||
await assert.rejects(runFleetHandoverStage(context, trip, 'token', details, { verify: async () => invalid,
|
||||
onReady: async () => assert.fail('invalid evidence must not reach the browser checkpoint') }), /current verified/)
|
||||
}
|
||||
})
|
||||
|
||||
test('cost shares use controlled physical pallets and the original external booking, never statistical totals', () => {
|
||||
const trip = { externalReceipt: { status: 'VERIFIED', bookingNo: 'BK-ONE', warehousePalletId: '3' }, externalDispatch: { dispatchNo: 'EXT' } }
|
||||
const details = [{ dispatchNo: 'OWN', items: [{ warehousePalletId: '1' }, { warehousePalletId: '1' }, { warehousePalletId: '2' }] },
|
||||
{ dispatchNo: 'EXT', items: [{ warehousePalletId: '3' }] }]
|
||||
assert.deepEqual(fleetCostShares(trip, details), [
|
||||
{ businessNo: 'OWN', sourceType: 'OWN', dispatchNo: 'OWN', warehousePalletIds: ['1', '2'], palletSpaces: 2 },
|
||||
{ businessNo: 'BK-ONE', sourceType: 'EXTERNAL', dispatchNo: 'EXT', warehousePalletIds: ['3'], palletSpaces: 1 },
|
||||
])
|
||||
assert.throws(() => fleetCostShares(trip, [{ ...details[0], items: [{ warehousePalletId: '3' }] }, details[1]]), /distinct/)
|
||||
assert.throws(() => fleetCostShares(trip, [details[0], { ...details[1], items: [{ warehousePalletId: '4' }] }]), /receipt/)
|
||||
})
|
||||
|
||||
test('missing isolated cost actor preparation rejects before readers or fixture creation', async () => {
|
||||
let touched = false
|
||||
await assert.rejects(runFleetAcceptance({ execute: true, environment: 'TEST' }, {
|
||||
env: { CHONGCHONG_ENV: 'test' }, prepareCostActors: null, readers: { preflight() { touched = true } },
|
||||
}), /actor preparation/)
|
||||
assert.equal(touched, false)
|
||||
})
|
||||
|
||||
for (const failLogin of [false, true]) test(`cost actors create exact isolated roles and disable only owned accounts; login failure=${failLogin}`, async () => {
|
||||
const calls = [], saved = [], roles = [], users = []
|
||||
const context = { marker: 'RUN', token: 'admin-secret', createdResources: {}, async findAvailablePhone() { return '19900000000' },
|
||||
async checkpoint() { saved.push(structuredClone(context.createdResources)) },
|
||||
async request(method, path, body, token) {
|
||||
const pending = saved.at(-1).fleetCostActors.requests.at(-1)
|
||||
assert.equal(pending.status, 'SENDING'); assert.equal(pending.path, path)
|
||||
calls.push({ method, path, body, token })
|
||||
let data
|
||||
if (path.endsWith('/authorization/permissions')) data = token === 'session-1' ? ['platform:fleet:trip:list'] : []
|
||||
else if (path.endsWith('/permissions') && method === 'GET') data = [{ id: '7', permissionCode: 'platform:fleet:trip:list', status: 'ACTIVE' }]
|
||||
else if (path.endsWith('/roles') && method === 'POST') { data = { ...body, id: String(roles.length + 1), roleType: 'CUSTOM' }; roles.push(data) }
|
||||
else if (path.endsWith('/roles/permissions')) data = body
|
||||
else if (path.endsWith('/users') && method === 'POST') { data = { ...body, membershipId: String(users.length + 10) }; users.push(data) }
|
||||
else if (path === '/api/auth/login') { if (failLogin) throw Error('login unavailable'); data = { accessToken: `session-${users.length}` } }
|
||||
else if (path.endsWith('/status')) data = body
|
||||
else assert.fail(path)
|
||||
return { http: 200, body: { code: '200', data }, requestId: 'request', traceId: 'trace' }
|
||||
} }
|
||||
if (failLogin) await assert.rejects(prepareFleetCostActors(context), /login unavailable/)
|
||||
else {
|
||||
const actors = await prepareFleetCostActors(context)
|
||||
assert.equal(actors.listOnlyToken, 'session-1'); assert.equal(actors.deniedToken, 'session-2')
|
||||
assert.equal(calls.filter(row => row.path.endsWith('/roles/permissions'))[1].body.permissionIds.length, 0)
|
||||
await actors.close()
|
||||
const count = calls.length; await actors.close(); assert.equal(calls.length, count)
|
||||
}
|
||||
assert.equal(context.createdResources.fleetCostActors.status, 'DISABLED')
|
||||
assert.equal(calls.filter(row => /\/users\/\d+\/status$/.test(row.path)).length, users.length)
|
||||
assert.equal(calls.filter(row => /\/roles\/\d+\/status$/.test(row.path)).length, roles.length)
|
||||
const evidence = JSON.stringify(saved)
|
||||
assert.ok(!evidence.includes('session-1')); assert.ok(!evidence.includes('admin-secret'))
|
||||
for (const user of users) assert.ok(!evidence.includes(user.password))
|
||||
assert.ok(!calls.some(row => row.method === 'DELETE'))
|
||||
})
|
||||
|
||||
test('main chain performs handover after actual DEPART and before receipts, and cleanup follows current-resource closure', async () => {
|
||||
const source = readFileSync(new URL('./fleet-test-env-acceptance.mjs', import.meta.url), 'utf8')
|
||||
const savedDeparture = source.indexOf('proof.departure = departed'), handover = source.indexOf("stage('IN_TRANSIT_HANDOVER')")
|
||||
const receipt = source.indexOf("stage('RECEIPTS_AND_RESOURCE_RELEASE')"), closure = source.indexOf('proof.closure = await finishFleetTrip')
|
||||
const cleanup = source.indexOf('await cleanupFleetHandoverResources(context, MODULE, trip)')
|
||||
assert.ok(savedDeparture > 0 && savedDeparture < handover && handover < receipt && receipt < closure && closure < cleanup)
|
||||
assert.match(source, /finally \{ await disablePicker\(context, picker, 71\) \}/)
|
||||
})
|
||||
|
||||
async function executeFixture(failProducer = false) {
|
||||
@@ -35,7 +122,19 @@ async function executeFixture(failProducer = false) {
|
||||
})
|
||||
try {
|
||||
const options = { root, env, createContext, readers: { async preflight() { calls.push('preflight') }, async readReceiving() { throw Error('not used') } },
|
||||
scenario: async context => { calls.push('fleet'); context.createdResources.fleetAcceptance = { status: 'HTTP_CHAIN_VERIFIED_WITH_ACK_GAP' } } }
|
||||
scenario: async context => {
|
||||
calls.push('fleet'); context.createdResources.fleetAcceptance = { status: 'HTTP_CHAIN_VERIFIED_WITH_ACK_GAP' }
|
||||
const command = { requests: [{ method: 'POST', status: 'SENDING', body: { idempotencyKey: 'original-key' }, accessToken: secrets }] }
|
||||
await context.context.checkpointFleetHandover(command)
|
||||
const firstPath = join(root, 'reports/fleet-fixture/evidence/handover-000001.json')
|
||||
const before = readFileSync(firstPath, 'utf8'), saved = JSON.parse(before)
|
||||
assert.equal(saved.proof.requests[0].body.idempotencyKey, 'original-key')
|
||||
assert.equal(saved.proof.requests[0].status, 'SENDING'); assert.ok(!before.includes(secrets))
|
||||
command.requests[0].status = 'RESPONDED'
|
||||
await context.context.checkpointFleetHandover(command)
|
||||
assert.equal(readFileSync(firstPath, 'utf8'), before)
|
||||
assert.equal(JSON.parse(readFileSync(join(root, 'reports/fleet-fixture/evidence/handover-000002.json'), 'utf8')).proof.requests[0].status, 'RESPONDED')
|
||||
} }
|
||||
const result = await runFleetAcceptance(config, options)
|
||||
const report = JSON.parse(readFileSync(result.report, 'utf8'))
|
||||
assert.ok(!readFileSync(result.report, 'utf8').includes(secrets))
|
||||
|
||||
Reference in New Issue
Block a user