import { spawn } from 'node:child_process' import { readFile } from 'node:fs/promises' import { parseEnv } from 'node:util' import { resolve, dirname } from 'node:path' import { fileURLToPath, pathToFileURL } from 'node:url' import { executionOptions } from './client-workbench-test-env.mjs' export async function execute(args = process.argv.slice(2), execArgs = process.execArgv, env = process.env) { if (!args.length || args.length === 1 && args[0] === '--plan') return { plan: true, businessWrites: false, checks: ['full warehouse catalog', 'channel union', 'own customer addresses', 'named dropdown ranges', '50 input rows'], required: 'Explicit TEST profile, new run-id, and --python=' } if (![4, 5].includes(args.length) || !args[3].startsWith('--python=')) throw new Error('Explicit bundled Python required') const options = executionOptions(args.slice(0, 3), execArgs, env) const python = resolve(args[3].slice(9)) if (!python.replaceAll('\\', '/').endsWith('/codex-primary-runtime/dependencies/python/python.exe')) throw new Error('Use bundled workspace Python') const root = resolve(dirname(fileURLToPath(import.meta.url)), '..') const config = { environment: 'TEST', base: options.base, username: env.CHONGCHONG_MERCHANT_USERNAME, password: env.CHONGCHONG_MERCHANT_PASSWORD, runId: options.runId } if (args[4]) { const profile = customerProfilePath(args[4], execArgs[0].slice(11)) const credentials = parseEnv(await readFile(profile, 'utf8')) if (!credentials.MERCHANT_TEST_USERNAME || !credentials.MERCHANT_TEST_PASSWORD) throw new Error('Missing TEST customer credentials') config.username = credentials.MERCHANT_TEST_USERNAME config.password = credentials.MERCHANT_TEST_PASSWORD } const child = spawn(python, [resolve(root, 'scripts/client-import-template-inspect.py')], { cwd: root, windowsHide: true, stdio: ['pipe', 'inherit', 'inherit'] }) child.stdin.end(JSON.stringify(config)) const code = await new Promise((accept, reject) => { child.on('error', reject); child.on('close', accept) }) return { success: code === 0, report: resolve(root, 'reports', `client-template-${options.runId}`, 'report.json') } } export function customerProfilePath(argument, environmentFile) { if (!argument.startsWith('--customer-profile=')) throw new Error('Unknown argument') const profile = resolve(argument.slice(19)) if (dirname(profile) !== dirname(resolve(environmentFile)) || !/^test-ui-[A-Za-z0-9_-]+\.env$/.test(profile.split(/[\\/]/).at(-1))) throw new Error('Customer profile must be an explicit TEST UI file beside the authorized TEST configuration') return profile } export function compareCustomerTemplates(primary, control) { const checks = { bothRunsPassed: primary.success === true && control.success === true, bothSessionsClosed: primary.sessionClosed === true && control.sessionClosed === true, differentCustomers: typeof primary.customerIdentitySha256 === 'string' && typeof control.customerIdentitySha256 === 'string' && primary.customerIdentitySha256 !== control.customerIdentitySha256, primaryHasPrivateAddress: primary.template?.checks?.['私人目的仓地址']?.actualCount > 0, controlHasNoPrivateAddress: control.template?.checks?.['私人目的仓地址']?.actualCount === 0, privateOptionsMatchOwnDirectory: [primary, control].every(r => r.template?.checks?.['私人目的仓地址']?.passed === true), pickupOptionsMatchOwnDirectory: [primary, control].every(r => r.template?.checks?.['取货地址']?.passed === true), differentPickupAddressSamples: primary.template?.checks?.['取货地址']?.actualSha256 !== control.template?.checks?.['取货地址']?.actualSha256, } return { success: Object.values(checks).every(Boolean), checks, limitation: 'Read-time customer isolation only; importing another customer address remains a separate negative test' } } if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { execute().then(result => { console.log(JSON.stringify(result)); if (result.success === false) process.exitCode = 1 }) .catch(() => { console.error('Template audit did not start. Check explicit TEST profile, runtime and new run-id.'); process.exitCode = 1 }) }