import assert from 'node:assert/strict' import fs from 'node:fs' import path from 'node:path' import test from 'node:test' import { fileURLToPath } from 'node:url' import { EXCEPTION_CODES, QUERY_STATUSES, STANDARD_NODES, run } from './phase4-carrier-nodes.mjs' const here = path.dirname(fileURLToPath(import.meta.url)) const source = fs.readFileSync(path.join(here, 'phase4-carrier-nodes.mjs'), 'utf8') function response(data) { return { http: 200, body: { code: '200', data } } } function contextFor(routes) { const calls = [] return { calls, marker: 'T-PHASE4', token: 'token', request: async (method, requestPath, body, _token, headers = {}) => { calls.push(method === 'POST' ? { method, path: requestPath, body, headers } : { method, path: requestPath }) assert.ok(['GET', 'POST'].includes(method)) assert.ok(Object.hasOwn(routes, requestPath), `unexpected path: ${requestPath}`) return response(routes[requestPath]) }, apiSuccess: (_module, _name, result) => { assert.equal(result.http, 200) assert.equal(result.body.code, '200') return result.body.data }, assert: (_module, name, _result, condition, evidence) => { assert.ok(condition, `${name}: ${evidence}`) }, } } const task = { id: '9007199254740993', queryNo: 'CSQ9007199254740993', customerId: '10001', oceanBookingId: '20001', containerBookingId: '30001', carrierCode: 'COSCO', trackingType: 'CONTAINER_NO', trackingNo: 'CSNU1234567', status: 'STOPPED', latestActualNode: 'PICKED_UP', latestActualAt: '2026-08-20T12:00:00', version: 5, lastSuccessAt: '2026-08-20T12:00:00', sourceUpdatedAt: '2026-08-20T12:00:00', expiresAt: null, stoppedAt: null, stopReason: null, } const node = { id: '40001', providerCode: 'COSCO', providerEventId: 'event-40001', providerNodeCode: 'OUTGATE', standardNode: 'PICKED_UP', nodeSequence: 90, observedAt: '2026-08-20T12:00:00', estimatedAt: null, actualAt: '2026-08-20T11:55:00', locationCode: 'USLAX', locationName: 'Los Angeles', sourceReference: null, rawPayloadHash: 'a'.repeat(64), createdAt: '2026-08-20T12:00:01', } const manualNode = { ...node, id: '40002', providerCode: 'MANUAL', providerEventId: 'manual-40002', providerNodeCode: 'PICKED_UP', estimatedAt: '2026-08-20T12:00:00', actualAt: null, rawPayloadHash: 'b'.repeat(64) } const exception = { id: '45001', providerCode: 'MATSON_PUBLIC_TRACKING', providerEventId: 'hold-1', providerStatus: 'CONTAINER ON HOLD - Customs Clearance', exceptionCode: 'CUSTOMS_HOLD', active: true, observedAt: '2026-08-20T12:00:00', locationCode: 'USLAX', locationName: 'Long Beach', sourceReference: 'MATSON:9974749', rawPayloadHash: 'c'.repeat(64), createdAt: '2026-08-20T12:00:01' } const history = { id: '50001', action: 'STOP', fromVersion: 4, toVersion: 5, reason: null, operatorId: null, operatorName: '系统', createdAt: '2026-08-20T12:01:00', } const carriers = [{ carrierCode: 'COSCO', carrierName: 'COSCO', officialUrl: 'https://example.invalid', accessMode: 'INTERNAL_PROVIDER', automated: true, coverageGap: false, }] test('module only permits the documented manual-node write and forbids internal execution endpoints', () => { assert.match(source, /context\.request\('GET'/) assert.doesNotMatch(source, /\bfetch\s*\(/) assert.match(source, /context\.request\('POST',[\s\S]*\/manual-nodes/) assert.match(source, /'Idempotency-Key'/) for (const method of ['PUT', 'PATCH', 'DELETE']) { assert.doesNotMatch(source, new RegExp(`context\\.request\\(['"]${method}['"]`)) } for (const endpoint of ['/scheduler', '/attempts', '/manual-results', '/internal', '/pda']) { assert.doesNotMatch(source, new RegExp(endpoint.replace('/', '\\/'), 'i')) } }) test('canonical task node and exception sets are locked to the phase-4 contract', () => { assert.deepEqual(QUERY_STATUSES, ['READY', 'MANUAL_REQUIRED', 'STOPPED']) assert.deepEqual(STANDARD_NODES, [ 'DEPARTED_WAREHOUSE', 'AT_ORIGIN_PORT', 'LOADED_ON_VESSEL', 'DEPARTED_ORIGIN_PORT', 'IN_TRANSIT', 'ARRIVED_DESTINATION_PORT', 'DISCHARGED_FROM_VESSEL', 'PICKUP_READY', 'PICKED_UP', 'EMPTY_RETURNED', ]) assert.deepEqual(EXCEPTION_CODES, ['CUSTOMS_HOLD', 'TERMINAL_HOLD', 'DOCUMENT_HOLD', 'UNKNOWN_HOLD']) }) test('empty page validates read-only pagination without detail calls', async () => { const context = contextFor({ '/api/platform/v1/carrier-schedule-queries/carriers': carriers, '/api/platform/v1/carrier-schedule-queries?page=1&size=20': { records: [], page: 1, size: 20, total: 0, pages: 0 }, }) await run(context) assert.deepEqual(context.calls, [ { method: 'GET', path: '/api/platform/v1/carrier-schedule-queries/carriers' }, { method: 'GET', path: '/api/platform/v1/carrier-schedule-queries?page=1&size=20' }, ]) }) test('existing task reads page, detail and history with nullable node times', async () => { const base = '/api/platform/v1/carrier-schedule-queries' const context = contextFor({ [`${base}/carriers`]: carriers, [`${base}?page=1&size=20`]: { records: [{ query: task, expired: false }], page: 1, size: 20, total: 1, pages: 1 }, [`${base}/${task.id}`]: { query: { query: task, expired: false }, nodes: [node], exceptions: [exception], histories: [history] }, [`${base}/${task.id}/manual-nodes`]: { query: { query: task, expired: false }, nodes: [node, manualNode], exceptions: [exception], histories: [history] }, [`${base}/${task.id}/history`]: [history], }) await run(context) assert.deepEqual(context.calls.map((item) => item.method), ['GET', 'GET', 'GET', 'POST', 'GET']) assert.equal(context.calls.find((item) => item.method === 'POST').headers['Idempotency-Key'], `T-PHASE4-carrier-manual-${task.id}`) assert.equal(context.calls.at(-1).path, `${base}/${task.id}/history`) })