81 lines
3.0 KiB
TypeScript
81 lines
3.0 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { existsSync, mkdtempSync, readFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { ForbiddenException } from '@nestjs/common';
|
|
import { SettingsService } from '../src/settings/settings.service.js';
|
|
|
|
const viewer = { actor: 'viewer-1', role: 'viewer', project: 'default', tenant: 'default' };
|
|
const admin = { actor: 'admin-1', role: 'org-admin', project: 'default', tenant: 'default' };
|
|
|
|
function withTempStore(fn: (storeFile: string) => void) {
|
|
const prev = process.env.CASAN_CP_STORE_FILE;
|
|
const storeFile = join(mkdtempSync(join(tmpdir(), 'cp-settings-')), 'settings.json');
|
|
process.env.CASAN_CP_STORE_FILE = storeFile;
|
|
try {
|
|
fn(storeFile);
|
|
} finally {
|
|
if (prev === undefined) delete process.env.CASAN_CP_STORE_FILE;
|
|
else process.env.CASAN_CP_STORE_FILE = prev;
|
|
}
|
|
}
|
|
|
|
test('settings list is readable by viewer and exposes policy/capabilities', () => {
|
|
withTempStore(() => {
|
|
const svc = new SettingsService();
|
|
const res = svc.list(viewer) as any;
|
|
assert.ok(res.policy['compression.enabled']);
|
|
assert.equal(res.capabilities.can_write_standard, false);
|
|
assert.equal(res.capabilities.can_write_sensitive, false);
|
|
});
|
|
});
|
|
|
|
test('viewer cannot write settings', () => {
|
|
withTempStore(() => {
|
|
const svc = new SettingsService();
|
|
assert.throws(
|
|
() => svc.set({ key: 'compression.enabled', value: true, reason: 'test viewer deny' }, viewer),
|
|
ForbiddenException,
|
|
);
|
|
});
|
|
});
|
|
|
|
test('org-admin writes and rolls back through governed store with audit', () => {
|
|
withTempStore((storeFile) => {
|
|
const svc = new SettingsService();
|
|
const first = svc.set({ key: 'compression.enabled', value: true, reason: 'enable compression' }, admin) as any;
|
|
const second = svc.set({ key: 'compression.enabled', value: false, reason: 'disable compression' }, admin) as any;
|
|
const rolled = svc.rollback({ key: 'compression.enabled', reason: 'rollback compression' }, admin) as any;
|
|
|
|
assert.equal(first.setting.version, 1);
|
|
assert.equal(second.setting.version, 2);
|
|
assert.equal(rolled.setting.version, 3);
|
|
assert.equal(rolled.setting.value, true);
|
|
assert.equal(rolled.audit_verify.ok, true);
|
|
assert.ok(existsSync(storeFile));
|
|
|
|
const raw = JSON.parse(readFileSync(storeFile, 'utf8'));
|
|
assert.equal(raw.audit.length, 3);
|
|
assert.equal(raw.audit[0].action, 'set');
|
|
assert.equal(raw.audit[2].action, 'rollback');
|
|
});
|
|
});
|
|
|
|
test('sensitive setting requires approval even for org-admin in dev gate', () => {
|
|
withTempStore(() => {
|
|
const svc = new SettingsService();
|
|
assert.throws(
|
|
() => svc.set({ key: 'security.strict', value: false, reason: 'loosen strict mode' }, admin),
|
|
ForbiddenException,
|
|
);
|
|
const res = svc.set({
|
|
key: 'security.strict',
|
|
value: true,
|
|
reason: 'tighten strict mode with approval token',
|
|
approval: 'approved-in-dev',
|
|
}, admin) as any;
|
|
assert.equal(res.setting.value, true);
|
|
});
|
|
});
|