Files
baiyue-firstleg/scripts/gray-acceptance-entry-contract.test.mjs
T
2026-10-27 17:44:41 +08:00

119 lines
6.9 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { AcceptanceRun, configureReadOnlyEvidence, parseArguments, validateExecutionOptions } from './admin-test-env-acceptance.mjs'
const args = ['--environment', 'gray', '--modules', 'phase5-root-fulfillment',
'--gateway', 'https://gray.example.invalid', '--username', 'contract', '--password', 'contract',
'--pda-username', 'contract-pda', '--pda-password', 'contract-pda',
'--fixed-customer-id', 'TEN2100000000000000001', '--merchant-username', 'existing-gray', '--merchant-password', 'offline-gray-password',
'--f40-tax-mode', 'TAX_REFUND', '--output', 'reports/gray-contract.json']
test('gray is explicit, keeps real environment identity and uses the designated existing customer', () => {
const options = validateExecutionOptions(parseArguments(args))
assert.equal(options.environment, 'gray')
assert.deepEqual(options.modules, ['merchant-qualification-fixture', 'phase5-root-fulfillment'])
const run = new AcceptanceRun(options)
assert.match(run.marker, /^GRAY-API-/)
assert.equal(run.context.f40TaxMode, 'TAX_REFUND')
assert.equal(run.options.fixedCustomerId, 'TEN2100000000000000001')
})
test('gray cannot run unbounded full suite, extra modules, bulk data or TEST faults', () => {
assert.throws(() => parseArguments(['--environment', 'gray']), /显式指定/)
for (const extra of [
['--modules', 'phase4-cross-region-events'],
['--modules', 'phase5-root-fulfillment,customers'],
['--verify-unload-event-recovery', 'true'], ['--wait-split-count', '2'],
]) assert.throws(() => validateExecutionOptions(parseArguments([...args, ...extra])), /gray/)
assert.throws(() => validateExecutionOptions(parseArguments([...args, '--f40-tax-mode', 'UNKNOWN'])), /f40-tax-mode/)
})
test('gray endpoint must be a single HTTPS origin and needs a named report', () => {
for (const endpoint of ['http://gray.example.invalid', 'https://gray.example.invalid/api',
'https://user:secret@gray.example.invalid', 'https://gray.example.invalid?debug=1']) {
assert.throws(() => validateExecutionOptions(parseArguments([...args, '--gateway', endpoint])), /HTTPS/)
}
const options = parseArguments(args)
options.output = ''
assert.throws(() => validateExecutionOptions(options), /output/)
})
test('gray has no fallback to TEST account, gateway, customer or PDA environment variables', () => {
const keys = ['ADMIN_TEST_GATEWAY', 'ADMIN_TEST_USERNAME', 'ADMIN_TEST_PASSWORD', 'ADMIN_PDA_USERNAME',
'ADMIN_PDA_PASSWORD', 'MERCHANT_TEST_USERNAME', 'MERCHANT_TEST_PASSWORD', 'ADMIN_TEST_CUSTOMER_NAME',
'ADMIN_GRAY_GATEWAY', 'ADMIN_GRAY_USERNAME', 'ADMIN_GRAY_PASSWORD', 'ADMIN_GRAY_PDA_USERNAME',
'ADMIN_GRAY_PDA_PASSWORD', 'MERCHANT_GRAY_USERNAME', 'MERCHANT_GRAY_PASSWORD', 'ADMIN_GRAY_CUSTOMER_NAME',
'ADMIN_GRAY_FIXED_CUSTOMER_ID', 'ADMIN_TEST_FIXED_CUSTOMER_ID']
const saved = new Map(keys.map(key => [key, process.env[key]]))
try {
for (const key of keys) {
if (key.includes('GRAY')) delete process.env[key]
else process.env[key] = 'must-not-be-used'
}
const options = parseArguments(['--environment', 'gray', '--modules', 'phase5-root-fulfillment'])
for (const key of ['gateway', 'username', 'password', 'pdaUsername', 'pdaPassword',
'merchantUsername', 'merchantPassword', 'customerName', 'fixedCustomerId']) assert.equal(options[key], '')
process.env.ADMIN_GRAY_GATEWAY = 'https://gray.example.invalid'
assert.equal(parseArguments(['--environment', 'gray', '--modules', 'phase5-root-fulfillment']).gateway,
'https://gray.example.invalid')
} finally {
for (const [key, value] of saved) {
if (value === undefined) delete process.env[key]
else process.env[key] = value
}
}
})
test('gray missing fixed identity or customer credentials fails without network; both transports forbid customer creation', async () => {
const original = globalThis.fetch
globalThis.fetch = () => assert.fail('missing configuration and customer writes must stop before HTTP')
try {
for (const field of ['fixedCustomerId', 'merchantUsername', 'merchantPassword']) {
const options = parseArguments(args); options[field] = ''
assert.throws(() => validateExecutionOptions(options), /禁止自动创建客户/)
}
const runner = new AcceptanceRun(validateExecutionOptions(parseArguments(args)))
for (const entry of ['request', 'requestWithBody']) {
for (const path of ['/api/platform/v1/iam/tenants', '/api/auth/register', '/api/platform/v1/iam/tenants/123/submit-review'])
await assert.rejects(runner[entry]('POST', path, {}), /GRAY.*禁止创建/)
}
} finally { globalThis.fetch = original }
})
test('gray evidence preflight attaches only read-only readers, never constructs fault executors', async () => {
const run = new AcceptanceRun(validateExecutionOptions(parseArguments(args)))
const readReceiving = () => {}, readCompletion = () => {}
let preflight = false
const faults = new Proxy({}, { get() { throw Error('TEST fault factory must not be read') } })
assert.equal(await configureReadOnlyEvidence(run, {}, () => ({ readReceiving, readCompletion,
preflight: async () => { preflight = true } }), faults), undefined)
assert.equal(preflight, true)
assert.equal(run.context.phase4ReceivingEvidenceReader, readReceiving)
assert.equal(run.context.f40ReadOnlyOutboxEvidence, readCompletion)
assert.equal(run.context.beforeLoadingComplete, undefined)
assert.equal(run.context.beforeReceivingComplete, undefined)
assert.equal(run.context.f40DeliveryEventFaultExecutor, undefined)
})
test('direct gray evidence use cannot bypass module/fault restrictions or failed preflight', async () => {
const options = validateExecutionOptions(parseArguments(args))
const bad = { options: { ...options, verifyUnloadEventRecovery: true }, context: {} }
await assert.rejects(configureReadOnlyEvidence(bad, {}, () => { throw Error('must not get here') }), /故障注入/)
const run = new AcceptanceRun(options)
await assert.rejects(configureReadOnlyEvidence(run, {}, () => ({
preflight: async () => { throw Error('gray SSH unavailable') },
})), /gray SSH unavailable/)
assert.equal(run.context.phase4ReceivingEvidenceReader, undefined)
})
test('domestic H5 fixture is opt-in and cannot expand the gray module scope', () => {
const defaults = validateExecutionOptions(parseArguments(args))
assert.equal(defaults.prepareDomesticPdaH5, false)
const prepared = validateExecutionOptions(parseArguments([...args, '--prepare-domestic-pda-h5', 'true']))
assert.equal(new AcceptanceRun(prepared).context.prepareDomesticPdaH5, true)
for (const invalid of ['yes', 'false', '1']) assert.throws(() => parseArguments([...args, '--prepare-domestic-pda-h5', invalid]), /必须明确为 true/)
assert.deepEqual(prepared.requestedModules, ['phase5-root-fulfillment'])
assert.throws(() => validateExecutionOptions({ ...prepared, environment: 'test', requestedModules: ['orders'] }), /prepare-domestic-pda-h5/)
})