Files
baiyue-firstleg/scripts/admin-test-env-modules/pda-audit-sailing-contract.test.mjs
T
2026-10-20 14:22:55 +08:00

115 lines
6.5 KiB
JavaScript

import assert from 'node:assert/strict'
import test from 'node:test'
import { readFileSync } from 'node:fs'
import { run as runPdaReleases } from './pda-releases.mjs'
import { run as runAudit } from './audit.mjs'
import { run as runSailingCalendar } from './sailing-calendar.mjs'
const entry = readFileSync(new URL('../admin-test-env-acceptance.mjs', import.meta.url), 'utf8')
function context(handler) {
const calls = []
const gaps = []
return {
marker: 'ADMIN-API-OFFLINE',
calls,
gaps,
async request(method, path, body) {
calls.push({ method, path, body })
return handler(method, path, body)
},
async requestMultipart(method, path, form) {
calls.push({ method, path, multipart: form })
return handler(method, path, form)
},
apiSuccess(_module, _name, response) {
assert.equal(response.http, 200)
assert.equal(String(response.body?.code), '200')
return response.body.data
},
assert(_module, name, _response, condition, evidence) {
assert.equal(Boolean(condition), true, `${name}: ${evidence}`)
return true
},
addCoverageGap(module, scenario, name, reason, category, metadata) {
gaps.push({ module, scenario, name, reason, category, metadata })
},
}
}
test('PDA 版本模块注册为默认模块并只读核对双端发布列表', async () => {
assert.match(entry, /const CONFIGURED_MODULES = \[[^\n]*'pda-releases'/)
const releases = (app) => [{
id: '1', app, appid: `__UNI__${app}`, packageType: 'apk', versionName: '1.0.0', versionCode: 1,
minAppVersionCode: 1, mandatory: false, releaseNotes: 'offline', status: 'PUBLISHED', current: true,
sha256: 'a'.repeat(64), sizeBytes: 1024, downloadUrl: '/download', createdAt: '2026-10-01T00:00:00Z',
publishedAt: '2026-10-01T00:00:00Z',
}]
const ctx = context((method, path) => {
if (method === 'GET') return { http: 200, body: { code: '200', data: releases(path.endsWith('/domestic') ? 'domestic' : 'overseas') } }
if (method === 'POST' && path.includes('/publish')) return { http: 200, body: { code: 'A0404', message: '不存在' } }
if (method === 'POST' && path.includes('/withdraw')) return { http: 200, body: { code: 'A0404', message: '不存在' } }
return { http: 400, body: { code: 'A0400', message: '文件不能为空' } }
})
await runPdaReleases(ctx)
assert.deepEqual(ctx.calls.map((row) => row.method), ['GET', 'GET', 'POST', 'POST', 'POST'])
assert.ok(ctx.calls[0].path.endsWith('/domestic'))
assert.ok(ctx.calls[1].path.endsWith('/overseas'))
assert.ok(ctx.gaps.some((gap) => gap.name === '真实 APK/WGT 上传、发布、停发与下载' && gap.metadata.blocking === false))
})
test('平台审计模块注册为默认模块,列表不泄漏快照且详情身份一致', async () => {
assert.match(entry, /const CONFIGURED_MODULES = \[[^\n]*'audit'/)
const row = { id: '7', eventId: 'e-7', eventType: 'ORDER_SUBMIT', resourceType: 'ORDER', resourceId: '1',
accountId: '0', operatorTenantId: '0', userId: '1', occurredAt: '2026-10-01T00:00:00Z', success: true }
const detailCtx = context((method, path) => {
if (path.endsWith('/999999999999999')) return { http: 200, body: { code: 'A0404', message: '不存在' } }
if (path.endsWith('/7')) return { http: 200, body: { code: '200', data: { ...row, httpMethod: 'POST' } } }
return { http: 200, body: { code: '200', data: { records: [row], page: 1, size: 20, total: 1, pages: 1 } } }
})
const withSnapshot = context((method, path) => {
if (path.includes('/audit/logs/')) return { http: 200, body: { code: 'A0404', message: '不存在' } }
return { http: 200, body: { code: '200', data: { records: [{ ...row, beforeData: '{}' }], page: 1, size: 20, total: 1, pages: 1 } } }
})
await runAudit(detailCtx)
assert.equal(detailCtx.calls[0].method, 'GET')
assert.ok(detailCtx.calls[0].path.includes('/audit/logs?page=1&size=20'))
assert.equal(detailCtx.calls[1].path, '/api/platform/v1/audit/logs/7')
assert.ok(detailCtx.calls[2].path.endsWith('/999999999999999'))
await assert.rejects(runAudit(withSnapshot), /不返回变更前后快照/)
})
test('船期日历模块注册为默认模块并核对月历、渠道、模板与异常分配', async () => {
assert.match(entry, /const CONFIGURED_MODULES = \[[^\n]*'sailing-calendar'/)
const summary = {
actualReceivedCbm: 1, catchableForecastCbm: 2, riskUnmatchedCbm: 0, bookedContainerCount: 1,
recommendedContainerCount: 1, pendingContainerCount: 0, lateBatchCount: 0, overCapacity: false,
planningCapacityCbm: 10, bookedPlanningCapacityCbm: 5, catchableReceivedCbm: 1, lateReceivedCbm: 0,
snapshotAt: null, sailingCount: 1,
}
const sailing = {
id: '11', sailingNo: 'SAIL-11', warehouseId: '1', channelCode: 'SEA', channelName: '海运', templateId: null,
channelId: null, etaAt: '2026-10-20T00:00:00', sourceType: 'MANUAL', plannedCapacityCbm: 10,
volumeSummary: summary, version: 0,
}
const ctx = context((method, path) => {
if (path.includes('/calendar')) return { http: 200, body: { code: '200', data: {
records: [sailing], summary, filterOptions: { warehouses: [], channels: [], departurePorts: [], destinationPorts: [] } } } }
if (path.includes('/sailing-channels')) return { http: 200, body: { code: '200', data: [{ id: '1', channelCode: 'SEA', defaultCutoffDays: 3, version: 0 }] } }
if (path.includes('/sailing-templates')) return { http: 200, body: { code: '200', data: { records: [{ id: '1', channelId: '1', warehouseId: '1', weekdays: [1, 2], cutoffDays: 3, version: 0 }] } } }
if (path.includes('/sailing-assignments/exceptions')) return { http: 200, body: { code: '200', data: { records: [{ assignmentId: '1', volumeCbm: 1 }], total: 1 } } }
if (path.endsWith('/11')) return { http: 200, body: { code: '200', data: {
sailing: { ...sailing, volumeSummary: summary }, histories: [] } } }
if (path.endsWith('/999999999999999')) return { http: 200, body: { code: 'A0404', message: '不存在' } }
throw new Error(`unexpected ${method} ${path}`)
})
await runSailingCalendar(ctx)
assert.deepEqual(ctx.calls.map((row) => row.method), ['GET', 'GET', 'GET', 'GET', 'GET', 'GET'])
assert.ok(ctx.calls[0].path.includes('/sailings/calendar?month='))
assert.ok(ctx.calls[1].path.endsWith('/sailing-channels'))
assert.ok(ctx.calls[2].path.includes('/sailing-templates?page=1&size=200'))
assert.ok(ctx.calls[3].path.includes('/sailing-assignments/exceptions'))
assert.ok(ctx.calls[4].path.endsWith('/11'))
assert.ok(ctx.calls[5].path.endsWith('/999999999999999'))
})