import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { run } from './phase4-overseas-master-data.mjs' const source = readFileSync(new URL('./phase4-overseas-master-data.mjs', import.meta.url), 'utf8') const controllerSource = [ '../../lastleg-service/src/main/java/com/baiyue/lastleg/masterdata/quote/controller/PlatformOverseasQuoteController.java', '../../lastleg-service/src/main/java/com/baiyue/lastleg/masterdata/exchange/controller/PlatformMonthlyExchangeRateController.java', '../../lastleg-service/src/main/java/com/baiyue/lastleg/masterdata/vehicle/adapter/in/web/PlatformVehicleController.java', ].map((path) => readFileSync(new URL(path, import.meta.url), 'utf8')).join('\n') const DOMESTIC_BASE = '/api/platform/v1/warehouse-admin' const OVERSEAS_BASE = '/api/platform/v1/last-leg/master-data' const ok = (data) => ({ http: 200, body: { code: '200', message: 'success', data }, requestId: 'req-p4', traceId: 'trace-p4' }) const conflict = () => ({ http: 200, body: { code: 'A0409', message: 'record version conflict', data: null }, requestId: 'req-conflict' }) const forbidden = () => ({ http: 403, body: { code: 'A0403', message: 'Access denied', data: null }, requestId: 'req-forbidden' }) function detailQuote(state) { return { summary: { ...state.summary }, warehouseFees: state.warehouseFees.map((item) => ({ ...item })), transitPrices: state.transitPrices.map((item) => ({ ...item })), createdBy: 'admin', createdAt: '2026-08-21T00:00:00', } } function vehicleWire(state) { return { ...state.vehicle, mileageKm: String(state.vehicle.mileageKm), recordVersion: String(state.vehicle.recordVersion) } } function fakeContext({ softAssertions = false, refreshEmpty = false } = {}) { const calls = [] const coverageGaps = [] const steps = [] const marker = 'ADMIN-API-20260821123456789' const state = { warehouse: null, quote: null, exchange: null, vehicle: null, optionReads: 0, role: null, user: null } const context = { marker, calls, coverageGaps, steps, createdResources: {}, async findAvailablePhone() { return '13800138000' }, apiSuccess(_module, name, response) { assert.equal(response?.http, 200, `${name}: HTTP`) assert.equal(String(response?.body?.code), '200', `${name}: code`) return response.body.data }, assert(_module, name, _response, condition, evidence, options = {}) { const passed = Boolean(condition) steps.push({ name, passed, deploymentBlocker: Boolean(options.deploymentBlocker && !passed), evidence }) if (!passed && !softAssertions) assert.equal(passed, true, `${name}: ${evidence}`) return passed }, expectConflict(_module, name, response) { assert.equal(response.http, 200, `${name}: HTTP`) assert.ok(['A0409', '409'].includes(String(response.body?.code)), `${name}: code=${response.body?.code}`) return true }, addCoverageGap(module, scenario, name, reason, category, metadata = {}) { coverageGaps.push({ module, scenario, name, reason, category, ...metadata }) }, async request(method, path, body) { calls.push({ gateway: 'domestic', method, path, body }) assert.notEqual(method, 'DELETE') if (method === 'POST' && path === DOMESTIC_BASE) { state.warehouse = { ...body, id: '910000000000000001' } return ok(state.warehouse.id) } if (method === 'GET' && path === `${DOMESTIC_BASE}/910000000000000001?tenantId=0`) { return ok({ warehouse: { ...state.warehouse }, addresses: [state.warehouse.address], capabilities: state.warehouse.capabilities }) } if (method === 'PUT' && path === `${DOMESTIC_BASE}/910000000000000001?tenantId=0`) { state.warehouse = { ...body, id: state.warehouse.id } return ok(null) } if (method === 'PATCH' && path === `${DOMESTIC_BASE}/910000000000000001/status?tenantId=0&status=INACTIVE`) { state.warehouse.status = 'INACTIVE' return ok(null) } if (method === 'POST' && path === '/api/platform/v1/iam/roles') { state.role = { id: '940000000000000001', ...body, status: 'ACTIVE' } return ok({ ...state.role }) } if (method === 'POST' && path === '/api/platform/v1/iam/users') { state.user = { membershipId: '950000000000000001', ...body, status: 'ACTIVE' } return ok({ ...state.user }) } if (method === 'POST' && path === '/api/auth/login') { return ok({ accessToken: 'deny-domestic-token', overseasAccessToken: 'deny-overseas-token', overseasExpiresIn: 900 }) } if (method === 'PUT' && path === '/api/platform/v1/iam/users/950000000000000001/status') { state.user.status = body.status return ok(null) } if (method === 'PUT' && path === '/api/platform/v1/iam/roles/940000000000000001/status') { state.role.status = body.status return ok(null) } assert.fail(`unexpected domestic call ${method} ${path}`) }, async requestOverseas(method, path, body, extraHeaders = {}, token = 'admin-overseas-token') { calls.push({ gateway: 'overseas', method, path, body, extraHeaders, tokenKind: token === 'deny-overseas-token' ? 'denied' : 'admin' }) assert.notEqual(method, 'DELETE') if (token === 'deny-overseas-token') return forbidden() if (method === 'GET' && path === `${OVERSEAS_BASE}/overseas-quotes/warehouse-options`) { state.optionReads += 1 if (!state.warehouse) return ok([]) return ok([{ warehouseId: '910000000000000001', warehouseCode: state.warehouse.warehouseCode, warehouseName: state.warehouse.warehouseName, countryCode: 'US' }]) } if (method === 'GET' && path === `${OVERSEAS_BASE}/overseas-quotes/warehouse-options?refresh=true`) { state.optionReads += 1 if (refreshEmpty) return ok([]) return ok([{ warehouseId: '910000000000000001', warehouseCode: state.warehouse.warehouseCode, warehouseName: state.warehouse.warehouseName, countryCode: 'US' }]) } if (method === 'POST' && path === `${OVERSEAS_BASE}/overseas-quotes`) { state.quote = { summary: { id: '920000000000000001', ...body, warehouseCode: state.warehouse.warehouseCode, warehouseName: state.warehouse.warehouseName, countryCode: 'US', status: 'INACTIVE', recordVersion: 0 }, warehouseFees: body.warehouseFees, transitPrices: body.transitPrices || [], } return ok(detailQuote(state.quote)) } if (method === 'GET' && path.startsWith(`${OVERSEAS_BASE}/overseas-quotes?page=1&size=20&keyword=`)) { return ok({ records: state.quote ? [{ ...state.quote.summary }] : [], page: 1, size: 20, total: state.quote ? 1 : 0, pages: state.quote ? 1 : 0 }) } if (path === `${OVERSEAS_BASE}/overseas-quotes/920000000000000001`) { if (method === 'GET') return ok(detailQuote(state.quote)) if (method === 'PUT') { if (Number(body.recordVersion) !== state.quote.summary.recordVersion) return conflict() state.quote.summary = { ...state.quote.summary, name: body.name, providerName: body.providerName, currency: body.currency, effectiveStart: body.effectiveStart, effectiveEnd: body.effectiveEnd, remark: body.remark, recordVersion: state.quote.summary.recordVersion + 1 } state.quote.warehouseFees = body.warehouseFees state.quote.transitPrices = body.transitPrices || [] return ok(detailQuote(state.quote)) } } if (method === 'PATCH' && path === `${OVERSEAS_BASE}/overseas-quotes/920000000000000001/status`) { assert.equal(Number(body.recordVersion), state.quote.summary.recordVersion) state.quote.summary.status = body.status state.quote.summary.recordVersion += 1 return ok(detailQuote(state.quote)) } if (method === 'GET' && path.startsWith(`${OVERSEAS_BASE}/exchange-rates?page=1&size=20&monthFrom=`)) { const matches = state.exchange && path.includes(`monthFrom=${state.exchange.month}`) && (!path.includes('status=') || path.includes(`status=${state.exchange.status}`)) return ok({ records: matches ? [{ ...state.exchange }] : [], page: 1, size: 20, total: matches ? 1 : 0, pages: matches ? 1 : 0 }) } if (method === 'POST' && path === `${OVERSEAS_BASE}/exchange-rates`) { state.exchange = { ...body, baseCurrency: 'USD', quoteCurrency: 'CNY', status: 'ACTIVE', recordVersion: 0 } return ok({ ...state.exchange }) } if (state.exchange && path === `${OVERSEAS_BASE}/exchange-rates/${state.exchange.month}`) { if (method === 'GET') return ok({ ...state.exchange }) if (method === 'PUT') { if (Number(body.recordVersion) !== state.exchange.recordVersion) return conflict() state.exchange = { ...state.exchange, usdToCnyAverage: body.usdToCnyAverage, remark: body.remark, recordVersion: state.exchange.recordVersion + 1 } return ok({ ...state.exchange }) } } if (state.exchange && method === 'PATCH' && path === `${OVERSEAS_BASE}/exchange-rates/${state.exchange.month}/status`) { assert.equal(Number(body.recordVersion), state.exchange.recordVersion) state.exchange = { ...state.exchange, status: body.status, remark: body.remark ?? state.exchange.remark, recordVersion: state.exchange.recordVersion + 1 } return ok({ ...state.exchange }) } if (method === 'POST' && path === `${OVERSEAS_BASE}/vehicles`) { state.vehicle = { id: '930000000000000001', ...body, recordVersion: 0 } return ok(vehicleWire(state)) } if (path === `${OVERSEAS_BASE}/vehicles/930000000000000001`) { if (method === 'GET') return ok(vehicleWire(state)) if (method === 'PUT') { if (Number(body.recordVersion) !== state.vehicle.recordVersion) return conflict() state.vehicle = { ...state.vehicle, ...body, recordVersion: state.vehicle.recordVersion + 1, status: state.vehicle.status } return ok(vehicleWire(state)) } } if (method === 'PATCH' && path === `${OVERSEAS_BASE}/vehicles/930000000000000001/status`) { assert.equal(Number(body.recordVersion), state.vehicle.recordVersion) state.vehicle = { ...state.vehicle, status: body.status, recordVersion: state.vehicle.recordVersion + 1 } return ok(vehicleWire(state)) } if (method === 'GET' && path.startsWith(`${OVERSEAS_BASE}/vehicles?page=1&size=20&keyword=`)) { return ok({ records: state.vehicle ? [vehicleWire(state)] : [], page: 1, size: 20, total: state.vehicle ? 1 : 0, pages: state.vehicle ? 1 : 0 }) } assert.fail(`unexpected overseas call ${method} ${path}`) }, async requestOverseasBinary(method, path) { calls.push({ gateway: 'overseas-binary', method, path }) assert.equal(method, 'GET') assert.match(path, /\/(?:overseas-quotes|exchange-rates|vehicles)\/export\?/) let csv = '' if (path.includes('/overseas-quotes/')) csv = `报价ID,报价名称,合作仓编码,合作仓名称,报价方,币种,生效开始,生效结束,版本号,状态,更新时间\r\n${state.quote.summary.id},${state.quote.summary.name},${state.quote.summary.warehouseCode},${state.quote.summary.warehouseName},${state.quote.summary.providerName},USD,2098-01-01,2098-12-31,V1,ACTIVE,2026-08-21\r\n` else if (path.includes('/exchange-rates/')) csv = `月份,基准币种,报价币种,月平均汇率,状态,更新人,更新时间\r\n${state.exchange.month},USD,CNY,${state.exchange.usdToCnyAverage},${state.exchange.status},admin,2026-08-21\r\n` else csv = `车辆ID,车牌号,车型,油耗(L/100km),当前公里码(km),司机或外派单位,联系电话,区域,车辆归属,状态,更新时间\r\n${state.vehicle.id},${state.vehicle.plateNumber},${state.vehicle.model},18.75,101,Carrier,123,OVERSEAS,OWNED,${state.vehicle.status},2026-08-21\r\n` return { method, path, http: 200, body: null, bytes: new TextEncoder().encode(`\uFEFF${csv}`), contentType: 'text/csv;charset=UTF-8', requestId: 'req-csv' } }, } return { context, state } } test('静态锁定真实双Gateway生命周期、CSV与安全边界', () => { assert.match(source, /context\.request\('POST', DOMESTIC_BASE/) assert.match(source, /warehouseType: 'OVERSEAS_PARTNER'/) assert.match(source, /context\.requestOverseas\('POST'/) assert.match(source, /context\.requestOverseas\('PUT'/) assert.match(source, /context\.requestOverseas\('PATCH'/) assert.match(source, /context\.requestOverseasBinary\('GET'/) assert.match(source, /context\.expectConflict/) assert.match(source, /warehouse-options\?refresh=true/) assert.match(source, /空候选不负缓存30分钟/) assert.match(source, /30分钟正缓存与刷新快照一致/) assert.match(source, /无quote:list权限访问海外报价被拒绝/) assert.match(source, /表头与marker内容/) assert.match(source, /finally\s*\{/) assert.match(source, /context\.createdResources\.phase4OverseasMasterData/) assert.doesNotMatch(source, /context\.addCoverageGap/) assert.match(source, /2090 \+ Math\.floor/) assert.doesNotMatch(source, /\/internal\/|overseas-partners|https?:\/\/|fetch\(|process\.env|request(?:Overseas)?\('DELETE'/) for (const endpoint of ['overseas-quotes/warehouse-options', 'overseas-quotes/export', 'exchange-rates/export', 'vehicles/export']) assert.ok(source.includes(endpoint)) assert.doesNotMatch(controllerSource, /Idempotency-Key|IdempotencyKey/) assert.doesNotMatch(source, /Idempotency-Key/) }) test('marker资源完成创建、详情、编辑、冲突、状态、CSV并全部停用', async () => { const { context, state } = fakeContext() await run(context) const resource = context.createdResources.phase4OverseasMasterData assert.equal(resource.partnerWarehouse.id, '910000000000000001') assert.equal(resource.partnerWarehouse.finalStatus, 'INACTIVE') assert.equal(resource.quote.id, '920000000000000001') assert.equal(resource.quote.finalStatus, 'INACTIVE') assert.match(resource.exchangeRate.month, /^209\d-(0[1-9]|1[0-2])$/) assert.equal(resource.exchangeRate.finalStatus, 'INACTIVE') assert.equal(resource.vehicle.id, '930000000000000001') assert.equal(resource.vehicle.finalStatus, 'INACTIVE') assert.equal(typeof resource.vehicle.recordVersion, 'number') assert.equal(state.warehouse.status, 'INACTIVE') assert.equal(state.quote.summary.status, 'INACTIVE') assert.equal(state.exchange.status, 'INACTIVE') assert.equal(state.vehicle.status, 'INACTIVE') assert.equal(state.role.status, 'DISABLED') assert.equal(state.user.status, 'DISABLED') assert.equal(state.optionReads, 4) assert.equal(context.calls.filter((call) => call.gateway === 'overseas-binary').length, 3) assert.equal(context.calls.filter((call) => call.method === 'DELETE').length, 0) assert.equal(context.calls.filter((call) => call.method === 'PUT').length, 9) assert.equal(context.calls.filter((call) => call.tokenKind === 'denied').length, 1) assert.equal(context.coverageGaps.length, 0) }) test('创建前空候选不形成假PASS,刷新后本轮合作仓进入报价完整生命周期', async () => { const { context } = fakeContext() await run(context) const optionCalls = context.calls.filter((call) => call.path.includes('warehouse-options')) assert.equal(optionCalls.length, 4) assert.equal(optionCalls[0].path.endsWith('warehouse-options'), true) assert.equal(optionCalls[1].path.endsWith('warehouse-options'), true) assert.equal(optionCalls[2].path.endsWith('warehouse-options?refresh=true'), true) assert.equal(optionCalls[3].path.endsWith('warehouse-options'), true) assert.equal(context.createdResources.phase4OverseasMasterData.quote.id, '920000000000000001') assert.equal(context.coverageGaps.length, 0) }) test('refresh后仍为空时报价前置明确deployment blocker且不产生coverageGap假PASS', async () => { const { context, state } = fakeContext({ softAssertions: true, refreshEmpty: true }) await run(context) assert.equal(state.quote, null) assert.equal(state.warehouse.status, 'INACTIVE') assert.equal(state.exchange.status, 'INACTIVE') assert.equal(state.vehicle.status, 'INACTIVE') assert.equal(context.coverageGaps.length, 0) assert.equal(context.steps.some((step) => step.name === '刷新后新建合作仓立即可见' && !step.passed && step.deploymentBlocker), true) assert.equal(context.steps.some((step) => step.name === '海外报价生命周期执行' && !step.passed && step.deploymentBlocker), true) })