Files
Fenris/tests/test_issue_92.py
T
2026-10-05 17:28:15 +05:30

836 lines
33 KiB
Python

"""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
"""
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()
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.execute(
"UPDATE hour_observations SET coverage = 0.5, unknown_seconds = 1800 "
"WHERE hour = ?",
("2026-11-01T04:00:00+00:00",),
)
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
await pilot.press("left")
incomplete_hour = str(app.query_one("#bar-readout").render())
assert "00:00 -0400" in incomplete_hour
assert "50% coverage · incomplete" in incomplete_hour
finally:
patcher.stop()