"""Raw sample pruning tests. Spec §3.4, ST-5: Raw samples pruned to 14 days; hour observations and day aggregates retained indefinitely. """ import sqlite3 import sys from datetime import datetime, timezone, timedelta from pathlib import Path import pytest sys.path.insert(0, str(Path(__file__).parent.parent / "src")) from fenris.store import init_store from fenris.pruning import prune_old_samples @pytest.fixture def store_conn(tmp_path: Path): db_path = tmp_path / "test.db" conn = init_store(db_path) yield conn conn.close() def _insert_sample(conn, ts_iso, device="/dev/nvme0"): conn.execute( "INSERT INTO samples (ts, device, data_units_written, data_units_read, " " bytes_written, bytes_read, percentage_used) VALUES (?, ?, 0, 0, 0, 0, 0)", (ts_iso, device), ) conn.commit() class TestPruneOldSamples: """Spec §3.4: Raw samples pruned to 14 days.""" def test_keeps_recent_samples(self, store_conn): now = datetime(2026, 9, 15, 12, 0, 0, tzinfo=timezone.utc) # Insert a sample 1 day ago ts = (now - timedelta(days=1)).isoformat() _insert_sample(store_conn, ts) pruned = prune_old_samples(store_conn, now, retention_days=14) assert pruned == 0 cursor = store_conn.execute("SELECT COUNT(*) FROM samples") assert cursor.fetchone()[0] == 1 def test_removes_old_samples(self, store_conn): now = datetime(2026, 9, 15, 12, 0, 0, tzinfo=timezone.utc) # Insert samples at 10, 14, and 15 days ago for days_ago in [10, 14, 15]: ts = (now - timedelta(days=days_ago)).isoformat() _insert_sample(store_conn, ts) pruned = prune_old_samples(store_conn, now, retention_days=14) assert pruned == 1 # Only the 15-day-old sample removed cursor = store_conn.execute("SELECT COUNT(*) FROM samples") assert cursor.fetchone()[0] == 2 def test_removes_many_old_samples(self, store_conn): now = datetime(2026, 9, 15, 12, 0, 0, tzinfo=timezone.utc) for days_ago in range(1, 30): ts = (now - timedelta(days=days_ago)).isoformat() _insert_sample(store_conn, ts) pruned = prune_old_samples(store_conn, now, retention_days=14) assert pruned == 15 # Days 15-29 removed cursor = store_conn.execute("SELECT COUNT(*) FROM samples") assert cursor.fetchone()[0] == 14 # Days 1-14 kept def test_empty_store_no_error(self, store_conn): now = datetime(2026, 9, 15, 12, 0, 0, tzinfo=timezone.utc) pruned = prune_old_samples(store_conn, now, retention_days=14) assert pruned == 0 def test_hour_observations_not_pruned(self, store_conn): """Hour observations are retained indefinitely.""" now = datetime(2026, 9, 15, 12, 0, 0, tzinfo=timezone.utc) # Insert an old sample and a recent sample _insert_sample(store_conn, (now - timedelta(days=20)).isoformat()) _insert_sample(store_conn, (now - timedelta(days=1)).isoformat()) # Insert an old hour observation store_conn.execute( "INSERT INTO hour_observations (hour, active_seconds, sample_count) " "VALUES (?, 3600, 1)", ((now - timedelta(days=20)).replace(hour=0, minute=0, second=0).isoformat(),), ) store_conn.commit() prune_old_samples(store_conn, now, retention_days=14) # Sample removed cursor = store_conn.execute("SELECT COUNT(*) FROM samples") assert cursor.fetchone()[0] == 1 # Hour observation retained cursor = store_conn.execute("SELECT COUNT(*) FROM hour_observations") assert cursor.fetchone()[0] == 1