test(acceptance): reuse fixed customers and align native fulfillment flows

This commit is contained in:
2026-10-23 04:48:02 +08:00
parent b92f7fe991
commit 139f6b1932
51 changed files with 1076 additions and 228 deletions
@@ -10,8 +10,8 @@ function fixedContext(profileId = '2100000000000000001') {
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 })
async request(method, path, body) {
calls.push({ method, path, body })
const data = path.endsWith('/login') ? { accessToken: 'fixture-token', refreshToken: 'fixture-refresh' }
: path.endsWith('/profile') ? { id: profileId, status: 'ACTIVE', tenantName: 'Fixture' }
: { status: 'READY' }
@@ -51,6 +51,31 @@ test('second customer fixture cannot silently reuse the first customer', async (
assert.equal(context.calls.length, 0)
})
test('second fixed customer validates its own credentials and profile without creating a customer or publishing primary credentials', async () => {
const context = fixedContext('2100000000000000002')
context.options.fixedSecondaryCustomer = { customerId: 'TEN2100000000000000002', username: 'second-test', password: 'second-password' }
const primaryOptions = structuredClone(context.options)
const before = { username: process.env.MERCHANT_TEST_USERNAME, password: process.env.MERCHANT_TEST_PASSWORD }
await run(context, { publishEnvironment: false })
assert.equal(context.context.merchantQualificationFixture.tenantId, '2100000000000000002')
assert.equal(context.context.merchantQualificationFixture.username, 'second-test')
assert.deepEqual(context.calls[0].body, { loginId: 'second-test', password: 'second-password' })
assert.equal(context.createdResources.merchantQualificationFixture.password, undefined)
assert.deepEqual(context.options, primaryOptions)
assert.deepEqual({ username: process.env.MERCHANT_TEST_USERNAME, password: process.env.MERCHANT_TEST_PASSWORD }, before)
assert.deepEqual(context.calls.filter(call => call.method !== 'GET').map(call => call.path), ['/api/auth/login', '/api/auth/logout'])
})
for (const fault of ['same customer', 'wrong profile', 'missing credentials']) test(`second fixed customer rejects ${fault}`, async () => {
const context = fixedContext()
context.options.fixedSecondaryCustomer = { customerId: fault === 'same customer' ? context.options.fixedCustomerId : '2100000000000000002',
username: 'second-test', password: fault === 'missing credentials' ? '' : 'second-password' }
await assert.rejects(run(context, { publishEnvironment: false }), /第二个客户|第二个固定客户|归属或状态不匹配/)
assert.ok(context.calls.every(call => call.method === 'GET' || ['/api/auth/login', '/api/auth/logout'].includes(call.path)))
if (fault !== 'wrong profile') assert.equal(context.calls.length, 0)
else assert.equal(context.calls.at(-1).path, '/api/auth/logout')
})
test('TEST fixture without fixed customer fails before any request', async () => {
const context = fixedContext()
context.options = { environment: 'test' }
@@ -92,6 +117,18 @@ test('both request entrypoints reject customer creation before network access',
}
})
test('report security rejects the secondary password even under an ordinary evidence field', async () => {
for (const leak of [false, true]) {
const password = 'secondary-private-test-credential'
const runner = new AcceptanceRun({ environment: 'test', fixedCustomerId: '123', quiet: true, modules: [],
fixedSecondaryCustomer: { customerId: '456', username: 'second-test', password } })
runner.request = async () => ({ http: 200, body: { code: 'A0401' } })
runner.createdResources.note = leak ? `unexpected detail: ${password}` : 'ordinary diagnostic evidence'
const report = await runner.run()
assert.equal(report.steps.find(step => step.module === 'report-security').passed, !leak)
}
})
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')