"""Tests for issue #92: Browse activity dates from the keyboard. Covers: - AC92-1: [ and ] select previous/next day, g opens date entry, t returns to today/live - AC92-2: Date entry accepts valid dates, reports errors, returns on cancel - AC92-3: Selecting a date obtains correct local-day evidence - AC92-4: Historical date, measurement, and context remain stable across refresh - AC92-5: t resolves to today/live, themes through discoverable control - AC92-6: Drill-down shows available data with distinguishable states - AC92-7: Keyboard actions verified from normal launch with headless driver - AC92-8: Equivalent clickable actions at 80x24 and constrained widths """ 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.local_day import ( LocalDaySummary, local_day_boundaries, persist_local_day, ) from fenris.tz_util import detect_system_tz from fenris.tui import ( FenrisTuiApp, _RANGE_DEFAULT, ) # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _clock(year=2026, month=9, day=30, hour=12): return datetime(year, month, day, hour, 0, 0, tzinfo=timezone.utc) def _insert_segment(conn, opened_at="2026-09-01T00:00:00+00:00", identity_key="nqn.test"): conn.execute( "INSERT INTO controller_segments " "(opened_at, identity_key, identity_degraded, subnqn, sn, mn, fr, vid, ssvid, transport) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (opened_at, identity_key, False, "nqn.test", "SN123", "Samsung SSD 970 EVO Plus 1TB", "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 _open_period(conn, start="2026-09-01T00:00:00+00:00"): ensure_period_open(conn, datetime.fromisoformat(start)) def _insert_local_day(conn, local_date, tz_name="UTC", tz_offset="+00:00", bw=300, br=130, complete=True, utc_start=None, utc_end=None): if utc_start is None: utc_start = f"{local_date}T00:00:00+00:00" if utc_end is None: utc_end = f"{(datetime.fromisoformat(local_date) + timedelta(days=1)).strftime('%Y-%m-%d')}T00:00:00+00:00" summary = LocalDaySummary( local_date=local_date, tz_name=tz_name, tz_offset=tz_offset, utc_start=utc_start, utc_end=utc_end, bytes_written=bw, bytes_read=br, coverage=0.95, sample_count=24, complete=complete, ) persist_local_day(conn, summary) conn.commit() def _setup_store(tmp_path, n_days=30, today=None): """Create a store with n_days of data and return (conn, clock).""" if today is None: today = _clock() conn = init_store(tmp_path / "test.db") _insert_segment(conn) _open_period(conn) for i in range(n_days): d = (today - timedelta(days=n_days - 1 - i)).strftime("%Y-%m-%d") _insert_day(conn, d) return conn, today def _make_app(tmp_path, clock=None): """Create a FenrisTuiApp with the clock mocked to *clock*. Returns (app, patcher) - caller must keep patcher alive during the test. """ if clock is None: clock = _clock() from unittest.mock import patch import fenris.tui as tui_mod from datetime import datetime as _real_datetime class MockDatetime(_real_datetime): @classmethod def now(cls, tz=None): return clock mock_dt = MockDatetime patcher = patch.object(tui_mod, "datetime", mock_dt) patcher.start() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) return app, patcher def _visible_selected_date(app): return str(app.query_one("#local-day").render())[:10] # --------------------------------------------------------------------------- # AC92-1: Keyboard bindings exist # --------------------------------------------------------------------------- class TestBindingExistence: def test_open_square_bracket_binding(self, tmp_path): """The [ binding exists for previous day navigation.""" app = FenrisTuiApp(store_path=tmp_path / "test.db") binding_keys = {b.key for b in app.BINDINGS} assert "left_square_bracket" in binding_keys def test_close_square_bracket_binding(self, tmp_path): """The ] binding exists for next day navigation.""" app = FenrisTuiApp(store_path=tmp_path / "test.db") binding_keys = {b.key for b in app.BINDINGS} assert "right_square_bracket" in binding_keys def test_g_binding_exists(self, tmp_path): """The g binding exists for date entry.""" app = FenrisTuiApp(store_path=tmp_path / "test.db") binding_keys = {b.key for b in app.BINDINGS} assert "g" in binding_keys def test_t_binding_is_today(self, tmp_path): """The t binding maps to action_today, not a theme action.""" app = FenrisTuiApp(store_path=tmp_path / "test.db") t_binding = next(b for b in app.BINDINGS if b.key == "t") assert "today" in t_binding.action.lower() def test_pause_resume_quit_bindings_exist(self, tmp_path): """Existing bindings are preserved.""" app = FenrisTuiApp(store_path=tmp_path / "test.db") binding_keys = {b.key for b in app.BINDINGS} assert "p" in binding_keys # pause assert "r" in binding_keys # resume assert "q" in binding_keys # quit assert "d" in binding_keys # disclosures assert "m" in binding_keys # motion toggle # --------------------------------------------------------------------------- # AC92-1: Bracket navigation from normal launch # --------------------------------------------------------------------------- class TestBracketNavigation: @pytest.mark.asyncio async def test_right_bracket_advances_day(self, tmp_path): """The ] key moves to the next (newer) day when not at the end.""" conn, clock = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() # Move to an older day first so ] can advance for _ in range(3): await pilot.press("left_square_bracket") day_before = _visible_selected_date(app) await pilot.press("right_square_bracket") day_after = _visible_selected_date(app) # ] should move to a newer day (closer to today) assert day_after > day_before @pytest.mark.asyncio async def test_left_bracket_goes_to_previous_day(self, tmp_path): """The [ key moves to the previous (older) day.""" conn, clock = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() day_before = _visible_selected_date(app) await pilot.press("left_square_bracket") day_after = _visible_selected_date(app) # [ should move to an older day (further from today) assert day_after < day_before @pytest.mark.asyncio async def test_bracket_navigates_outside_visible_range(self, tmp_path): """[ beyond the current range shifts the visible window.""" app_clock = _clock() conn, clock = _setup_store(tmp_path, n_days=30, today=app_clock) conn.close() app, patcher = _make_app(tmp_path, clock=app_clock) try: async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() graph = app.query_one("#usage-history") assert graph.range_days == _RANGE_DEFAULT # Navigate to oldest day by pressing [ many times for _ in range(29): await pilot.press("left_square_bracket") # Visible readout identifies oldest selected date. selected_day = _visible_selected_date(app) oldest_day = (app_clock - timedelta(days=29)).strftime("%Y-%m-%d") assert selected_day == oldest_day finally: patcher.stop() @pytest.mark.asyncio async def test_bracket_at_boundary_does_not_move(self, tmp_path): """] at the last day does not move the selection.""" conn, clock = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() initial_date = _visible_selected_date(app) await pilot.press("right_square_bracket") assert _visible_selected_date(app) == initial_date # --------------------------------------------------------------------------- # AC92-1: Arrow keys inspect the selected view # --------------------------------------------------------------------------- class TestArrowInspection: @pytest.mark.asyncio async def test_arrow_keys_still_work_in_daily_view(self, tmp_path): """Arrow keys move the selection within the visible daily view.""" conn, clock = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() await pilot.press("v", "v") app.query_one("#usage-history").focus() await pilot.pause() day_before = _visible_selected_date(app) await pilot.press("left") day_after = _visible_selected_date(app) assert day_after != day_before @pytest.mark.asyncio async def test_arrow_keys_inspect_hours_in_drill(self, tmp_path): """Arrow keys navigate hours when in hourly drill-down.""" conn, clock = _setup_store(tmp_path, n_days=14) local_date = datetime.now().astimezone().date().isoformat() timezone_name = detect_system_tz() utc_start, utc_end, tz_offset = local_day_boundaries(local_date, timezone_name) _insert_local_day( conn, local_date, tz_name=timezone_name, tz_offset=tz_offset, utc_start=utc_start.isoformat(), utc_end=utc_end.isoformat(), ) hour = utc_start.replace(minute=0, second=0, microsecond=0) if hour < utc_start: hour += timedelta(hours=1) while hour + timedelta(hours=1) <= utc_end: conn.execute( "INSERT INTO hour_observations " "(hour, bytes_written_delta, bytes_read_delta, coverage, sample_count) " "VALUES (?, 1000000, 2000000, 1, 1)", (hour.isoformat(),), ) hour += timedelta(hours=1) conn.commit() conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() await pilot.press("v", "v") await pilot.click("#usage-history") # Enter drill-down await pilot.press("enter") assert app.query_one("#activity-tabs").active == "view-day" # Arrow keys navigate hours readout_before = str(app.query_one("#bar-readout").render()) await pilot.press("left") assert str(app.query_one("#bar-readout").render()) != readout_before # --------------------------------------------------------------------------- # AC92-1: t returns to today/live # --------------------------------------------------------------------------- class TestTodayBinding: @pytest.mark.asyncio async def test_t_returns_to_today_after_browsing(self, tmp_path): """The t key returns to today's live view after browsing.""" conn, clock = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() # Browse to an older day using [ for _ in range(5): await pilot.press("left_square_bracket") browsed_day = _visible_selected_date(app) today = datetime.now().astimezone().date().isoformat() assert browsed_day != today # Press t to return to today await pilot.press("t") assert app.query_one("#activity-tabs").active == "view-live" @pytest.mark.asyncio async def test_t_exits_hourly_drill(self, tmp_path): """The t key exits hourly drill-down back to daily view.""" conn, clock = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() await pilot.press("v", "v") await pilot.click("#usage-history") # Enter drill await pilot.press("enter") assert app.query_one("#activity-tabs").active == "view-day" # t returns to daily await pilot.press("t") assert app.query_one("#activity-tabs").active == "view-live" # --------------------------------------------------------------------------- # AC92-2: Date entry screen # --------------------------------------------------------------------------- class TestDateEntry: @pytest.mark.asyncio async def test_g_opens_date_entry(self, tmp_path): """The g key opens the date entry screen.""" conn, clock = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() await pilot.press("g") await pilot.pause() # Date picker should be pushed as a screen assert app.screen.query_one("#date-input") is not None @pytest.mark.asyncio async def test_valid_date_navigates(self, tmp_path): """Entering a valid date navigates to that day.""" app_clock = _clock() conn, clock = _setup_store(tmp_path, n_days=30, today=app_clock) conn.close() app, patcher = _make_app(tmp_path, clock=app_clock) try: async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() target_date = (app_clock - timedelta(days=5)).strftime("%Y-%m-%d") await pilot.press("g") await pilot.press(*target_date) await pilot.press("enter") await pilot.pause() assert _visible_selected_date(app) == target_date finally: patcher.stop() @pytest.mark.asyncio async def test_invalid_date_shows_error(self, tmp_path): """Entering an invalid date shows an error message.""" conn, clock = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() await pilot.press("g") await pilot.pause() await pilot.press("n", "o", "t", " ", "a", " ", "d", "a", "t", "e") await pilot.press("enter") await pilot.pause() # Should remain on date picker (error shown, not dismissed) error = str(app.screen.query_one("#date-error").render()) assert "Invalid date format" in error @pytest.mark.asyncio async def test_cancel_returns_to_prior(self, tmp_path): """Pressing escape in date entry returns to the prior view.""" conn, clock = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() prior_readout = str(app.query_one("#bar-readout").render()) await pilot.press("g") await pilot.pause() await pilot.press("escape") await pilot.pause() # Should be back to the graph with the same selection assert str(app.query_one("#bar-readout").render()) == prior_readout # --------------------------------------------------------------------------- # AC92-3: Selecting a date obtains correct local-day evidence # --------------------------------------------------------------------------- class TestLocalDayEvidence: @pytest.mark.asyncio async def test_bracket_shows_local_day_for_selected_date(self, tmp_path): """Navigating with brackets shows the local-day summary for that date.""" app_clock = _clock() conn, clock = _setup_store(tmp_path, n_days=14, today=app_clock) # Insert local-day data for a day within the 14-day range target = (app_clock - timedelta(days=4)).strftime("%Y-%m-%d") _insert_local_day(conn, target, bw=500, br=200) conn.close() app, patcher = _make_app(tmp_path, clock=app_clock) try: async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() # Navigate to the target day for _ in range(4): await pilot.press("left_square_bracket") await pilot.pause() local_day = app.query_one("#local-day") text = str(local_day.render()) assert target in text finally: patcher.stop() @pytest.mark.asyncio async def test_date_picker_shows_local_day(self, tmp_path): """Picking a date via the date picker shows its local-day summary.""" conn, clock = _setup_store(tmp_path, n_days=14) target = (clock - timedelta(days=10)).strftime("%Y-%m-%d") _insert_local_day(conn, target, bw=100, br=50) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() # Simulate the date picker callback directly app._go_to_date_callback(target) await pilot.pause() local_day = app.query_one("#local-day") text = str(local_day.render()) assert target in text # --------------------------------------------------------------------------- # AC92-4: Browsing stability across background refresh # --------------------------------------------------------------------------- class TestBrowsingStability: @pytest.mark.asyncio async def test_selection_stable_across_refresh(self, tmp_path): """Browsing selection is preserved when background refresh fires.""" conn, clock = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=0.1) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() # Browse to a specific day for _ in range(3): await pilot.press("left_square_bracket") browsed_day = _visible_selected_date(app) # Wait for at least one refresh tick import asyncio await asyncio.sleep(0.3) await pilot.pause() # Selection should be preserved assert _visible_selected_date(app) == browsed_day @pytest.mark.asyncio async def test_navigate_away_does_not_jump_on_refresh(self, tmp_path): """After navigating away from today, refresh does not jump back.""" conn, clock = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=0.1) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() # Browse to older day using [ for _ in range(5): await pilot.press("left_square_bracket") browsed_day = _visible_selected_date(app) today = datetime.now().astimezone().date().isoformat() assert browsed_day != today # Wait for refresh import asyncio await asyncio.sleep(0.3) await pilot.pause() # Should NOT jump back to today assert _visible_selected_date(app) == browsed_day assert _visible_selected_date(app) != today # --------------------------------------------------------------------------- # AC92-6: Drill-down shows available data # --------------------------------------------------------------------------- class TestDrillDownData: @pytest.mark.asyncio async def test_drill_down_shows_loading_then_data(self, tmp_path): """Drill-down transitions from loading to actual data.""" conn, clock = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() await pilot.press("v", "v") await pilot.click("#usage-history") # Enter drill-down await pilot.press("enter") assert app.query_one("#activity-tabs").active == "view-day" assert "hourly evidence unavailable" in str( app.query_one("#bar-readout").render() ) # --------------------------------------------------------------------------- # AC92-8: Constrained width behavior # --------------------------------------------------------------------------- class TestConstrainedWidth: @pytest.mark.asyncio async def test_bracket_navigation_works_at_80x24(self, tmp_path): """Date navigation works at minimum 80x24 terminal size.""" conn, clock = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(80, 24)) as pilot: await pilot.pause() day_before = _visible_selected_date(app) await pilot.press("left_square_bracket") day_after = _visible_selected_date(app) assert day_after < day_before @pytest.mark.asyncio async def test_g_opens_date_entry_at_80x24(self, tmp_path): """Date entry opens at minimum terminal size.""" conn, clock = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(80, 24)) as pilot: await pilot.pause() await pilot.press("g") await pilot.pause() assert app.screen.query_one("#date-input") is not None class TestClickableNavigation: @pytest.mark.asyncio @pytest.mark.parametrize("size", [(80, 24), (70, 20)]) async def test_click_and_keyboard_select_same_local_date(self, tmp_path, size): """Clickable and keyboard navigation share date-entry transitions.""" app_clock = _clock() conn, _ = _setup_store(tmp_path, n_days=14, today=app_clock) conn.close() app, patcher = _make_app(tmp_path, clock=app_clock) expected = (app_clock - timedelta(days=1)).strftime("%Y-%m-%d") direct = (app_clock - timedelta(days=5)).strftime("%Y-%m-%d") try: async with app.run_test(size=size) as pilot: await pilot.press("left_square_bracket") keyboard_date = _visible_selected_date(app) assert keyboard_date == expected await pilot.press("t") await pilot.click("#activity-tools", offset=(2, 0)) assert _visible_selected_date(app) == keyboard_date await pilot.press("g") await pilot.press(*direct) await pilot.press("enter") await pilot.pause() assert _visible_selected_date(app) == direct finally: patcher.stop() class TestHistoricalSelectionIdentity: @pytest.mark.asyncio async def test_unavailable_entered_date_stays_visible_after_refresh(self, tmp_path): """An unavailable requested date remains selected and explained.""" conn, clock = _setup_store(tmp_path, n_days=30) conn.close() app, patcher = _make_app(tmp_path, clock=clock) target = (clock - timedelta(days=120)).strftime("%Y-%m-%d") try: async with app.run_test(size=(100, 40)) as pilot: await pilot.press("g") await pilot.press(*target) await pilot.press("enter") await pilot.pause() local_day = str(app.query_one("#local-day").render()) assert target in local_day assert "unavailable" in local_day app.on_refresh_tick() await pilot.pause() local_day = str(app.query_one("#local-day").render()) assert target in local_day assert "unavailable" in local_day finally: patcher.stop() @pytest.mark.asyncio async def test_retained_summary_outside_recent_window_remains_available(self, tmp_path): """Direct date entry can inspect durable local summaries beyond 90 days.""" clock = _clock() conn, _ = _setup_store(tmp_path, n_days=30, today=clock) target = (clock - timedelta(days=120)).strftime("%Y-%m-%d") utc_start, utc_end, tz_offset = local_day_boundaries( target, "Asia/Kolkata", ) _insert_local_day( conn, target, tz_name="Asia/Kolkata", tz_offset=tz_offset, bw=2_000_000_000, br=1_000_000_000, utc_start=utc_start.isoformat(), utc_end=utc_end.isoformat(), ) conn.close() app, patcher = _make_app(tmp_path, clock=clock) try: async with app.run_test(size=(100, 40)) as pilot: await pilot.press("g") await pilot.press(*target) await pilot.press("enter") await pilot.pause() await pilot.press("v") await pilot.pause() readout = str(app.query_one("#bar-readout").render()) assert target in readout assert "Asia/Kolkata +05:30" in readout assert "W 2.000 GB known · R 1.000 GB known" in readout finally: patcher.stop() @pytest.mark.asyncio async def test_constrained_history_keeps_known_totals_and_evidence_state(self, tmp_path): """Small textual view retains selected day, totals, and shared evidence.""" clock = _clock() conn, _ = _setup_store(tmp_path, n_days=14, today=clock) target = (clock - timedelta(days=2)).strftime("%Y-%m-%d") utc_start, utc_end, tz_offset = local_day_boundaries( target, "Asia/Kolkata", ) _insert_local_day( conn, target, tz_name="Asia/Kolkata", tz_offset=tz_offset, bw=1_500_000_000, br=500_000_000, utc_start=utc_start.isoformat(), utc_end=utc_end.isoformat(), ) conn.execute( "INSERT INTO local_day_unallocated_evidence " "(start_sample_id, end_sample_id, start_local_date, end_local_date, " " start_tz_name, end_tz_name, started_at, ended_at, bytes_written, " " bytes_read, reason) VALUES (1, 2, ?, ?, ?, ?, ?, ?, ?, ?, 'local_midnight')", (target, "2026-09-29", "Asia/Kolkata", "Asia/Kolkata", utc_start.isoformat(), utc_start.isoformat(), 200_000_000, 300_000_000), ) conn.commit() conn.close() app, patcher = _make_app(tmp_path, clock=clock) try: async with app.run_test(size=(70, 20)) as pilot: await pilot.press("g") await pilot.press(*target) await pilot.press("enter") await pilot.press("v") await pilot.pause() readout = str(app.query_one("#bar-readout").render()) assert target in readout assert "Asia/Kolkata +05:30" in readout assert "incomplete" in readout assert "W 1.500 GB known · R 0.500 GB known" in readout assert "shared at midnight W 0.200 GB · R 0.300 GB" in readout finally: patcher.stop() @pytest.mark.asyncio async def test_date_picker_keystrokes_do_not_toggle_measurement(self, tmp_path): """Typing in date entry does not trigger dashboard shortcuts.""" conn, _ = _setup_store(tmp_path, n_days=14) conn.close() app = FenrisTuiApp(store_path=tmp_path / "test.db", refresh_interval_s=999) async with app.run_test(size=(100, 40)) as pilot: await pilot.pause() await pilot.press("g") await pilot.press("w") await pilot.press("escape") await pilot.pause() header = str(app.query_one("#live-header").render()) assert "Writes" in header @pytest.mark.asyncio async def test_historical_hour_uses_recorded_timezone_and_utc_identity(self, tmp_path): """Repeated local clock labels retain distinct UTC-hour identities.""" clock = _clock(2026, 11, 2, 12) conn, _ = _setup_store(tmp_path, n_days=30, today=clock) target = "2026-11-01" _insert_local_day( conn, target, tz_name="America/New_York", tz_offset="-04:00", utc_start="2026-11-01T04:00:00+00:00", utc_end="2026-11-02T05:00:00+00:00", ) conn.execute( "INSERT INTO local_day_unallocated_evidence " "(start_sample_id, end_sample_id, start_local_date, end_local_date, " " start_tz_name, end_tz_name, started_at, ended_at, bytes_written, " " bytes_read, reason) VALUES (1, 2, ?, ?, ?, ?, ?, ?, ?, ?, 'local_midnight')", (target, "2026-11-02", "America/New_York", "America/New_York", "2026-11-02T03:59:00+00:00", "2026-11-02T04:01:00+00:00", 2_000_000_000, 3_000_000_000), ) for index in range(25): hour = datetime(2026, 11, 1, 4, tzinfo=timezone.utc) + timedelta(hours=index) conn.execute( "INSERT INTO hour_observations " "(hour, bytes_written_delta, bytes_read_delta, coverage, sample_count) " "VALUES (?, ?, ?, 1, 1)", (hour.isoformat(), (index + 1) * 1_000_000_000, (index + 1) * 2_000_000_000), ) conn.commit() conn.close() app, patcher = _make_app(tmp_path, clock=clock) try: async with app.run_test(size=(100, 40)) as pilot: await pilot.click("#view-history") await pilot.press("g") await pilot.press(*target) await pilot.press("enter") await pilot.pause() local_day = str(app.query_one("#local-day").render()) assert "America/New_York -04:00" in local_day assert "incomplete" in local_day assert "shared at midnight W 2.000 GB · R 3.000 GB" in local_day # Move from final local hour to the second 01:00 occurrence. await pilot.press(*(["left"] * 22)) second_occurrence = str(app.query_one("#bar-readout").render()) assert "01:00 -0500" in second_occurrence assert "America/New_York -0500" in second_occurrence assert "06:00 UTC" in second_occurrence await pilot.press("left") first_occurrence = str(app.query_one("#bar-readout").render()) assert "01:00 -0400" in first_occurrence assert "America/New_York -0400" in first_occurrence assert "05:00 UTC" in first_occurrence finally: patcher.stop()