"""Tests for issue #79: Apply Fenris identity and Drive health grouping. Covers: - TPH-1: Titlebox shows Fenris identity with wolf fallback - TPH-11: Single maker-credit placement, vendor wear under Drive health - Preserve: continuity, pause block, quit rail, auth banner """ import sqlite3 from datetime import datetime, timedelta, timezone from pathlib import Path from unittest.mock import patch 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 from fenris.tui import FenrisTuiApp # --------------------------------------------------------------------------- # Helpers (reuse from test_tui.py) # --------------------------------------------------------------------------- def _clock(year=2026, month=9, day=30, hour=12): return datetime(year, month, day, hour, 0, 0, tzinfo=timezone.utc) def _insert_baseline(conn, tbw_tb=1.0, verified=True, model="Samsung SSD 970 EVO Plus 1TB"): 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", model, 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_segment(conn, opened_at="2026-09-01T00:00:00+00:00", identity_key="nqn.test", degraded=False, mn="Samsung SSD 970 EVO Plus 1TB"): 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", mn, "FW1", "0x144d", "0x144d", "pcie"), ) conn.commit() def _insert_day(conn, day, bw=1024*1024*100, coverage=0.95, samples=24): 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, 0, samples, coverage), ) conn.commit() def _insert_sample(conn, ts, pu=5, device="/dev/nvme0n1"): conn.execute( "INSERT INTO samples (ts, device, data_units_written, data_units_read, " "percentage_used, bytes_written, bytes_read, power_on_hours) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?)", (ts, device, 1000000, 500000, pu, 512000000000, 256000000000, 8765), ) conn.commit() def _open_period(conn, start="2026-09-01T00:00:00+00:00"): ensure_period_open(conn, datetime.fromisoformat(start)) # --------------------------------------------------------------------------- # Issue #79: Fenris identity and Drive health grouping # --------------------------------------------------------------------------- class TestFenrisIdentity: """TPH-1: Titlebox shows Fenris identity with wolf fallback.""" @pytest.mark.asyncio async def test_titlebox_shows_wolf_identity(self, tmp_path): """Top titlebox reads 🐺 Fenris by Bongbetic.""" app = FenrisTuiApp( store_path=tmp_path / "nonexistent.db", refresh_interval_s=0.2, ) async with app.run_test(size=(120, 24)) as pilot: headline = str(app.query_one("#headline-band").render()) assert "🐺 Fenris by Bongbetic" in headline @pytest.mark.asyncio async def test_titlebox_fallback_without_wolf(self, tmp_path): """Fallback to 'Fenris by Bongbetic' when wolf is unsupported.""" app = FenrisTuiApp( store_path=tmp_path / "nonexistent.db", refresh_interval_s=0.2, ) # Simulate narrow terminal that can't render wolf async with app.run_test(size=(60, 24)) as pilot: headline = str(app.query_one("#headline-band").render()) # Either shows wolf or fallback - both are acceptable assert "Fenris by Bongbetic" in headline @pytest.mark.asyncio async def test_wolf_never_shows_tofu(self, tmp_path): """Wolf glyph is never rendered as tofu (unsupported character).""" app = FenrisTuiApp( store_path=tmp_path / "nonexistent.db", refresh_interval_s=0.2, ) async with app.run_test(size=(120, 24)) as pilot: headline = str(app.query_one("#headline-band").render()) # No replacement character (U+FFFD) should appear assert "\ufffd" not in headline.lower() assert "?" not in headline or "Fenris" in headline @pytest.mark.asyncio async def test_lifespan_headline_is_data_surface(self, tmp_path): """Lifespan headline remains a data surface, not app title.""" conn = init_store(tmp_path / "test.db") _insert_baseline(conn, tbw_tb=1.0, verified=True) _insert_segment(conn) _open_period(conn) for i in range(30): d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d") _insert_day(conn, d, bw=1024*1024*100, coverage=0.95, samples=24) _insert_sample(conn, "2026-09-30T10:00:00+00:00") conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db") async with app.run_test(size=(120, 24)) as pilot: headline = str(app.query_one("#headline-band").render()) # Identity appears once, lifespan is separate data assert "🐺 Fenris by Bongbetic" in headline assert "remaining" in headline.lower() or "projection" in headline.lower() class TestSingleMakerCredit: """TPH-1: Single maker-credit placement in title only.""" @pytest.mark.asyncio async def test_maker_credit_not_in_service_strip(self, tmp_path): """Remove duplicate maker credit from service facts.""" conn = init_store(tmp_path / "test.db") _insert_segment(conn) _open_period(conn) for i in range(20): d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d") _insert_day(conn, d, bw=1024*1024*100) _insert_sample(conn, "2026-09-30T10:00:00+00:00") conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db") async with app.run_test(size=(120, 24)) as pilot: strip = str(app.query_one("#service-strip").render()) # "by Bongbetic" should NOT appear in service strip assert "by Bongbetic" not in strip @pytest.mark.asyncio async def test_maker_credit_in_titlebox(self, tmp_path): """Maker credit appears in the titlebox identity.""" app = FenrisTuiApp( store_path=tmp_path / "nonexistent.db", refresh_interval_s=0.2, ) async with app.run_test(size=(120, 24)) as pilot: headline = str(app.query_one("#headline-band").render()) assert "Fenris by Bongbetic" in headline @pytest.mark.asyncio async def test_empty_store_no_maker_credit_in_strip(self, tmp_path): """Empty store: no maker credit in service strip.""" app = FenrisTuiApp(store_path=tmp_path / "nonexistent.db") async with app.run_test(size=(120, 24)) as pilot: strip = str(app.query_one("#service-strip").render()) assert "by Bongbetic" not in strip @pytest.mark.asyncio async def test_store_fault_no_maker_credit_in_strip(self, tmp_path): """Store fault: no maker credit in service strip.""" # Create a corrupt store db = tmp_path / "test.db" db.write_bytes(b"not a database") app = FenrisTuiApp(store_path=db) async with app.run_test(size=(120, 24)) as pilot: strip = str(app.query_one("#service-strip").render()) assert "by Bongbetic" not in strip class TestDriveHealthVendorWear: """TPH-1: Vendor wear renders under Drive health context.""" @pytest.mark.asyncio async def test_vendor_wear_in_drive_health(self, tmp_path): """Vendor wear shows with health context, not as Settings.""" conn = init_store(tmp_path / "test.db") _insert_segment(conn) _open_period(conn) for i in range(20): d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d") _insert_day(conn, d, bw=1024*1024*100) _insert_sample(conn, "2026-09-30T10:00:00+00:00", pu=10) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db") async with app.run_test(size=(120, 24)) as pilot: health = str(app.query_one("#drive-health").render()) # Vendor wear should be present with health context assert "vendor wear" in health.lower() # Should show thermal, spare, errors, etc. assert "temperature" in health.lower() assert "spare" in health.lower() assert "media errors" in health.lower() # Settings section should NOT be a separate heading assert "[bold]Settings[/bold]" not in health @pytest.mark.asyncio async def test_missing_wear_values_honest(self, tmp_path): """Missing values remain honest unavailable facts.""" conn = init_store(tmp_path / "test.db") _insert_segment(conn) _open_period(conn) for i in range(20): d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d") _insert_day(conn, d, bw=1024*1024*100) # Insert sample with zero wear values conn.execute( "INSERT INTO samples (ts, device, data_units_written, data_units_read, " "percentage_used, bytes_written, bytes_read, power_on_hours) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?)", ("2026-09-30T10:00:00+00:00", "/dev/nvme0n1", 0, 0, 0, 0, 0, 0), ) conn.commit() conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db") async with app.run_test(size=(120, 24)) as pilot: health = str(app.query_one("#drive-health").render()) # Should show 0% used or honest zero, not crash assert "vendor wear" in health.lower() class TestPreservedBehavior: """Preserve existing continuity, pause, quit, auth behavior.""" @pytest.mark.asyncio async def test_continuity_preserved(self, tmp_path): """Continuity wording preserved in service strip.""" conn = init_store(tmp_path / "test.db") _open_period(conn) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db") with patch("fenris.tui.query_service_state", return_value={ "boot_enabled": True, "timer_active": True, "last_collect_ok": True, "last_collect_age_s": 60, "last_collect_reason": None, }): async with app.run_test(size=(120, 24)) as pilot: strip = str(app.query_one("#service-strip").render()) assert "continuity" in strip.lower() assert "monitoring" in strip.lower() @pytest.mark.asyncio async def test_quit_rail_preserved(self, tmp_path): """Separate q QUIT TUI rail preserved.""" app = FenrisTuiApp(store_path=tmp_path / "nonexistent.db") async with app.run_test(size=(120, 24)) as pilot: rail = str(app.query_one("#quit-rail").render()) assert "q QUIT TUI" in rail @pytest.mark.asyncio async def test_auth_banner_preserved(self, tmp_path): """Polkit authentication banner lifecycle preserved.""" app = FenrisTuiApp( store_path=tmp_path / "nonexistent.db", refresh_interval_s=0.2, ) async with app.run_test(size=(120, 24)) as pilot: headline = str(app.query_one("#headline-band").render()) assert "polkit" in headline.lower() # Should clear after first tick await pilot.pause(0.25) headline_after = str(app.query_one("#headline-band").render()) assert "polkit" not in headline_after.lower() @pytest.mark.asyncio async def test_deliberate_pause_block_preserved(self, tmp_path): """Deliberate pause banner preserved.""" db = tmp_path / "test.db" conn = init_store(db) conn.execute( "INSERT INTO monitoring_periods (started_at, ended_at, end_cause) " "VALUES (?, ?, ?)", ("2026-09-01T09:00:00+00:00", "2026-09-01T10:00:00+00:00", "user_disabled"), ) conn.commit() conn.close() app = FenrisTuiApp(store_path=db) with patch("fenris.tui.query_service_state", return_value={ "boot_enabled": False, "timer_active": False, "last_collect_ok": None, "last_collect_age_s": None, "last_collect_reason": None, }): async with app.run_test(size=(120, 24)) as pilot: banner = str(app.query_one("#paused-banner").render()) assert "paused" in banner.lower() assert "deliberate" in banner.lower()