补充财务主体缺失阻断诊断

This commit is contained in:
2026-08-12 13:55:16 +08:00
parent 49967bbc28
commit 4375beaa13
2 changed files with 27 additions and 1 deletions
@@ -18,7 +18,7 @@ function ok(data) {
return { http: 200, body: { code: '200', data }, requestId: 'request-test', traceId: 'trace-test' }
}
function fakeContext(withFixture = true, permissions = financePermissions, denyFinanceWrites = false) {
function fakeContext(withFixture = true, permissions = financePermissions, denyFinanceWrites = false, missingSubject = false) {
const marker = 'FIN-TEST-MARKER'
const receivables = [
{ feeSourceId: '101', orderId: '201', direction: 'RECEIVABLE', currency: 'CNY', businessDate: '2026-08-10' },
@@ -85,6 +85,8 @@ function fakeContext(withFixture = true, permissions = financePermissions, denyF
}
if (method === 'POST' && requestPath.endsWith('/statements')) {
if (body.currency !== 'CNY') return { http: 200, body: { code: 'FIN_400' } }
if (missingSubject) return { http: 200, body: { code: 'FIN_409', message: '平台财务主体未配置' },
requestId: 'request-subject', traceId: 'trace-subject' }
const selected = body.generationMode === 'BY_CUTOFF'
? receivables.filter((item) => item.businessDate >= body.periodStart && item.businessDate <= body.periodEnd)
: feeSources.filter((item) => body.feeSourceIds.includes(item.feeSourceId))
@@ -146,6 +148,7 @@ test('静态契约覆盖日期、SELECTED、三 AR、AP、状态机和数组输
assert.match(source, /\/api\/platform\/v1\/iam\/authorization\/permissions/)
assert.match(source, /rejectedWith\(currencyResponse, 'FIN_400'\)/)
assert.match(source, /stale-finance-token-authorities/)
assert.match(source, /missing-active-finance-subject/)
assert.match(source, /businessDates\.size < 2/)
assert.match(source, /receivables\.length < 4/)
assert.match(source, /const groups = \[cutoffSources/)
@@ -225,3 +228,15 @@ test('权限快照已授权但 Finance 返回 A0403 时立即停止并保留诊
assert.equal(context.calls.some((call) => call.body?.generationMode === 'SELECTED'
&& call.body?.currency === 'CNY'), false)
})
test('平台财务主体缺失时报明确 blocker 且不继续状态流转', async () => {
const context = fakeContext(true, financePermissions, false, true)
await run(context)
assert.equal(context.coverageGaps.some((gap) => gap.category === 'missing-active-finance-subject'
&& gap.reason.includes('requestId=request-subject') && gap.reason.includes('traceId=trace-subject')), true)
assert.equal(context.calls.filter((call) => call.method === 'POST'
&& call.path.endsWith('/statements')).length, 2)
assert.equal(context.calls.some((call) => call.path.includes('/actions')
|| call.path.endsWith('/receivable-settlements') || call.path.endsWith('/payable-settlements')), false)
assert.equal(context.steps.some((step) => step.name === '第 1 张对账单出账'), false)
})
@@ -29,6 +29,10 @@ function rejectedWith(response, code) {
return response?.http === 200 && String(response?.body?.code) === code
}
function accessDenied(response) { return rejectedWith(response, 'A0403') }
function financeSubjectMissing(response) {
return rejectedWith(response, 'FIN_409')
&& String(response?.body?.message ?? '').includes('平台财务主体未配置')
}
function sameIds(actual, expected) {
const left = [...new Set(actual)].sort()
const right = [...new Set(expected)].sort()
@@ -301,6 +305,13 @@ export async function run(context) {
finish(context, start)
return
}
if (financeSubjectMissing(created.response)) {
blocker(context, '平台财务主体 Fixture 门禁',
`POST ${BASE}/statements 返回 FIN_409 平台财务主体未配置;sourceIds=${created.body.feeSourceIds.join(',')}, requestId=${created.response.requestId ?? 'null'}, traceId=${created.response.traceId ?? 'null'};需在测试库 fin_subject 中提供 ACTIVE 主体`,
'missing-active-finance-subject', created.response)
finish(context, start)
return
}
context.assert(MODULE, `第 ${index + 1} 张仅使用分组来源`, created.response,
created.statement && sameIds(lineSourceIds(created.statement), groups[index].map((source) => source.feeSourceId)),
`expected=${groups[index].length}, actual=${lineSourceIds(created.statement).length}`, OPTIONS)