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', '--f40-tax-mode', 'TAX_REFUND', '--output', 'reports/gray-contract.json'] test('gray is explicit, keeps real environment identity and creates its own 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') }) 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'] 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']) 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 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/) })