test(phase4): provision overseas lifecycle fixtures

This commit is contained in:
2026-08-24 15:07:17 +08:00
parent cd97229791
commit e11a528dfe
12 changed files with 248 additions and 48 deletions
@@ -10,7 +10,7 @@ const source = fs.readFileSync(path.join(here, 'phase4-overseas-business.mjs'),
function response(data, code = '200') { return { http: 200, body: { code, message: code === '200' ? 'success' : 'conflict', data }, requestId: 'request-test', traceId: 'trace-test' } }
function page(records = []) { return { records, page: 1, size: 20, total: records.length, pages: records.length ? 1 : 0 } }
function fixture({ ownedPurchase = false, historicalPurchase = false } = {}) {
function fixture() {
const calls = []; const steps = []; const gaps = []
const state = {
truck: null,
@@ -19,16 +19,30 @@ function fixture({ ownedPurchase = false, historicalPurchase = false } = {}) {
}
const marker = 'HTTP-20260821-001'
const context = {
marker, calls, steps, gaps, context: {},
createdResources: ownedPurchase ? { overseasProcurement: { marker, orderNo: 'OP1', customerId: '10001' } } : {},
async request(method, requestPath, body) { calls.push({ channel: 'domestic', method, path: requestPath, body }); return response(page([{ id: '10001', tenantName: '客户甲', status: 'ACTIVE' }])) },
async requestOverseas(method, requestPath, body) {
marker, calls, steps, gaps,
context: { merchantQualificationFixture: { tenantId: '10001', username: 'merchant', password: 'secret' } },
createdResources: { merchantQualificationFixture: { tenantId: '10001', username: 'merchant', marker } },
async request(method, requestPath, body) {
calls.push({ channel: 'domestic', method, path: requestPath, body })
if (requestPath.includes('/iam/tenants?')) return response(page([{ id: '10001', tenantName: '客户甲', status: 'ACTIVE' }]))
if (requestPath === '/api/auth/login') return response({ overseasAccessToken: 'merchant-overseas-token' })
if (method === 'POST' && requestPath === '/api/platform/v1/warehouse-admin') return response('20001')
if (method === 'PATCH' && requestPath.includes('/warehouse-admin/20001/status?')) return response(null)
throw new Error(`unexpected domestic ${method} ${requestPath}`)
},
async requestOverseas(method, requestPath, body, headers, token) {
calls.push({ channel: 'overseas', method, path: requestPath, body })
if (requestPath === '/api/merchant/v1/last-leg/overseas-procurements' && method === 'POST') {
assert.equal(token, 'merchant-overseas-token')
state.purchase = { ...state.purchase, customerName: body.customerName, remark: body.remark }
return response({ summary: state.purchase })
}
if (requestPath.includes('/customer-options')) return response([{ customerId: '10001', customerName: '客户甲' }])
if (requestPath.includes('/warehouse-options')) return response([{ warehouseId: '20001', warehouseCode: 'LAX', warehouseName: '洛杉矶仓', countryCode: 'US' }])
if (requestPath.includes('/overseas-procurements?') && method === 'GET') return response(page(ownedPurchase || historicalPurchase ? [state.purchase] : []))
if (requestPath.includes('/overseas-procurements?') && method === 'GET') return response(page([state.purchase]))
if (requestPath.includes('/overseas-procurements/OP1?') && method === 'GET') return response({ summary: state.purchase, remark: marker })
if (requestPath.includes('/overseas-procurements/OP1?') && method === 'PATCH') { state.purchase = { ...state.purchase, ...body, version: state.purchase.version + 1 }; return response({ summary: state.purchase }) }
if (requestPath.includes('/overseas-procurements/OP1/review?') && method === 'POST') { state.purchase = { ...state.purchase, status: 'REJECTED', version: state.purchase.version + 1 }; return response({ summary: state.purchase }) }
if (requestPath.includes('/us-truck-orders?') && method === 'POST') { state.truck = { id: '40001', orderNo: 'TK1', customerId: '10001', status: 'PENDING_REVIEW', version: 0, ...body }; return response({ summary: state.truck }) }
if (requestPath.includes('/us-truck-orders/TK1/status?') && method === 'PATCH') { if (Number(body.version) !== state.truck.version) return response(null, 'A0409'); state.truck = { ...state.truck, status: body.status, version: state.truck.version + 1 }; return response({ summary: state.truck }) }
if (requestPath.includes('/us-truck-orders/TK1?') && method === 'GET') return response({ summary: state.truck })
@@ -50,7 +64,7 @@ function fixture({ ownedPurchase = false, historicalPurchase = false } = {}) {
test('module is context injected and excludes forbidden business surfaces', () => {
assert.match(source, /export async function run\(context\)/)
assert.doesNotMatch(source, /https?:\/\/|process\.env|fetch\(/)
for (const forbidden of ['/merchant/', '/review', '/outbounds', '/receive-pallets', '/outbound', '/delivery', '/dispatch', '/export']) assert.equal(source.includes(forbidden), false, forbidden)
for (const forbidden of ['/outbounds', '/receive-pallets', '/outbound', '/delivery', '/dispatch', '/export']) assert.equal(source.includes(forbidden), false, forbidden)
assert.doesNotMatch(source, /requestOverseas\('DELETE'/)
})
@@ -58,14 +72,13 @@ test('marker truck and looseboard cover create detail edit stale conflict and fi
const context = fixture()
await run(context)
assert.equal(context.steps.every((step) => step.passed), true, context.steps.filter((step) => !step.passed).map((step) => `${step.name}: ${step.evidence}`).join('\n'))
assert.deepEqual(context.calls.filter((call) => call.method === 'POST').map((call) => call.path), [
'/api/platform/v1/last-leg/us-truck-orders?customerId=10001',
'/api/platform/v1/last-leg/local-looseboard-orders?customerId=10001',
])
assert.ok(context.calls.some((call) => call.method === 'POST' && call.path === '/api/merchant/v1/last-leg/overseas-procurements'))
assert.ok(context.calls.some((call) => call.method === 'POST' && call.path === '/api/platform/v1/last-leg/us-truck-orders?customerId=10001'))
assert.ok(context.calls.some((call) => call.method === 'POST' && call.path === '/api/platform/v1/last-leg/local-looseboard-orders?customerId=10001'))
assert.equal(context.calls.filter((call) => call.method === 'PATCH' && call.path.includes('/us-truck-orders/')).length, 3)
assert.equal(context.calls.filter((call) => call.method === 'PUT' && call.path.includes('/local-looseboard-orders/')).length, 2)
assert.equal(context.calls.filter((call) => call.method === 'PATCH' && call.path.includes('/local-looseboard-orders/')).length, 1)
const statusCalls = context.calls.filter((call) => call.path.includes('/status?'))
const statusCalls = context.calls.filter((call) => call.channel === 'overseas' && call.path.includes('/status?'))
assert.deepEqual(statusCalls.map((call) => [call.method, call.path, call.body.status]), [
['PATCH', '/api/platform/v1/last-leg/us-truck-orders/TK1/status?customerId=10001', 'CANCELLED'],
['PATCH', '/api/platform/v1/last-leg/local-looseboard-orders/LB1/status?customerId=10001', 'CANCELLED'],
@@ -81,24 +94,20 @@ test('marker truck and looseboard cover create detail edit stale conflict and fi
assert.ok(context.gaps.every((gap) => !/requestId=missing|traceId=missing/.test(gap[3])))
})
test('historical procurement data is never patched without marker ownership', async () => {
const context = fixture({ historicalPurchase: true })
await run(context)
assert.equal(context.calls.some((call) => call.method === 'PATCH' && call.path.includes('/overseas-procurements/')), false)
assert.equal(context.gaps.some((gap) => gap[4] === 'missing-marker-owned-procurement'), true)
})
test('marker-owned procurement may be patched but never created, reviewed or shipped', async () => {
const context = fixture({ ownedPurchase: true })
test('marker procurement is created through merchant then edited and rejected by platform', async () => {
const context = fixture()
await run(context)
const purchaseWrites = context.calls.filter((call) => call.path.includes('/overseas-procurements') && call.method !== 'GET')
assert.deepEqual(purchaseWrites.map((call) => call.method), ['PATCH'])
assert.match(purchaseWrites[0].body.productSpecification, new RegExp(context.marker))
assert.deepEqual(purchaseWrites.map((call) => call.method), ['POST', 'PATCH', 'POST'])
assert.match(purchaseWrites[1].body.productSpecification, new RegExp(context.marker))
assert.equal(context.createdResources.phase4OverseasBusiness.purchase.marker, context.marker)
assert.equal(context.createdResources.phase4OverseasBusiness.purchase.finalStatus, 'REJECTED')
assert.equal(context.createdResources.phase4OverseasBusiness.purchase.retained, false)
})
test('contract locks real candidates, idempotency, version conflicts and trace evidence', () => {
for (const token of ['/api/platform/v1/iam/tenants?page=1&size=200&status=ACTIVE', '/overseas-procurements/customer-options?limit=200',
'/overseas-procurements/warehouse-options', 'idempotencyKey', 'phase4-truck', 'phase4-looseboard',
'/overseas-procurements/warehouse-options', '/api/merchant/v1/last-leg/overseas-procurements',
"decision: 'REJECT'", 'idempotencyKey', 'phase4-truck', 'phase4-looseboard',
'旧 version', '/status', "status: 'CANCELLED'", 'finally', 'requestId=', 'traceId=', 'createdResources.phase4OverseasBusiness']) assert.ok(source.includes(token), token)
})