Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
197 lines
6.8 KiB
JavaScript
197 lines
6.8 KiB
JavaScript
// ADR-0011 · Phase E2 · Integration smoke for the atom-write-path hook.
|
|
//
|
|
// Mocks dbGet (so no real DB hit) and exercises the conditional branches:
|
|
// - non-atoms_v2 table → no-op
|
|
// - atoms_v2 row without semantic_type → no-op
|
|
// - atoms_v2 + verification_settings + flag-off → no-op
|
|
// - flag-on + missing column_id → 400
|
|
// - flag-on + non-verification column → 403
|
|
// - flag-on + tightening override → 200
|
|
// - flag-on + loosening override → 400 with field
|
|
|
|
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
|
|
const dbGetMock = vi.fn();
|
|
|
|
vi.mock('../../../database/connection.js', () => ({
|
|
dbGet: (...args) => dbGetMock(...args),
|
|
}));
|
|
|
|
const { validateVerificationSettingsAtom } = await import('../applyOverrideValidator.js');
|
|
const { ATOMS_V2_TABLE_ID } = await import('../../atoms-archive.js');
|
|
|
|
const baseConfig = {
|
|
available_methods: ['totp', 'captcha'],
|
|
required_methods: 1,
|
|
method: 'totp',
|
|
cooldown_seconds: 300,
|
|
cooldown_ms: 300000,
|
|
ttl_seconds: 3600,
|
|
ttl_ms: 3600000,
|
|
locks_on_statuses: [],
|
|
unlocks_on_statuses: [],
|
|
guards: ['row_update_guard'],
|
|
policy: 'any_n',
|
|
rate_limit: null,
|
|
method_config: {},
|
|
};
|
|
|
|
beforeEach(() => {
|
|
dbGetMock.mockReset();
|
|
});
|
|
|
|
afterEach(() => {
|
|
delete process.env.VERIFICATION_COLUMN_ENABLED;
|
|
});
|
|
|
|
describe('validateVerificationSettingsAtom — short-circuits', () => {
|
|
it('non-atoms_v2 table → ok (no DB hit)', async () => {
|
|
const r = await validateVerificationSettingsAtom({
|
|
tableId: 999,
|
|
data: { semantic_type: 'verification_settings', column_id: 1, override: { cooldown_seconds: 60 } },
|
|
});
|
|
expect(r).toEqual({ ok: true });
|
|
expect(dbGetMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('atoms_v2 + non-verification atom → ok', async () => {
|
|
const r = await validateVerificationSettingsAtom({
|
|
tableId: ATOMS_V2_TABLE_ID,
|
|
data: { semantic_type: 'widget_atom', widget_ref: 218 },
|
|
});
|
|
expect(r).toEqual({ ok: true });
|
|
expect(dbGetMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('flag off → ok regardless of payload', async () => {
|
|
delete process.env.VERIFICATION_COLUMN_ENABLED;
|
|
const r = await validateVerificationSettingsAtom({
|
|
tableId: ATOMS_V2_TABLE_ID,
|
|
data: { semantic_type: 'verification_settings', column_id: 1, override: { cooldown_seconds: 1 } },
|
|
});
|
|
expect(r).toEqual({ ok: true });
|
|
expect(dbGetMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('null/empty data → ok', async () => {
|
|
expect((await validateVerificationSettingsAtom({ tableId: ATOMS_V2_TABLE_ID, data: null })).ok).toBe(true);
|
|
expect((await validateVerificationSettingsAtom({ tableId: ATOMS_V2_TABLE_ID, data: 'string' })).ok).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('validateVerificationSettingsAtom — flag-on validation', () => {
|
|
beforeEach(() => {
|
|
process.env.VERIFICATION_COLUMN_ENABLED = 'true';
|
|
});
|
|
|
|
it('missing column_id → 400', async () => {
|
|
const r = await validateVerificationSettingsAtom({
|
|
tableId: ATOMS_V2_TABLE_ID,
|
|
data: { semantic_type: 'verification_settings', override: {} },
|
|
});
|
|
expect(r.ok).toBe(false);
|
|
expect(r.status).toBe(400);
|
|
expect(r.field).toBe('column_id');
|
|
});
|
|
|
|
it('column not found → 400', async () => {
|
|
dbGetMock.mockResolvedValueOnce(null);
|
|
const r = await validateVerificationSettingsAtom({
|
|
tableId: ATOMS_V2_TABLE_ID,
|
|
data: { semantic_type: 'verification_settings', column_id: 42, override: { cooldown_seconds: 600 } },
|
|
});
|
|
expect(r.ok).toBe(false);
|
|
expect(r.status).toBe(400);
|
|
expect(r.field).toBe('column_id');
|
|
});
|
|
|
|
it('non-verification column → 403', async () => {
|
|
dbGetMock.mockResolvedValueOnce({ id: 99, type: 'text', config: '{}' });
|
|
const r = await validateVerificationSettingsAtom({
|
|
tableId: ATOMS_V2_TABLE_ID,
|
|
data: { semantic_type: 'verification_settings', column_id: 99, override: {} },
|
|
});
|
|
expect(r.ok).toBe(false);
|
|
expect(r.status).toBe(403);
|
|
expect(r.field).toBe('column_id');
|
|
});
|
|
|
|
it('malformed base config → 400', async () => {
|
|
dbGetMock.mockResolvedValueOnce({ id: 5, type: 'verification', config: 'not-json' });
|
|
const r = await validateVerificationSettingsAtom({
|
|
tableId: ATOMS_V2_TABLE_ID,
|
|
data: { semantic_type: 'verification_settings', column_id: 5, override: { cooldown_seconds: 600 } },
|
|
});
|
|
expect(r.ok).toBe(false);
|
|
expect(r.status).toBe(400);
|
|
});
|
|
|
|
it('null override → ok (no-op)', async () => {
|
|
const r = await validateVerificationSettingsAtom({
|
|
tableId: ATOMS_V2_TABLE_ID,
|
|
data: { semantic_type: 'verification_settings', column_id: 5, override: null },
|
|
});
|
|
expect(r.ok).toBe(true);
|
|
expect(dbGetMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('non-object override → 400', async () => {
|
|
const r = await validateVerificationSettingsAtom({
|
|
tableId: ATOMS_V2_TABLE_ID,
|
|
data: { semantic_type: 'verification_settings', column_id: 5, override: 'tighten please' },
|
|
});
|
|
expect(r.ok).toBe(false);
|
|
expect(r.field).toBe('override');
|
|
});
|
|
|
|
it('tightening override → ok', async () => {
|
|
dbGetMock.mockResolvedValueOnce({ id: 5, type: 'verification', config: JSON.stringify(baseConfig) });
|
|
const r = await validateVerificationSettingsAtom({
|
|
tableId: ATOMS_V2_TABLE_ID,
|
|
data: {
|
|
semantic_type: 'verification_settings',
|
|
column_id: 5,
|
|
override: { cooldown_seconds: 600, guards: ['row_update_guard', 'extra'] },
|
|
},
|
|
});
|
|
expect(r).toEqual({ ok: true });
|
|
});
|
|
|
|
it('loosening override (lower cooldown) → 400', async () => {
|
|
dbGetMock.mockResolvedValueOnce({ id: 5, type: 'verification', config: JSON.stringify(baseConfig) });
|
|
const r = await validateVerificationSettingsAtom({
|
|
tableId: ATOMS_V2_TABLE_ID,
|
|
data: {
|
|
semantic_type: 'verification_settings',
|
|
column_id: 5,
|
|
override: { cooldown_seconds: 60 },
|
|
},
|
|
});
|
|
expect(r.ok).toBe(false);
|
|
expect(r.status).toBe(400);
|
|
expect(r.field).toBe('cooldown_seconds');
|
|
});
|
|
|
|
it('loosening override (drops guard) → 400 with field=guards', async () => {
|
|
dbGetMock.mockResolvedValueOnce({ id: 5, type: 'verification', config: JSON.stringify(baseConfig) });
|
|
const r = await validateVerificationSettingsAtom({
|
|
tableId: ATOMS_V2_TABLE_ID,
|
|
data: {
|
|
semantic_type: 'verification_settings',
|
|
column_id: 5,
|
|
override: { guards: [] },
|
|
},
|
|
});
|
|
expect(r.ok).toBe(false);
|
|
expect(r.field).toBe('guards');
|
|
});
|
|
|
|
it('config returned as object (PG JSONB) → handled', async () => {
|
|
dbGetMock.mockResolvedValueOnce({ id: 5, type: 'verification', config: baseConfig });
|
|
const r = await validateVerificationSettingsAtom({
|
|
tableId: ATOMS_V2_TABLE_ID,
|
|
data: { semantic_type: 'verification_settings', column_id: 5, override: { cooldown_seconds: 600 } },
|
|
});
|
|
expect(r).toEqual({ ok: true });
|
|
});
|
|
});
|