外包风险评估系统:领域引擎+前端+服务端持久化与生产部署

- 确定性领域引擎(分类/评分/分级/红线/费用/裁决)+LLM(通义千问)语言理解
- 6步评估向导、服务端草稿持久化(跨设备/编辑草稿保护)
- 工作流(草稿→风控→管理层)、RBAC、报告导出、校准、客户/费率/红线/最低工资管理
- 专业图标体系替换全部emoji、看板美化
- 生产化:API_BASE可配置(同源反代)、auth密钥惰性读取修复RBAC
- 444单测+204前端测试+51 e2e
This commit is contained in:
freedakgmail
2026-06-13 01:06:39 +08:00
commit c670b9e454
404 changed files with 61820 additions and 0 deletions
@@ -0,0 +1,98 @@
/**
* Property 45: 非管理员配置修改一律拒绝且配置不变 的属性化测试(RBAC,Req 12.1, 12.3)。
*
* 属性陈述:对任意非 Administrator 角色(含 Assessor、未认证、未授权)发起的
* Risk_Model 配置修改请求,System 必拒绝该请求(status='rejected')、保持当前配置
* 不变(返回与请求中 currentConfig 同一引用且值相等),并返回权限不足错误
* AuthorizationErroruserMessage 含「权限不足」)。
*
* 测试中 `apply` 故意产出一个不同于当前配置的新值;若拒绝逻辑被绕过而调用了
* `apply`,返回的配置将发生变化,断言即会失败——以此真实校验"配置不变"。
*
* Feature: outsourcing-risk-assessment, Property 45: 非管理员配置修改一律拒绝且配置不变
* Validates: Requirements 12.1, 12.3
*/
import { describe, expect, it } from 'vitest';
import fc from 'fast-check';
import {
applyConfigChange,
AuthorizationError,
InMemoryConfigAuditStore,
type Actor,
type AuthRole,
} from '../index.js';
// 非 Administrator 角色全集(Req 12.1, 12.3)。
const NON_ADMIN_ROLES = ['Assessor', '未认证', '未授权'] as const satisfies readonly AuthRole[];
const nonAdminActorArb: fc.Arbitrary<Actor> = fc.record({
id: fc.string({ minLength: 0, maxLength: 12 }),
role: fc.constantFrom<AuthRole>(...NON_ADMIN_ROLES),
});
/** 任意配置值(以简单可比较对象代表 Risk_Model 配置形态)。 */
interface TestConfig {
readonly version: number;
readonly name: string;
}
const configArb: fc.Arbitrary<TestConfig> = fc.record({
version: fc.integer({ min: 0, max: 1_000 }),
name: fc.string({ minLength: 0, maxLength: 10 }),
});
const changedKeysArb: fc.Arbitrary<string[]> = fc.array(
fc.string({ minLength: 1, maxLength: 8 }),
{ minLength: 0, maxLength: 4 },
);
describe('Property 45: 非管理员配置修改一律拒绝且配置不变 (Req 12.1, 12.3)', () => {
it('非 Administrator 请求被拒绝、配置不变并返回权限不足错误', () => {
fc.assert(
fc.property(
nonAdminActorArb,
configArb,
changedKeysArb,
(actor, currentConfig, changedConfigKeys) => {
const auditStore = new InMemoryConfigAuditStore();
let applyCalled = false;
const result = applyConfigChange<TestConfig>(
{
actor,
currentConfig,
changedConfigKeys,
// 故意产出与当前配置不同的值:若被调用,配置必然改变。
apply: (current) => {
applyCalled = true;
return { version: current.version + 1, name: `${current.name}!` };
},
},
auditStore,
);
// 1) 一律拒绝(Req 12.1, 12.3)。
expect(result.status).toBe('rejected');
// 2) 鉴权失败时绝不调用 apply(保证配置不会被改动)。
expect(applyCalled).toBe(false);
// 3) 配置不变:同一引用且值相等(Req 12.1, 12.3)。
expect(result.config).toBe(currentConfig);
expect(result.config).toEqual(currentConfig);
// 4) 返回权限不足错误(Req 12.1, 12.3)。
if (result.status === 'rejected') {
expect(result.error).toBeInstanceOf(AuthorizationError);
expect(result.error.userMessage).toContain('权限不足');
expect(result.error.requiredRole).toBe('Administrator');
expect(result.error.actualRole).toBe(actor.role);
expect(result.error.actorId).toBe(actor.id);
}
},
),
{ numRuns: 100 },
);
});
});