Files
CASAN/packages/casan-control-panel/backend/test/settings.test.ts
T
2026-07-08 19:07:35 +09:00

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);
});
});