c670b9e454
- 确定性领域引擎(分类/评分/分级/红线/费用/裁决)+LLM(通义千问)语言理解 - 6步评估向导、服务端草稿持久化(跨设备/编辑草稿保护) - 工作流(草稿→风控→管理层)、RBAC、报告导出、校准、客户/费率/红线/最低工资管理 - 专业图标体系替换全部emoji、看板美化 - 生产化:API_BASE可配置(同源反代)、auth密钥惰性读取修复RBAC - 444单测+204前端测试+51 e2e
99 lines
3.6 KiB
TypeScript
99 lines
3.6 KiB
TypeScript
/**
|
||
* Property 45: 非管理员配置修改一律拒绝且配置不变 的属性化测试(RBAC,Req 12.1, 12.3)。
|
||
*
|
||
* 属性陈述:对任意非 Administrator 角色(含 Assessor、未认证、未授权)发起的
|
||
* Risk_Model 配置修改请求,System 必拒绝该请求(status='rejected')、保持当前配置
|
||
* 不变(返回与请求中 currentConfig 同一引用且值相等),并返回权限不足错误
|
||
* (AuthorizationError,userMessage 含「权限不足」)。
|
||
*
|
||
* 测试中 `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 },
|
||
);
|
||
});
|
||
});
|