import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import vm from 'node:vm' import { qualificationCreditCode, normalizeFixedCustomerId, run } from './merchant-qualification-fixture.mjs' import { AcceptanceRun } from '../admin-test-env-acceptance.mjs' function fixedContext(profileId = '2100000000000000001') { const calls = [] return { calls, marker: 'FIXED-CUSTOMER-TEST', context: {}, createdResources: {}, options: { fixedCustomerId: 'TEN2100000000000000001', merchantUsername: 'fixture-user', merchantPassword: 'fixture-password' }, async request(method, path) { calls.push({ method, path }) const data = path.endsWith('/login') ? { accessToken: 'fixture-token', refreshToken: 'fixture-refresh' } : path.endsWith('/profile') ? { id: profileId, status: 'ACTIVE', tenantName: 'Fixture' } : { status: 'READY' } return { http: 200, body: { code: 200, data } } }, apiSuccess(_module, _label, response) { return response.body.data }, assert() {}, } } test('fixed customer accepts business number without losing integer precision', () => { assert.equal(normalizeFixedCustomerId('TEN2100000000000000001'), '2100000000000000001') assert.throws(() => normalizeFixedCustomerId('TENinvalid')) }) test('fixed customer preparation only authenticates and reads existing customer', async () => { const context = fixedContext() await run(context, { publishEnvironment: false, requireDistinctCustomer: false }) assert.equal(context.context.merchantQualificationFixture.reused, true) assert.equal(context.context.merchantQualificationFixture.tenantId, '2100000000000000001') assert.equal(context.context.customerId, '2100000000000000001') assert.deepEqual(context.calls.filter(call => call.method !== 'GET').map(call => call.path), ['/api/auth/login', '/api/auth/logout']) assert.equal(context.createdResources.merchantQualificationFixture.password, undefined) }) test('wrong customer identity stops preparation and logs out', async () => { const context = fixedContext('2100000000000000002') await assert.rejects(run(context, { publishEnvironment: false, requireDistinctCustomer: false }), /归属或状态不匹配/) assert.equal(context.context.merchantQualificationFixture, undefined) assert.equal(context.calls.at(-1).path, '/api/auth/logout') assert.equal(context.calls.length, 3) }) test('second customer fixture cannot silently reuse the first customer', async () => { const context = fixedContext() await assert.rejects(run(context, { publishEnvironment: false }), /跨客户场景阻断/) assert.equal(context.calls.length, 0) }) test('TEST fixture without fixed customer fails before any request', async () => { const context = fixedContext() context.options = { environment: 'test' } await assert.rejects(run(context), /必须配置固定客户/) assert.equal(context.calls.length, 0) }) test('cross-customer financial helpers stop before provisioning or writing', async () => { for (const [file, name, end] of [ ['finance-direct-paid-isolation.mjs', 'createSecondCustomer', 'async function verifyCustomerIsolation'], ['firstleg-container-fee-allocation.mjs', 'provisionCrossCustomerPlan', 'export async function run'], ]) { const source = readFileSync(new URL(file, import.meta.url), 'utf8') const helper = source.slice(source.indexOf(`async function ${name}(`), source.indexOf(end)) const scope = vm.createContext({}) vm.runInContext(helper.replaceAll('import.meta.url', "'file:///unused-test-module.mjs'"), scope) await assert.rejects(scope[name]({ options: { fixedCustomerId: '123' } }), /缺少第二个固定客户/) } }) test('fixed resource runner excludes lifecycle handlers without marking them passed', async () => { const runner = new AcceptanceRun({ environment: 'test', fixedCustomerId: '123', quiet: true }) let calls = 0 for (const name of ['customers', 'supplemental-boundaries', 'system', 'master-data', 'suppliers', 'notifications', 'pricing', 'phase4-overseas-master-data', 'phase4-overseas-wallet-and-pallet-cost']) { await runner.executeModule(name, async () => { calls++ }) } assert.equal(calls, 0) assert.equal(runner.modules.length, 9) assert.ok(runner.modules.every(module => module.skipped && !module.success)) }) test('both request entrypoints reject customer creation before network access', async () => { const runner = new AcceptanceRun({ environment: 'test' }) for (const method of ['request', 'requestWithBody']) { for (const path of ['/api/auth/register', '/api/auth/register/?source=test', '/api/platform/v1/iam/tenants', '/api/platform/v1/iam/tenants/123/archive', '/api/platform/v1/iam/tenants/123/submit-review']) { await assert.rejects(runner[method]('POST', path, {}), /禁止创建、归档客户/) } } }) const acceptance = readFileSync(new URL('../admin-test-env-acceptance.mjs', import.meta.url), 'utf8') const fixture = readFileSync(new URL('./merchant-qualification-fixture.mjs', import.meta.url), 'utf8') const orders = readFileSync(new URL('./orders.mjs', import.meta.url), 'utf8') test('credit codes distinguish repeated CUSTOMER-B and SIGNED-B fixtures using the complete marker', () => { const customerId = '2100000000000000001' const values = [] for (const ending of ['CUSTOMER-B', 'SIGNED-B']) { const firstMarker = `ADMIN-API-20260930121500000-${ending}` const secondMarker = `ADMIN-API-20260930121600000-${ending}` const first = qualificationCreditCode(firstMarker, customerId) const second = qualificationCreditCode(secondMarker, customerId) assert.notEqual(first, second, `时间不同但后缀同为 ${ending} 必须生成不同代码`) assert.equal(qualificationCreditCode(firstMarker, customerId), first) for (const value of [first, second]) assert.match(value, /^91440300[0-9A-F]{10}$/) values.push(first, second) } assert.equal(new Set(values).size, values.length) assert.match(fixture, /unifiedSocialCreditCode = qualificationCreditCode\(context\.marker, tenantId\)/) }) test('a new real customer under the same marker receives a distinct stable code', () => { const marker = 'ADMIN-API-20260930121500000-CUSTOMER-B' assert.notEqual(qualificationCreditCode(marker, '2100000000000000001'), qualificationCreditCode(marker, '2100000000000000002')) assert.throws(() => qualificationCreditCode('', '2100000000000000001'), /完整批次标识/) assert.throws(() => qualificationCreditCode(marker, ''), /真实客户编号/) }) test('默认全量验收先准备已审核商户再运行订单链路', () => { const modules = acceptance.match(/const CONFIGURED_MODULES = \[([^\n]+)\]/)?.[1] || '' assert.match(modules, /^'merchant-qualification-fixture', 'customers'/) assert.ok(modules.indexOf("'merchant-qualification-fixture'") < modules.indexOf("'orders'")) assert.match(acceptance, /DEFAULT_MODULES\s*=\s*normalizeModuleOrder\(CONFIGURED_MODULES\)/) }) test('商户夹具通过正式 File 和 IAM 审核流程建立下单资格', () => { assert.match(fixture, /files\/qualifications\/\$\{encodeURIComponent\(tenantId\)\}\/required-package/) assert.match(fixture, /required-package\?unifiedSocialCreditCode=\$\{encodeURIComponent\(unifiedSocialCreditCode\)\}/) assert.doesNotMatch(fixture, /packageForm\.append\('unifiedSocialCreditCode'/) assert.match(fixture, /iam\/tenants\/\$\{encodeURIComponent\(tenantId\)\}\/submit-review/) assert.match(fixture, /iam\/tenants\/\$\{encodeURIComponent\(tenantId\)\}\/dimension-initialization/) assert.match(fixture, /status === 'READY'/) assert.match(fixture, /setTimeout\(resolve, 500\)/) assert.match(fixture, /iam\/reviews\/\$\{encodeURIComponent\(review\.id\)\}\/approve/) assert.match(fixture, /item\.status === 'APPROVED'/) assert.match(fixture, /MERCHANT_TEST_USERNAME/) assert.match(fixture, /MERCHANT_TEST_PASSWORD/) assert.match(fixture, /context\.options\?\.customerName/) assert.match(fixture, /tenantName/) assert.doesNotMatch(fixture, /expectedProcurementPrefix/) assert.match(acceptance, /administrator-phone-availability\?phone=/) assert.match(acceptance, /attempt < 100/) assert.doesNotMatch(acceptance, /phoneSequence % 10/) assert.doesNotMatch(fixture, /internal\/iam|UPDATE\s+iam_|INSERT\s+INTO\s+iam_/i) }) test('批量待分柜参数固定同一客户并限制为订单测量链', () => { assert.match(acceptance, /--customer-name /) assert.match(acceptance, /--merchant-username /) assert.match(acceptance, /--origin-warehouse-name /) assert.match(acceptance, /--channel-name /) assert.match(acceptance, /--wait-split-count /) assert.match(acceptance, /--order-profile-mode /) assert.match(acceptance, /merchant-username和merchant-password必须同时提供/) assert.match(acceptance, /modules\.splice\(fixtureIndex, 1\)/) assert.match(acceptance, /wait-split-count必须是1-100的整数/) assert.match(acceptance, /fixtureModules = new Set\(\['merchant-qualification-fixture', 'orders', 'inbound-measurement',[\s\S]{0,180}'coload-groups'[\s\S]{0,180}'firstleg-container-fee-allocation'/) assert.match(acceptance, /批量WAIT_SPLIT只允许执行WAIT_SPLIT造数、同柜组与柜次跨客户分摊模块/) assert.match(acceptance, /createAdditionalWaitSplitFixtures/) assert.match(acceptance, /orderBlockingFailure[\s\S]*step\.deploymentBlocker/) assert.match(acceptance, /measurementBlockingFailure[\s\S]*step\.deploymentBlocker/) assert.doesNotMatch(acceptance, /this\.modules\.at\(-1\)\?\.success !== true/) assert.match(acceptance, /measurement\.processStatus === expectedProcessStatus/) assert.match(acceptance, /prepareDropshipManualCount \? 'WAIT_PALLETIZE' : 'WAIT_SPLIT'/) assert.match(acceptance, /measurement\.finalStatus === 'APPROVED'/) assert.match(acceptance, /waitSplitVariant\(sequence\)/) assert.match(acceptance, /packageCountPerSku/) assert.match(acceptance, /grossWeightPerPalletKg/) assert.match(acceptance, /randomizedOrderProfile\(sequence\)/) assert.match(acceptance, /\['PRIVATE', 'STANDARD'\]/) assert.match(acceptance, /ONE_CLICK_DROPSHIP/) assert.match(acceptance, /destinationWarehouseCode: order\.destinationWarehouseCode/) assert.match(acceptance, /channelName: order\.channelName/) assert.match(acceptance, /pricingTaxMode: order\.pricingTaxMode/) assert.match(orders, /customerId: profile\.id/) assert.match(orders, /customerName: companyName/) assert.match(orders, /\['FBA', 'OVERSEAS', 'PRIVATE'\]\.includes/) assert.match(orders, /destinationType === 'PRIVATE'/) assert.match(orders, /PRIVATE_ADDRESS/) assert.match(orders, /eligible\.length < 1/) assert.match(orders, /Client 查询发货客户候选', partnerResponse, '', CONTRACT_OPTIONS/) assert.doesNotMatch(orders, /customerName: master\.partner\?\.partnerName/) })