/** * Tests for the shared jail-exemption collector (per-module-jail-policy * task 3.1-3.3). * * Every reporting surface — `module list`, `system doctor`, `system audit` — * builds its view from this one function, so these tests are what stops the * three from disagreeing about what an exemption is. Isolation mirrors * module-jail.test.ts: CELILO_DB_PATH / CELILO_DATA_DIR are set before the * SUT is imported. */ import { afterAll, beforeEach, describe, expect, test } from 'bun:test'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { eq } from 'drizzle-orm'; const testRoot = mkdtempSync(join(tmpdir(), 'celilo-jail-exemptions-')); process.env.CELILO_DB_PATH = join(testRoot, 'celilo.db'); process.env.CELILO_DATA_DIR = join(testRoot, 'data'); const { getDb, closeDb } = await import('../db/client'); const { moduleJailPolicies, modules, systemConfig } = await import('../db/schema'); const { collectJailExemptions } = await import('./jail-exemptions'); function insertModule(id: string): void { getDb() .insert(modules) .values({ id, name: id, sourcePath: `/tmp/${id}`, version: '1.0.0', manifestData: {} }) .run(); } function setModulePolicy(moduleId: string, policy: string): void { const typed = policy as 'auto' | 'off' | 'required'; getDb() .insert(moduleJailPolicies) .values({ moduleId, policy: typed, updatedAt: new Date() }) .onConflictDoUpdate({ target: moduleJailPolicies.moduleId, set: { policy: typed, updatedAt: new Date() }, }) .run(); } function setSystemPolicy(value: string | undefined): void { const db = getDb(); if (value === undefined) { db.delete(systemConfig).where(eq(systemConfig.key, 'hooks.jail_policy')).run(); } else { db.insert(systemConfig) .values({ key: 'hooks.jail_policy', value }) .onConflictDoUpdate({ target: systemConfig.key, set: { value } }) .run(); } } afterAll(() => { closeDb(); rmSync(testRoot, { recursive: true, force: true }); }); describe('collectJailExemptions (per-module-jail-policy task 3.4)', () => { beforeEach(() => { const db = getDb(); db.delete(moduleJailPolicies).run(); db.delete(systemConfig).where(eq(systemConfig.key, 'hooks.jail_policy')).run(); db.delete(modules).run(); insertModule('strictmod'); insertModule('exemptmod'); }); test('no per-module rows produce no exemptions', () => { expect(collectJailExemptions(getDb())).toEqual([]); }); test("a module policy stronger than the system's is not an exemption", () => { // The fleet default is `off`, so `auto` STRENGTHENS the posture. Reporting // it as an exemption would be a surface reporting an exemption nobody set. setModulePolicy('strictmod', 'required'); expect(collectJailExemptions(getDb())).toEqual([]); }); test("a module policy equal to the system's is not an exemption", () => { setSystemPolicy('auto'); setModulePolicy('strictmod', 'auto'); expect(collectJailExemptions(getDb())).toEqual([]); }); test("a module policy weaker than the system's is reported, with both values", () => { setSystemPolicy('auto'); setModulePolicy('exemptmod', 'off'); expect(collectJailExemptions(getDb())).toEqual([ { moduleId: 'exemptmod', policy: 'off', systemPolicy: 'auto' }, ]); }); test('a weaker exemption against the unset-system default (off) is impossible', () => { // off is the floor; nothing can be weaker than it. This is the negative // half of task 3.4: with nobody having set the system key, no module row // — whatever it holds — may read as an exemption. setModulePolicy('exemptmod', 'off'); expect(collectJailExemptions(getDb())).toEqual([]); }); test('two exemptions are both reported', () => { insertModule('exemptmod2'); setSystemPolicy('required'); setModulePolicy('exemptmod', 'auto'); setModulePolicy('exemptmod2', 'off'); const exemptions = collectJailExemptions(getDb()); expect(exemptions).toHaveLength(2); expect(exemptions.map((e) => e.moduleId).sort()).toEqual(['exemptmod', 'exemptmod2']); }); test('a stored module policy outside the accepted set throws, not coerces', () => { setModulePolicy('exemptmod', 'sometimes'); expect(() => collectJailExemptions(getDb())).toThrow(/not a hook jail policy/); }); test('a stored system policy outside the accepted set throws, naming the fix', () => { setSystemPolicy('jail-harder'); setModulePolicy('exemptmod', 'off'); expect(() => collectJailExemptions(getDb())).toThrow(/hooks\.jail_policy='jail-harder'/); }); });