import os from pathlib import Path import sqlite3 import sys import tempfile import unittest from unittest import mock ROOT = Path(__file__).resolve().parents[1] APP_DIR = ROOT / 'app' sys.path.insert(0, str(APP_DIR)) import scanner_db from scanner_db import ScannerDB class OperationsSchemaTests(unittest.TestCase): def setUp(self): self.environment = mock.patch.dict( os.environ, {'SCANNER_DB_URL': '', 'DATABASE_URL': ''}, ) self.environment.start() self.temp = tempfile.TemporaryDirectory() self.db = ScannerDB(db_path=os.path.join(self.temp.name, 'scanner.db')) def tearDown(self): self.db.close() self.temp.cleanup() self.environment.stop() def test_fresh_schema_has_operations_authority_and_migration(self): self.assertEqual( scanner_db.PIPELINE_MIGRATION_VERSIONS[-1], scanner_db.REMOTE_ASSIGNMENT_CAPACITY_MIGRATION, ) self.assertEqual(len(scanner_db.PIPELINE_MIGRATION_VERSIONS), 33) for table in ( 'runtime_operations', 'runtime_operations_control', 'runtime_audit_events', ): with self.subTest(table=table): self.assertEqual( set(self.db.conn.table_columns(table)), set(scanner_db.RUNTIME_TABLE_SPECS[table]), ) control = self.db.conn.execute( 'SELECT * FROM runtime_operations_control WHERE id = 1' ).fetchone() self.assertEqual(int(control['revision']), 0) self.assertEqual(int(control['discovery_paused']), 0) self.assertEqual(int(control['dispatch_paused']), 0) self.assertEqual(control['drain_state'], 'normal') self.assertEqual(control['actor'], 'system:migration') marker = self.db.conn.execute( 'SELECT version FROM runtime_schema_migrations WHERE version = ?', (scanner_db.OPERATIONS_CONTROL_MIGRATION,), ).fetchone() self.assertIsNotNone(marker) self.assertTrue(self.db.runtime_safety_schema_available(), self.db.last_error) self.assertTrue(self.db.pipeline_schema_available(), self.db.last_error) def test_operations_authority_invariants_fail_closed(self): mutations = ( ( 'DELETE FROM runtime_operations_control WHERE id = 1', 'runtime_operations_control singleton row', ), ( 'DROP INDEX idx_runtime_operations_status_updated', 'index idx_runtime_operations_status_updated', ), ( 'DROP TRIGGER runtime_audit_events_reject_delete', 'trigger runtime_audit_events_reject_delete', ), ) for sql, expected in mutations: with self.subTest(sql=sql): self.db.conn.execute(sql) self.db.conn.commit() self.assertFalse(self.db.runtime_safety_schema_available()) self.assertIn(expected, self.db.last_error) with self.assertRaisesRegex( scanner_db.RuntimeSafetySchemaError, expected, ): self.db.require_runtime_safety_schema() self.db.initialize_schema() self.assertTrue(self.db.runtime_safety_schema_available(), self.db.last_error) def test_pipeline_schema_requires_operations_authority_indexes(self): self.db.conn.execute('DROP INDEX idx_runtime_audit_events_operation') self.db.conn.commit() self.assertFalse(self.db.pipeline_schema_available()) self.assertIn('index idx_runtime_audit_events_operation', self.db.last_error) def test_initialize_schema_repairs_malformed_audit_trigger(self): self.db.conn.execute('DROP TRIGGER runtime_audit_events_reject_update') self.db.conn.execute(''' CREATE TRIGGER runtime_audit_events_reject_update AFTER INSERT ON runtime_audit_events BEGIN SELECT 1; END ''') self.db.conn.commit() self.assertFalse(self.db.runtime_safety_schema_available()) self.assertIn('trigger runtime_audit_events_reject_update', self.db.last_error) self.db.initialize_schema() self.assertTrue(self.db.runtime_safety_schema_available(), self.db.last_error) trigger = self.db.conn.table_triggers('runtime_audit_events')[ 'runtime_audit_events_reject_update' ] self.assertIn('BEFORE UPDATE', trigger['sql'].upper()) def test_control_seed_is_idempotent(self): self.db.conn.execute( '''UPDATE runtime_operations_control SET revision = 7, discovery_paused = 1, actor = 'operator', updated_at = ? WHERE id = 1''', (scanner_db.utc_now_iso(),), ) self.db.conn.commit() self.db.initialize_schema() control = self.db.conn.execute( 'SELECT revision, discovery_paused, actor FROM runtime_operations_control WHERE id = 1' ).fetchone() self.assertEqual(dict(control), { 'revision': 7, 'discovery_paused': 1, 'actor': 'operator', }) def test_audit_events_are_append_only(self): self.db.conn.execute( '''INSERT INTO runtime_audit_events( actor, action, target_kind, target_ref, result, event_sha256, created_at ) VALUES (?, ?, ?, ?, ?, ?, ?)''', ( 'operator', 'control-update', 'runtime-control', '', 'accepted', 'a' * 64, scanner_db.utc_now_iso(), ), ) self.db.conn.commit() with self.assertRaisesRegex(sqlite3.DatabaseError, 'append-only'): self.db.conn.execute( "UPDATE runtime_audit_events SET result = 'succeeded' WHERE id = 1" ) self.db.conn.rollback() with self.assertRaisesRegex(sqlite3.DatabaseError, 'append-only'): self.db.conn.execute('DELETE FROM runtime_audit_events WHERE id = 1') self.db.conn.rollback() count = self.db.conn.execute( 'SELECT COUNT(*) AS count FROM runtime_audit_events' ).fetchone() self.assertEqual(int(count['count']), 1) if __name__ == '__main__': unittest.main()