"""Tests for the database module.""" import sqlite3 import pytest from database import close_connection, get_connection, init_db, set_db_path @pytest.fixture(autouse=True) def setup_db(db_path): """Initialize a fresh database for each test.""" set_db_path(db_path) init_db() yield close_connection() class TestTableCreation: """Tests for database schema initialization.""" def test_known_objects_table_exists(self): conn = get_connection() cursor = conn.execute( "SELECT name FROM sqlite_master WHERE type='table' AND name='known_objects'" ) assert cursor.fetchone() is not None def test_embeddings_table_exists(self): conn = get_connection() cursor = conn.execute( "SELECT name FROM sqlite_master WHERE type='table' AND name='embeddings'" ) assert cursor.fetchone() is not None def test_events_table_exists(self): conn = get_connection() cursor = conn.execute( "SELECT name FROM sqlite_master WHERE type='table' AND name='events'" ) assert cursor.fetchone() is not None def test_settings_table_exists(self): conn = get_connection() cursor = conn.execute( "SELECT name FROM sqlite_master WHERE type='table' AND name='settings'" ) assert cursor.fetchone() is not None def test_wal_mode_enabled(self): conn = get_connection() cursor = conn.execute("PRAGMA journal_mode") mode = cursor.fetchone()[0] assert mode == "wal" def test_foreign_keys_enabled(self): conn = get_connection() cursor = conn.execute("PRAGMA foreign_keys") assert cursor.fetchone()[0] == 1 def test_indexes_created(self): conn = get_connection() cursor = conn.execute( "SELECT name FROM sqlite_master WHERE type='index' AND name LIKE 'idx_%'" ) indexes = {row[0] for row in cursor.fetchall()} expected = { "idx_events_created_at", "idx_events_match_status", "idx_events_camera", "idx_embeddings_known_object_id", } assert expected.issubset(indexes) class TestDefaultSettings: """Tests for default settings insertion.""" def test_vision_backend_default(self): conn = get_connection() cursor = conn.execute( "SELECT value FROM settings WHERE key = 'vision_backend'" ) assert cursor.fetchone()[0] == "haiku" def test_recognition_enabled_default(self): conn = get_connection() cursor = conn.execute( "SELECT value FROM settings WHERE key = 'recognition_enabled'" ) assert cursor.fetchone()[0] == "false" def test_event_retention_days_default(self): conn = get_connection() cursor = conn.execute( "SELECT value FROM settings WHERE key = 'event_retention_days'" ) assert cursor.fetchone()[0] == "90" def test_reinit_does_not_overwrite_settings(self): """Calling init_db() again should not reset changed settings.""" conn = get_connection() conn.execute( "UPDATE settings SET value = 'ollama' WHERE key = 'vision_backend'" ) conn.commit() # Re-init init_db() cursor = conn.execute( "SELECT value FROM settings WHERE key = 'vision_backend'" ) assert cursor.fetchone()[0] == "ollama" class TestKnownObjectsCRUD: """Tests for known_objects CRUD operations.""" def test_insert_known_object(self): conn = get_connection() conn.execute( "INSERT INTO known_objects (name, type, action) VALUES (?, ?, ?)", ("John", "person", "quiet"), ) conn.commit() cursor = conn.execute("SELECT * FROM known_objects WHERE name = 'John'") row = cursor.fetchone() assert row is not None assert row["name"] == "John" assert row["type"] == "person" assert row["action"] == "quiet" def test_update_known_object(self): conn = get_connection() conn.execute( "INSERT INTO known_objects (name, type, action) VALUES (?, ?, ?)", ("Rover", "pet", "quiet"), ) conn.commit() conn.execute( "UPDATE known_objects SET action = 'suppress' WHERE name = 'Rover'" ) conn.commit() cursor = conn.execute( "SELECT action FROM known_objects WHERE name = 'Rover'" ) assert cursor.fetchone()[0] == "suppress" def test_delete_known_object(self): conn = get_connection() conn.execute( "INSERT INTO known_objects (name, type, action) VALUES (?, ?, ?)", ("Delete Me", "person", "quiet"), ) conn.commit() conn.execute("DELETE FROM known_objects WHERE name = 'Delete Me'") conn.commit() cursor = conn.execute( "SELECT * FROM known_objects WHERE name = 'Delete Me'" ) assert cursor.fetchone() is None def test_type_check_constraint(self): conn = get_connection() with pytest.raises(sqlite3.IntegrityError): conn.execute( "INSERT INTO known_objects (name, type, action) VALUES (?, ?, ?)", ("Bad", "alien", "quiet"), ) def test_action_check_constraint(self): conn = get_connection() with pytest.raises(sqlite3.IntegrityError): conn.execute( "INSERT INTO known_objects (name, type, action) VALUES (?, ?, ?)", ("Bad", "person", "explode"), ) class TestEventsCRUD: """Tests for events CRUD operations.""" def test_insert_event(self): conn = get_connection() conn.execute( """INSERT INTO events (frigate_event_id, camera, label, score, ai_description, notified) VALUES (?, ?, ?, ?, ?, ?)""", ("evt-001", "front_door", "person", 0.95, "A person at the door.", 1), ) conn.commit() cursor = conn.execute( "SELECT * FROM events WHERE frigate_event_id = 'evt-001'" ) row = cursor.fetchone() assert row is not None assert row["camera"] == "front_door" assert row["label"] == "person" assert row["score"] == 0.95 assert row["match_status"] == "unreviewed" assert row["notified"] == 1 def test_frigate_event_id_unique(self): conn = get_connection() conn.execute( """INSERT INTO events (frigate_event_id, camera, label, score, notified) VALUES (?, ?, ?, ?, ?)""", ("evt-dup", "cam1", "car", 0.8, 1), ) conn.commit() with pytest.raises(sqlite3.IntegrityError): conn.execute( """INSERT INTO events (frigate_event_id, camera, label, score, notified) VALUES (?, ?, ?, ?, ?)""", ("evt-dup", "cam2", "dog", 0.9, 0), ) def test_match_status_check_constraint(self): conn = get_connection() with pytest.raises(sqlite3.IntegrityError): conn.execute( """INSERT INTO events (frigate_event_id, camera, label, score, match_status, notified) VALUES (?, ?, ?, ?, ?, ?)""", ("evt-bad", "cam1", "person", 0.8, "invalid_status", 1), ) def test_matched_object_id_foreign_key_set_null(self): """Deleting a known object should set matched_object_id to NULL on events.""" conn = get_connection() # Create known object conn.execute( "INSERT INTO known_objects (name, type, action) VALUES (?, ?, ?)", ("Jane", "person", "quiet"), ) conn.commit() obj_id = conn.execute( "SELECT id FROM known_objects WHERE name = 'Jane'" ).fetchone()[0] # Create event linked to the known object conn.execute( """INSERT INTO events (frigate_event_id, camera, label, score, matched_object_id, match_status, notified) VALUES (?, ?, ?, ?, ?, ?, ?)""", ("evt-fk", "cam1", "person", 0.9, obj_id, "known", 1), ) conn.commit() # Delete the known object conn.execute("DELETE FROM known_objects WHERE id = ?", (obj_id,)) conn.commit() # Event should still exist with matched_object_id = NULL cursor = conn.execute( "SELECT matched_object_id FROM events WHERE frigate_event_id = 'evt-fk'" ) row = cursor.fetchone() assert row is not None assert row["matched_object_id"] is None class TestEmbeddingsCascade: """Tests for embeddings cascade delete.""" def test_cascade_delete_embeddings(self): """Deleting a known object should cascade delete its embeddings.""" conn = get_connection() # Create known object conn.execute( "INSERT INTO known_objects (name, type, action) VALUES (?, ?, ?)", ("Bob", "person", "alert"), ) conn.commit() obj_id = conn.execute( "SELECT id FROM known_objects WHERE name = 'Bob'" ).fetchone()[0] # Create embeddings for that object fake_embedding = b"\x00\x01\x02\x03" conn.execute( "INSERT INTO embeddings (known_object_id, embedding) VALUES (?, ?)", (obj_id, fake_embedding), ) conn.execute( "INSERT INTO embeddings (known_object_id, embedding) VALUES (?, ?)", (obj_id, fake_embedding), ) conn.commit() # Verify embeddings exist cursor = conn.execute( "SELECT COUNT(*) FROM embeddings WHERE known_object_id = ?", (obj_id,) ) assert cursor.fetchone()[0] == 2 # Delete the known object conn.execute("DELETE FROM known_objects WHERE id = ?", (obj_id,)) conn.commit() # Embeddings should be gone cursor = conn.execute( "SELECT COUNT(*) FROM embeddings WHERE known_object_id = ?", (obj_id,) ) assert cursor.fetchone()[0] == 0 def test_embedding_foreign_key_constraint(self): """Inserting an embedding with a non-existent known_object_id should fail.""" conn = get_connection() with pytest.raises(sqlite3.IntegrityError): conn.execute( "INSERT INTO embeddings (known_object_id, embedding) VALUES (?, ?)", (99999, b"\x00"), )