/** * 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 = fc.record({ id: fc.string({ minLength: 0, maxLength: 12 }), role: fc.constantFrom(...NON_ADMIN_ROLES), }); /** 任意配置值(以简单可比较对象代表 Risk_Model 配置形态)。 */ interface TestConfig { readonly version: number; readonly name: string; } const configArb: fc.Arbitrary = fc.record({ version: fc.integer({ min: 0, max: 1_000 }), name: fc.string({ minLength: 0, maxLength: 10 }), }); const changedKeysArb: fc.Arbitrary = 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( { 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 }, ); }); });