"""Edge-case tests for issue #72 remaining acceptance criteria. Covers: - TPH-#34-36: Pause-crossing intervals - TPH-#59: Staleness frozen estimate wording - TPH-#62: Zero-delta evidence in live graph - TPH-#63: Horizon anchoring at evidence endpoint - TPH-#64: Burst/habit checks with unknown daily shares """ import sqlite3 from datetime import datetime, timedelta, timezone from pathlib import Path import pytest import sys sys.path.insert(0, str(Path(__file__).parent.parent / "src")) from fenris.store import init_store from fenris.monitoring_periods import ensure_period_open, close_period from fenris.derive import derive_hours_from_interval from fenris.day_aggregate import derive_day from fenris.projection import compute_projection, ConfidenceState, STALENESS_HOURS from fenris.tui import _query_live_graph_data, _query_daily_graph_data # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- def _clock(year=2026, month=10, day=1, hour=12): return datetime(year, month, day, hour, 0, 0, tzinfo=timezone.utc) def _insert_sample(conn, ts, bw=512000000000, br=256000000000, segment_id=1): conn.execute( "INSERT INTO samples (ts, device, data_units_written, data_units_read, " "percentage_used, bytes_written, bytes_read, power_on_hours, segment_id) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", (ts, "/dev/nvme0n1", 1000000, 500000, 5, bw, br, 8765, segment_id), ) conn.commit() def _insert_segment(conn, opened_at="2026-09-01T00:00:00+00:00", identity_key="nqn.test", degraded=False): conn.execute( "INSERT INTO controller_segments " "(opened_at, identity_key, identity_degraded, subnqn, sn, mn, fr, vid, ssvid, transport) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (opened_at, identity_key, degraded, "nqn.test", "SN123", "Samsung SSD 970 EVO Plus 1TB", "FW1", "0x144d", "0x144d", "pcie"), ) conn.commit() def _insert_baseline(conn, tbw_tb=1.0, verified=True): conn.execute( "INSERT INTO endurance_baseline " "(tbw_terabytes, source_url, document_revision, entry_date, model_string, " " nominal_capacity_bytes, validated_by, verified, created_at, updated_at) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (tbw_tb, "https://example.com/spec", "v1.0", "2026-01-01", "Samsung SSD 970 EVO Plus 1TB", 1024000000000, "machine_match" if verified else None, verified, "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00"), ) conn.commit() def _insert_day(conn, day, bw=10*1024*1024*1024, br=0, coverage=0.95, samples=24): """Insert a day aggregate with ~10GB writes (realistic for projection).""" conn.execute( "INSERT INTO day_aggregates (day, active_seconds, idle_seconds, powered_off_seconds, " "unknown_seconds, bytes_written_delta, bytes_read_delta, sample_count, coverage) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", (day, 3600, 0, 0, 0, bw, br, samples, coverage), ) conn.commit() def _insert_local_day(conn, local_date, tz_name="UTC", tz_offset="+00:00", utc_start=None, utc_end=None, bw=10*1024*1024*1024, br=0, coverage=0.95, samples=24, complete=True): """Insert a local_days row for testing the observation day gate.""" if utc_start is None: utc_start = local_date + "T00:00:00+00:00" if utc_end is None: dt = datetime.strptime(local_date, "%Y-%m-%d") + timedelta(days=1) utc_end = dt.strftime("%Y-%m-%dT00:00:00+00:00") conn.execute( "INSERT INTO local_days " "(local_date, tz_name, tz_offset, utc_start, utc_end, " " bytes_written, bytes_read, coverage, sample_count, complete, activity_intervals) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)", (local_date, tz_name, tz_offset, utc_start, utc_end, bw, br, coverage, samples, complete), ) conn.commit() def _insert_hour(conn, hour_iso, bw=0, br=0, sample_count=1, active=3600, idle=0, powered_off=0, unknown=0): known = active + idle + powered_off coverage = known / 3600.0 conn.execute( "INSERT INTO hour_observations " "(hour, active_seconds, idle_seconds, powered_off_seconds, unknown_seconds, " " bytes_written_delta, bytes_read_delta, sample_count, coverage) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", (hour_iso, active, idle, powered_off, unknown, bw, br, sample_count, coverage), ) conn.commit() # --------------------------------------------------------------------------- # TPH-#34-36: Pause-crossing intervals # --------------------------------------------------------------------------- class TestPauseCrossingIntervals: """Intervals crossing a pause boundary preserve evidence correctly.""" def test_interval_crossing_pause_preserves_total(self, tmp_path): """An interval that spans a pause boundary keeps the full delta in hour observations; the monitoring-period exclusion happens at the day-aggregate coverage level.""" conn = init_store(tmp_path / "test.db") _insert_segment(conn) t1 = datetime(2026, 10, 1, 10, 0, 0, tzinfo=timezone.utc) t2 = datetime(2026, 10, 1, 11, 0, 0, tzinfo=timezone.utc) t3 = datetime(2026, 10, 1, 12, 0, 0, tzinfo=timezone.utc) ensure_period_open(conn, t1) _insert_sample(conn, t1.isoformat(), bw=100000000000) _insert_sample(conn, t2.isoformat(), bw=200000000000) prev = {"id": 1, "ts": t1.isoformat(), "bytes_written": 100000000000, "bytes_read": 0, "power_on_hours": 8765, "temperature_c": 35, "data_units_written": 1000000, "data_units_read": 0} curr = {"id": 2, "ts": t2.isoformat(), "bytes_written": 200000000000, "bytes_read": 0, "power_on_hours": 8765, "temperature_c": 35, "data_units_written": 2000000, "data_units_read": 0} derive_hours_from_interval(conn, prev, curr) close_period(conn, t2, "user_disabled") ensure_period_open(conn, t3) _insert_sample(conn, t3.isoformat(), bw=300000000000) prev2 = {"id": 2, "ts": t2.isoformat(), "bytes_written": 200000000000, "bytes_read": 0, "power_on_hours": 8765, "temperature_c": 35, "data_units_written": 2000000, "data_units_read": 0} curr2 = {"id": 3, "ts": t3.isoformat(), "bytes_written": 300000000000, "bytes_read": 0, "power_on_hours": 8765, "temperature_c": 35, "data_units_written": 3000000, "data_units_read": 0} derive_hours_from_interval(conn, prev2, curr2) hour11 = conn.execute( "SELECT bytes_written_delta FROM hour_observations WHERE hour = ?", ("2026-10-01T11:00:00+00:00",) ).fetchone() assert hour11 is not None assert hour11[0] == 100000000000 agg = derive_day(conn, "2026-10-01") assert agg is not None assert agg.bytes_written_delta == 100000000000 conn.close() def test_deliberate_pause_vs_raw_stop(self, tmp_path): """Deliberate pause closes period with user_disabled; raw service stop leaves period open (interrupted).""" conn = init_store(tmp_path / "test.db") _insert_segment(conn) t1 = datetime(2026, 10, 1, 10, 0, 0, tzinfo=timezone.utc) ensure_period_open(conn, t1) _insert_sample(conn, t1.isoformat()) close_period(conn, t1 + timedelta(hours=1), "user_disabled") period = conn.execute( "SELECT ended_at, end_cause FROM monitoring_periods WHERE ended_at IS NOT NULL" ).fetchone() assert period is not None assert period[1] == "user_disabled" t2 = datetime(2026, 10, 1, 14, 0, 0, tzinfo=timezone.utc) ensure_period_open(conn, t2) _insert_sample(conn, t2.isoformat()) open_periods = conn.execute( "SELECT COUNT(*) FROM monitoring_periods WHERE ended_at IS NULL" ).fetchone() assert open_periods[0] == 1 conn.close() # --------------------------------------------------------------------------- # TPH-#59: Staleness frozen estimate wording # --------------------------------------------------------------------------- class TestStalenessFact: """Projection includes staleness fact when evidence is old.""" def test_staleness_fact_present_when_old(self, tmp_path): """When newest data is ≥48h old, projection includes staleness fact.""" conn = init_store(tmp_path / "test.db") _insert_segment(conn) _insert_baseline(conn) base = datetime(2026, 9, 15, 12, 0, 0, tzinfo=timezone.utc) for i in range(14): day = (base + timedelta(days=i)).strftime("%Y-%m-%d") _insert_day(conn, day, bw=10*1024*1024*1024) _insert_local_day(conn, day, complete=True) clock = datetime(2026, 10, 2, 12, 0, 0, tzinfo=timezone.utc) proj = compute_projection(conn, clock) assert proj.staleness_fact is not None assert "48" in proj.staleness_fact or "old" in proj.staleness_fact staleness_in_facts = any("old" in f or "48" in f for f in proj.contributing_facts) assert staleness_in_facts conn.close() def test_no_staleness_fact_when_fresh(self, tmp_path): """When newest data is recent, no staleness fact.""" conn = init_store(tmp_path / "test.db") _insert_segment(conn) _insert_baseline(conn) base = datetime(2026, 9, 18, 12, 0, 0, tzinfo=timezone.utc) for i in range(14): day = (base + timedelta(days=i)).strftime("%Y-%m-%d") _insert_day(conn, day, bw=10*1024*1024*1024) _insert_local_day(conn, day, complete=True) clock = datetime(2026, 10, 2, 12, 0, 0, tzinfo=timezone.utc) proj = compute_projection(conn, clock) assert proj.staleness_fact is None conn.close() # --------------------------------------------------------------------------- # TPH-#62: Zero-delta evidence in live graph # --------------------------------------------------------------------------- class TestZeroDeltaEvidence: """Zero-delta intervals are distinguished from missing evidence.""" def test_zero_delta_shown_as_zero(self, tmp_path): """Two identical readings produce a zero-delta interval.""" conn = init_store(tmp_path / "test.db") now = _clock() _insert_sample(conn, (now - timedelta(minutes=6)).isoformat(), bw=100000000000) _insert_sample(conn, (now - timedelta(minutes=3)).isoformat(), bw=100000000000) data = _query_live_graph_data(conn, now) assert len(data) == 1 assert data[0]["bytes_written"] == 0 assert data[0]["is_zero"] is True assert data[0]["is_gap"] is False conn.close() def test_gap_distinguished_from_zero(self, tmp_path): """A gap (missed collection) with non-zero delta is distinct from measured zero.""" conn = init_store(tmp_path / "test.db") now = _clock() _insert_sample(conn, (now - timedelta(hours=2)).isoformat(), bw=100000000000) _insert_sample(conn, (now - timedelta(minutes=3)).isoformat(), bw=200000000000) data = _query_live_graph_data(conn, now) assert len(data) == 1 assert data[0]["is_gap"] is True assert data[0]["is_zero"] is False assert data[0]["bytes_written"] == 100000000000 # --------------------------------------------------------------------------- # TPH-#63: Horizon anchoring at evidence endpoint # --------------------------------------------------------------------------- class TestHorizonAnchoring: """Scenario windows end at the latest evidence endpoint T.""" def test_horizon_rate_uses_evidence_endpoint(self, tmp_path): """Horizon rates are computed from the latest evidence endpoint, not from clock_now.""" conn = init_store(tmp_path / "test.db") _insert_segment(conn) _insert_baseline(conn) ensure_period_open(conn, datetime(2026, 9, 10, 0, 0, 0, tzinfo=timezone.utc)) # Insert 28 days of data ending 5 days ago with realistic writes base = datetime(2026, 9, 10, 12, 0, 0, tzinfo=timezone.utc) for i in range(28): day = (base + timedelta(days=i)).strftime("%Y-%m-%d") _insert_day(conn, day, bw=10*1024*1024*1024) # ~10GB/day _insert_local_day(conn, day, complete=True) clock = datetime(2026, 10, 13, 12, 0, 0, tzinfo=timezone.utc) proj = compute_projection(conn, clock) # Scenario range should exist with rates assert proj.scenario_range is not None assert len(proj.scenario_range.rates) > 0 for horizon in [7, 28]: if horizon in proj.scenario_range.rates: rate = proj.scenario_range.rates[horizon] assert rate > 0, f"Horizon {horizon} should have positive rate" conn.close() # --------------------------------------------------------------------------- # TPH-#64: Burst/habit checks with unknown daily shares # --------------------------------------------------------------------------- class TestBurstChecks: """Burst and habit checks handle unknown daily shares correctly.""" def test_habit_change_detected(self, tmp_path): """Habit change detection works with clear write rate change.""" conn = init_store(tmp_path / "test.db") _insert_segment(conn, opened_at="2026-08-01T00:00:00+00:00") _insert_baseline(conn, tbw_tb=10.0) ensure_period_open(conn, datetime(2026, 8, 1, 0, 0, 0, tzinfo=timezone.utc)) # 28 days of normal usage (100 MiB/day) bw_normal = 100 * 1024 * 1024 for i in range(28): day = (datetime(2026, 8, 1) + timedelta(days=i)).strftime("%Y-%m-%d") _insert_day(conn, day, bw=bw_normal) _insert_local_day(conn, day, complete=True, bw=bw_normal) # 10 days of high usage (300 MiB/day = 3x, triggers habit change) bw_high = 300 * 1024 * 1024 for i in range(10): day = (datetime(2026, 8, 29) + timedelta(days=i)).strftime("%Y-%m-%d") _insert_day(conn, day, bw=bw_high) _insert_local_day(conn, day, complete=True, bw=bw_high) clock = datetime(2026, 9, 8, 12, 0, 0, tzinfo=timezone.utc) proj = compute_projection(conn, clock) # Should have a habit change fact (3× increase for 10 days) assert proj.habit_change_fact is not None assert "usage habit changed" in proj.habit_change_fact conn.close() def test_zero_delta_not_treated_as_unknown(self, tmp_path): """A measured zero-delta interval is not treated as missing evidence.""" conn = init_store(tmp_path / "test.db") now = _clock() _insert_sample(conn, (now - timedelta(minutes=6)).isoformat(), bw=100000000000) _insert_sample(conn, (now - timedelta(minutes=3)).isoformat(), bw=100000000000) data = _query_live_graph_data(conn, now) assert len(data) == 1 assert data[0]["is_zero"] is True assert data[0]["is_gap"] is False assert data[0]["bytes_written"] == 0 conn.close()