Files
baiyue-firstleg/scripts/admin-test-env-modules/fixed-test-actors-contract.test.mjs
T

106 lines
6.5 KiB
JavaScript
Raw Normal View History

2026-10-27 17:44:41 +08:00
import test from 'node:test'
import assert from 'node:assert/strict'
import { openFixedActor, requireFixedActors } from './fixed-test-actors.mjs'
import { AcceptanceRun } from '../admin-test-env-acceptance.mjs'
import { readFileSync } from 'node:fs'
function fixture(fault = '') {
const calls = [], actor = { membershipId: '101', roleId: '201', roleCode: 'EXISTING_READER', username: 'existing-reader', password: 'private-fixed-password' }
const context = { token: 'admin', options: { fixedActors: { reader: actor } }, async request(method, path, body, token) {
calls.push({ method, path, body, token })
let data
if (path.endsWith('/roles')) data = [{ id: '201', roleCode: actor.roleCode, status: fault === 'inactive role' ? 'DISABLED' : 'ACTIVE' }]
else if (path.includes('/users?')) {
const keyword = new URL(path, 'https://fixture.invalid').searchParams.get('keyword')
// The service does not search login names; its keyword filters profile fields/member numbers.
assert.equal(keyword, null)
data = { page: 1, size: 100, total: 1, records: [{ membershipId: '101', username: actor.username,
tenantId: fault === 'foreign user' ? '9' : '0', status: fault === 'inactive user' ? 'DISABLED' : 'ACTIVE', roleIds: fault === 'extra role' ? ['201', '999'] : ['201'] }] }
}
else if (path.endsWith('/login')) data = { accessToken: 'reader-session', refreshToken: 'reader-refresh' }
else if (path.endsWith('/authorization/permissions')) data = fault === 'extra permissions' ? ['read', 'write'] : ['read']
else if (path.endsWith('/logout')) return { http: 200, body: { code: fault === 'logout fails' ? 'S0503' : '200' } }
else assert.fail(path)
return { http: 200, body: { code: '200', data } }
} }
return { context, calls, actor }
}
test('repeated runs reuse the same configured membership and role, and logout is idempotent', async () => {
const { context, calls } = fixture()
for (let run = 0; run < 3; run++) {
const actor = await openFixedActor(context, 'reader', ['read'])
assert.equal(actor.membershipId, '101'); assert.equal(actor.roleId, '201'); assert.equal(actor.reused, true)
assert.equal(actor.password, undefined)
await actor.close(); const count = calls.length; await actor.close(); assert.equal(calls.length, count)
}
assert.ok(calls.every(call => call.method === 'GET' || ['/api/auth/login', '/api/auth/logout'].includes(call.path)))
assert.equal(calls.filter(call => call.path.endsWith('/logout')).length, 3)
})
for (const fault of ['inactive role', 'foreign user', 'inactive user', 'extra role', 'extra permissions']) test(`fixed actor rejects ${fault} without changing resources`, async () => {
const { context, calls } = fixture(fault)
await assert.rejects(openFixedActor(context, 'reader', ['read']), /不匹配/)
assert.equal(calls.some(call => ['PUT', 'PATCH', 'DELETE'].includes(call.method)), false)
assert.equal(calls.some(call => call.method === 'POST' && !call.path.startsWith('/api/auth/')), false)
if (fault === 'extra permissions') assert.equal(calls.at(-1).path, '/api/auth/logout')
})
test('missing configuration or reusing one account for two isolated actors stops before HTTP', async () => {
const { context, actor, calls } = fixture()
await assert.rejects(openFixedActor(context, 'missing', []), /禁止自动创建/)
assert.throws(() => requireFixedActors({ fixedActors: { a: actor, b: actor } }, ['a', 'b']), /不同的固定账号/)
assert.equal(calls.length, 0)
})
test('membership lookup finds a configured user on later pages without treating the login name as a searchable profile field', async () => {
const { context, calls } = fixture(), original = context.request
context.request = async (method, path, ...args) => {
if (path.includes('/users?')) {
const url = new URL(path, 'https://fixture.invalid')
assert.equal(url.searchParams.has('keyword'), false)
if (url.searchParams.get('page') === '1') return { http: 200, body: { code: '200', data: {
page: 1, size: 100, total: 101, records: Array.from({ length: 100 }, (_, index) => ({ membershipId: String(1000 + index), username: 'unrelated' })) } } }
const response = await original(method, path, ...args)
response.body.data.page = 2; response.body.data.total = 101
return response
}
return original(method, path, ...args)
}
const actor = await openFixedActor(context, 'reader', ['read'])
assert.equal(actor.membershipId, '101'); await actor.close()
assert.equal(calls.some(call => call.path.includes('page=2')), true)
})
test('logout failures remain visible and can be checked again without creating accounts', async () => {
const { context, calls } = fixture('logout fails')
const actor = await openFixedActor(context, 'reader', ['read'])
await assert.rejects(actor.close(), /退出未确认/)
assert.equal(calls.filter(call => call.path.endsWith('/logout')).length, 1)
})
test('all Admin HTTP transports reject creation of roles, users, permissions and suppliers regardless of environment', async () => {
for (const environment of [undefined, 'test', 'dev', 'prod']) {
const context = new AcceptanceRun({ environment })
for (const path of ['/api/platform/v1/iam/roles', '/api/platform/v1/iam/users', '/api/platform/v1/iam/permissions', '/api/platform/v1/suppliers?tenantId=0'])
for (const entry of ['request', 'requestWithBody', 'requestMultipart'])
await assert.rejects(context[entry]('POST', path, new FormData()), /禁止新增角色/)
}
})
test('local transport and F33 handoff have no master provisioning paths', () => {
for (const file of ['local-transfer-pickup-acceptance.mjs', 'local-dispatch-transport-acceptance.mjs', 'fba-remaining-nodes-acceptance-pda-operator.mjs']) {
const source = readFileSync(new URL(`../${file}`, import.meta.url), 'utf8')
assert.doesNotMatch(source, /['"]POST['"]\s*,\s*['"](?:\/api)?\/platform\/v1\/iam\/(roles|users|permissions)['"]/, file)
assert.doesNotMatch(source, /['"]PUT['"]\s*,\s*['"](?:\/api)?\/platform\/v1\/iam\/roles\/permissions['"]/, file)
}
})
test('report security scans fixed actor passwords even if hidden under ordinary evidence keys', async () => {
const context = new AcceptanceRun({ environment: 'test', fixedCustomerId: '123', quiet: true, modules: [], fixedActors: { reader: { password: 'fixed-sensitive-password' } } })
context.request = async () => ({ http: 200, body: { code: 'A0401' } })
context.createdResources.evidence = { note: 'fixed-sensitive-password' }
const report = await context.run()
assert.equal(report.steps.find(step => step.module === 'report-security').passed, false)
})