import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { normalizeWeightPalletOptionPage } from './inbound-weight-ocr.mjs' const read = (relativeUrl) => readFileSync(new URL(relativeUrl, import.meta.url), 'utf8') const controller = read('../../firstleg/src/main/java/com/baiyue/firstleg/controller/PlatformInboundMeasurementController.java') const pageResult = read('../../common-core/src/main/java/com/baiyue/common/core/api/PageResult.java') const longJackson = read('../../common-web/src/main/java/com/baiyue/common/web/LongIdJacksonConfiguration.java') const serviceTests = read('../../firstleg/src/test/java/com/baiyue/firstleg/service/InboundMeasurementServiceTest.java') const openapi = read('../../firstleg/src/main/resources/openapi/firstleg-v1.yaml') const adminApi = read('../../../apps/admin-web/src/modules/inbounds/weight/api/weightOcr.ts') const adminApiTests = read('../../../apps/admin-web/src/modules/inbounds/weight/api/weightOcr.test.ts') const adminPanel = read('../../../apps/admin-web/src/modules/inbounds/weight/components/WeightOcrPanel.vue') const adminBehavior = read('../../../apps/admin-web/src/modules/inbounds/weight/components/WeightOcrPanel.behavior.test.ts') function endpointBlock(source, marker, nextMarker) { const start = source.indexOf(marker) assert.notEqual(start, -1, `missing marker: ${marker}`) const end = source.indexOf(nextMarker, start + marker.length) return source.slice(start, end === -1 ? source.length : end) } test('formal success accepts two records and string pagination numbers', () => { const records = [{ palletId: '1' }, { palletId: '2' }] assert.deepEqual(normalizeWeightPalletOptionPage({ records, page: '1', size: '20', total: '2', pages: '1' }), { records, page: 1, size: 20, total: 2, pages: 1 }) }) test('formal empty page remains a successful page instead of an error', () => { assert.deepEqual(normalizeWeightPalletOptionPage({ records: [], page: '1', size: '20', total: '0', pages: '0' }), { records: [], page: 1, size: 20, total: 0, pages: 0 }) }) test('malformed array and invalid page metadata are rejected by the HTTP contract', () => { assert.equal(normalizeWeightPalletOptionPage([]), null) assert.equal(normalizeWeightPalletOptionPage({ records: [], page: 1, size: 20, total: -1, pages: 0 }), null) assert.equal(normalizeWeightPalletOptionPage({ records: [], page: 1, size: 101, total: 0, pages: 0 }), null) }) test('Controller DTO and response envelope use PageResult records/page/size/total/pages', () => { const block = endpointBlock(controller, '@GetMapping("/weight-pallet-options")', '@BusinessAudit') assert.match(block, /ApiResponse>/) assert.match(pageResult, /record PageResult\(List records, long page, long size, long total, long pages\)/) }) test('OpenAPI documents the page object and permits runtime string pagination numbers', () => { const endpoint = endpointBlock(openapi, ' /api/platform/v1/first-leg/inbounds/{inboundId}/weight-pallet-options:', ' /api/platform/v1/first-leg/inbounds/{inboundId}/weight-results:') assert.match(endpoint, /data: \{\$ref: '#\/components\/schemas\/WeightPalletOptionPage'\}/) const schema = endpointBlock(openapi, ' WeightPalletOptionPage:', ' CreateWeightResultRequest:') assert.match(schema, /required: \[records, page, size, total, pages\]/) for (const field of ['page', 'size', 'total', 'pages']) { assert.match(schema, new RegExp(`${field}:[^\n]*(?:oneOf|string)`), `${field} must permit the string form emitted by the shared Long serializer`) } assert.match(longJackson, /serializerByType\(Long\.TYPE, ToStringSerializer\.instance\)/) }) test('Admin reads records, normalizes numeric strings, and never treats data as an array', () => { const block = endpointBlock(adminApi, 'export async function getWeightPalletOptions', 'export async function uploadWeightImage') assert.match(block, /ApiEnvelope/) assert.match(block, /records:\s*page\.records/) assert.match(block, /normalizedPageNumber\(page\.(?:page|size|total|pages)/) assert.doesNotMatch(block, /ApiEnvelope/) assert.match(adminPanel, /palletOptions\.value = result\.records/) }) test('Admin behavior covers two-record success and an empty successful page', () => { assert.match(adminApiTests, /formal paged pallet response[\s\S]{0,900}records[\s\S]{0,300}page: '1'/) assert.match(adminBehavior, /no pallet options[\s\S]{0,500}palletPage\(\[\]\)/) }) test('Admin behavior exposes a body code business error as a candidate load failure', () => { assert.match(adminBehavior, /getWeightPalletOptions\)\.mockRejectedValueOnce\(new ApiResponseError\([^)]*'A0500'/) assert.match(adminBehavior, /可选托盘加载失败|托盘服务暂不可用/) }) test('Admin behavior separately covers a non-2xx HTTP or network error', () => { assert.match(adminBehavior, /(?:HTTP error|HTTP错误|network error|网络错误)[\s\S]{0,700}(?:AxiosError|HTTP_5|status)/i) }) test('Admin aborts and ignores an expired candidate response', () => { assert.match(adminBehavior, /in-flight pallet request[\s\S]{0,900}signal\?\.aborted[\s\S]{0,300}过期托盘/) }) test('backend preserves failure evidence for Warehouse non-2xx and network errors', () => { assert.match(serviceTests, /weightPalletOptionsFailsClosedOnWarehouseNon2xxResponse/) assert.match(serviceTests, /weightPalletOptionsFailsClosedOnWarehouseNetworkError/) assert.match(serviceTests, /仓库服务暂不可用/) })