78 lines
5.2 KiB
JavaScript
78 lines
5.2 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { executionOptions, validatePage, verifyWorkbench } from './client-workbench-test-env.mjs'
|
|
|
|
const env = { CHONGCHONG_ENV: 'TEST', CHONGCHONG_MERCHANT_API_BASE_URL: 'https://test.invalid/api', CHONGCHONG_MERCHANT_USERNAME: 'fixture', CHONGCHONG_MERCHANT_PASSWORD: 'fixture' }
|
|
const args = ['--execute', '--environment=TEST', '--run-id=fixture']
|
|
const execArgs = ['--env-file=C:/Users/BY-OY/.codex/env/chongchong-global/test-acceptance.env']
|
|
test('plan has no credential or environment dependency', () => {
|
|
assert.deepEqual(executionOptions([], [], {}), { execute: false })
|
|
assert.deepEqual(executionOptions(['--plan'], [], {}), { execute: false })
|
|
})
|
|
test('execution requires explicit TEST profile, safe run id and API prefix', () => {
|
|
assert.equal(executionOptions(args, execArgs, env).runId, 'fixture')
|
|
for (const [a, x, e] of [
|
|
[args, [], env], [args, execArgs, { ...env, CHONGCHONG_ENV: 'PROD' }],
|
|
[args, [execArgs[0].replace('test-acceptance', 'prod-acceptance')], env],
|
|
[['--execute', '--environment=TEST', '--run-id=../escape'], execArgs, env],
|
|
[[...args, '--resume'], execArgs, env],
|
|
[args, execArgs, { ...env, CHONGCHONG_MERCHANT_API_BASE_URL: 'https://test.invalid/' }],
|
|
]) assert.throws(() => executionOptions(a, x, e))
|
|
})
|
|
function fixture() {
|
|
const rows = Array.from({ length: 215 }, (_, i) => ({ orderId: String(1000 + i), tenantId: '11',
|
|
mainStatus: i < 3 ? 'DRAFT' : 'LOADED', customerFeeConfirmationRequired: i >= 10 && i < 12,
|
|
channelName: i % 2 ? 'SEA' : 'AIR' }))
|
|
const calls = []
|
|
const queues = list => ({ all: list.length, fee: list.filter(r => r.customerFeeConfirmationRequired).length,
|
|
customs: list.filter(r => r.orderId === '1012').length, draft: list.filter(r => r.mainStatus === 'DRAFT').length })
|
|
function read(path) {
|
|
calls.push(path)
|
|
const url = new URL(path, 'https://test.invalid'), q = url.searchParams
|
|
let selected = rows.filter(r => !q.get('channelName') || r.channelName === q.get('channelName'))
|
|
const counts = queues(selected)
|
|
if (url.pathname === '/merchant/v1/orders') selected = selected.filter(r => r.mainStatus !== 'DRAFT')
|
|
if (q.get('queue') === 'fee') selected = selected.filter(r => r.customerFeeConfirmationRequired)
|
|
if (q.get('queue') === 'customs') selected = selected.filter(r => r.orderId === '1012')
|
|
if (q.get('queue') === 'draft') selected = selected.filter(r => r.mainStatus === 'DRAFT')
|
|
const number = Number(q.get('page')), size = Number(q.get('size'))
|
|
const page = { page: number, size, total: selected.length, pages: Math.ceil(selected.length / size), records: selected.slice((number - 1) * size, number * size).map(r => ({ ...r })) }
|
|
return url.pathname.endsWith('/workbench') ? { page, queues: counts, channelNames: ['AIR', 'SEA'] } : page
|
|
}
|
|
return { read, calls, rows }
|
|
}
|
|
test('complete traversal proves beyond-200 data, independent formal membership, queues and filters', async () => {
|
|
const h = fixture(), proof = await verifyWorkbench(h.read, '11')
|
|
assert.equal(proof.total, 215); assert.equal(proof.formalTotal, 212)
|
|
assert.ok(proof.checks.length >= 19); assert.equal(proof.membershipSha256.length, 64)
|
|
assert.ok(h.calls.includes('/merchant/v1/orders/workbench?page=21&size=10'))
|
|
assert.equal(proof.limitations.length, 2)
|
|
})
|
|
for (const [name, mutate, expected] of [
|
|
['first-200 truncation', (r, path) => { if (path.includes('workbench?') && path.includes('page=1&size=200')) { r.page.total = 200; r.page.pages = 1 } }, /former 200/],
|
|
['wrong page metadata', r => { (r.page ?? r).size = 10 }, /metadata/],
|
|
['customer leakage', r => { (r.page ?? r).records[0].tenantId = '22' }, /Customer scope/],
|
|
['duplicate identity', r => { const p = r.page ?? r; if (p.records.length > 1) p.records[1] = p.records[0] }, /duplicate/],
|
|
['partial response', r => { (r.page ?? r).records.pop() }, /Truncated/],
|
|
['wrong fee count', r => { if (r.queues) r.queues.fee = 99 }, /fee count/],
|
|
['wrong draft count', r => { if (r.queues) r.queues.draft = 0 }, /draft count/],
|
|
['missing channel option', r => { if (r.queues) r.channelNames = ['SEA'] }, /channel options/],
|
|
['wrong page 21 slice', (r, path) => { if (path.includes('page=21&size=10')) r.page.records.reverse() }, /ordered slice/],
|
|
]) test(`refuses ${name}`, async () => {
|
|
const h = fixture()
|
|
await assert.rejects(verifyWorkbench(path => { const response = h.read(path); mutate(response, path); return response }, '11'), expected)
|
|
})
|
|
test('changed independent formal set is a failure, not partial success', async () => {
|
|
const h = fixture()
|
|
await assert.rejects(verifyWorkbench(path => {
|
|
const result = h.read(path)
|
|
if (!path.includes('/workbench') && path.includes('page=1&') && result.records.length) result.records[0].orderId = 'foreign-formal'
|
|
return result
|
|
}, '11'), /independent unified/)
|
|
})
|
|
test('empty out-of-range pages validate while impossible totals and missing ids fail', () => {
|
|
assert.equal(validatePage({ page: 3, size: 10, total: 20, pages: 2, records: [] }, 3, 10).total, 20)
|
|
assert.throws(() => validatePage({ page: 1, size: 10, total: -1, pages: 0, records: [] }, 1, 10))
|
|
assert.throws(() => validatePage({ page: 1, size: 10, total: 1, pages: 1, records: [{}] }, 1, 10))
|
|
})
|