feat: Apply Fenris identity and Drive health grouping (issue #79)\n\n- Add wolf glyph identity (Fenris by Bongbetic) with fallback for unsupported terminals\n- Remove duplicate maker credit from service strip (single placement in titlebox)\n- Move vendor wear under Drive health with full context\n- Preserve all existing behavior: continuity, pause block, quit rail, auth banner, controls\n\nCloses #79
This commit is contained in:
+34
-12
@@ -120,6 +120,26 @@ def _habit_bar(a: float, i: float, o: float, u: float, width: int = 40) -> str:
|
|||||||
return bar + "\n" + legend
|
return bar + "\n" + legend
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Identity and wolf glyph detection (issue #79)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Wolf glyph for Fenris branding
|
||||||
|
_WOLF_GLYPH = "\U0001f43a" # 🐺
|
||||||
|
_IDENTITY_FULL = _WOLF_GLYPH + " Fenris by Bongbetic"
|
||||||
|
_IDENTITY_FALLBACK = "Fenris by Bongbetic"
|
||||||
|
|
||||||
|
|
||||||
|
def _can_render_wolf(width: int) -> bool:
|
||||||
|
"""Determine if the wolf glyph can be rendered without tofu.
|
||||||
|
|
||||||
|
Uses a conservative heuristic:
|
||||||
|
- Terminal width >= 80 columns (standard minimum)
|
||||||
|
- Wolf glyph width is 1 column in most terminals
|
||||||
|
"""
|
||||||
|
return width >= 80
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Bar graph glyphs and constants
|
# Bar graph glyphs and constants
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -893,7 +913,7 @@ class DisclosuresScreen(ModalScreen[None]):
|
|||||||
class FenrisTuiApp(App):
|
class FenrisTuiApp(App):
|
||||||
"""Fenris Panes TUI — keyboard-first, one dense screen (spec §7)."""
|
"""Fenris Panes TUI — keyboard-first, one dense screen (spec §7)."""
|
||||||
|
|
||||||
TITLE = "Fenris — NVMe endurance monitor"
|
TITLE = _IDENTITY_FULL
|
||||||
SUB_TITLE = ""
|
SUB_TITLE = ""
|
||||||
|
|
||||||
CSS = """
|
CSS = """
|
||||||
@@ -982,7 +1002,13 @@ class FenrisTuiApp(App):
|
|||||||
|
|
||||||
def _headline_prefix(self) -> str:
|
def _headline_prefix(self) -> str:
|
||||||
"""Render identity and any launch-only guidance above drive state."""
|
"""Render identity and any launch-only guidance above drive state."""
|
||||||
lines = ["[bold]Fenris — NVMe endurance monitor[/bold]"]
|
# Use wolf glyph if terminal can render it (issue #79)
|
||||||
|
width = getattr(self, '_size', (80, 24))[0] if hasattr(self, '_size') else 80
|
||||||
|
if _can_render_wolf(width):
|
||||||
|
identity = _IDENTITY_FULL
|
||||||
|
else:
|
||||||
|
identity = _IDENTITY_FALLBACK
|
||||||
|
lines = ["[bold]%s[/bold]" % identity]
|
||||||
if self._show_auth_notice:
|
if self._show_auth_notice:
|
||||||
lines.append("[dim]privileged actions will prompt for authentication (polkit)[/dim]")
|
lines.append("[dim]privileged actions will prompt for authentication (polkit)[/dim]")
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
@@ -1029,8 +1055,7 @@ class FenrisTuiApp(App):
|
|||||||
self.query_one("#usage-history").set_data([])
|
self.query_one("#usage-history").set_data([])
|
||||||
self.query_one("#drive-health").update("")
|
self.query_one("#drive-health").update("")
|
||||||
self.query_one("#service-strip").update(
|
self.query_one("#service-strip").update(
|
||||||
"boot: disabled · timer: inactive · last collect: unknown · freshness: empty · "
|
"boot: disabled · timer: inactive · last collect: unknown · freshness: empty\n"
|
||||||
"[dim]by Bongbetic[/dim]\n"
|
|
||||||
"[bold]CONTINUITY[/bold] %s\n"
|
"[bold]CONTINUITY[/bold] %s\n"
|
||||||
"p pause · r resume · c collect · d disclosures"
|
"p pause · r resume · c collect · d disclosures"
|
||||||
% monitoring_continuity({"boot_enabled": False})
|
% monitoring_continuity({"boot_enabled": False})
|
||||||
@@ -1044,7 +1069,6 @@ class FenrisTuiApp(App):
|
|||||||
self.query_one("#usage-history").set_data([])
|
self.query_one("#usage-history").set_data([])
|
||||||
self.query_one("#drive-health").update("")
|
self.query_one("#drive-health").update("")
|
||||||
self.query_one("#service-strip").update(
|
self.query_one("#service-strip").update(
|
||||||
"[dim]by Bongbetic[/dim]\n"
|
|
||||||
"p pause · r resume · c collect · d disclosures"
|
"p pause · r resume · c collect · d disclosures"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1092,14 +1116,14 @@ class FenrisTuiApp(App):
|
|||||||
else:
|
else:
|
||||||
graph.set_data(day_data, on_drill=self._on_graph_drill)
|
graph.set_data(day_data, on_drill=self._on_graph_drill)
|
||||||
|
|
||||||
# --- Drive-health pane (§7.2 right) ---
|
# --- Drive-health pane (§7.2 right, issue #79) ---
|
||||||
health = _query_drive_health(conn)
|
health = _query_drive_health(conn)
|
||||||
|
# Vendor wear grouped under Drive health with context
|
||||||
health_text = (
|
health_text = (
|
||||||
"[bold]Drive health[/bold] · %s\n"
|
"[bold]Drive health[/bold] · %s\n"
|
||||||
" temperature %d°C · spare %d%%\n"
|
" temperature %d°C · spare %d%%\n"
|
||||||
" media errors %d · unsafe shutdowns %d\n"
|
" media errors %d · unsafe shutdowns %d\n"
|
||||||
" power-on %d h · %d cycles · %s\n\n"
|
" power-on %d h · %d cycles · %s\n"
|
||||||
"[bold]Settings[/bold]\n"
|
|
||||||
" vendor wear: %d%% used · %.1f TB written"
|
" vendor wear: %d%% used · %.1f TB written"
|
||||||
) % (
|
) % (
|
||||||
health["model"],
|
health["model"],
|
||||||
@@ -1127,10 +1151,9 @@ class FenrisTuiApp(App):
|
|||||||
)
|
)
|
||||||
# Render using the shared composition with TUI styling
|
# Render using the shared composition with TUI styling
|
||||||
status_text = render_status_tui(comp)
|
status_text = render_status_tui(comp)
|
||||||
# Add TUI-only branding and actions
|
# Add TUI-only actions
|
||||||
self.query_one("#service-strip").update(
|
self.query_one("#service-strip").update(
|
||||||
"%s\n"
|
"%s\n"
|
||||||
"[dim]by Bongbetic[/dim]\n"
|
|
||||||
"p pause · r resume · c collect · d disclosures"
|
"p pause · r resume · c collect · d disclosures"
|
||||||
% status_text
|
% status_text
|
||||||
)
|
)
|
||||||
@@ -1138,8 +1161,7 @@ class FenrisTuiApp(App):
|
|||||||
except Exception:
|
except Exception:
|
||||||
self._hide_paused_banner()
|
self._hide_paused_banner()
|
||||||
self.query_one("#service-strip").update(
|
self.query_one("#service-strip").update(
|
||||||
"boot: unknown · timer: unknown · last collect: unknown · freshness: unknown · "
|
"boot: unknown · timer: unknown · last collect: unknown · freshness: unknown\n"
|
||||||
"[dim]by Bongbetic[/dim]\n"
|
|
||||||
"p pause · r resume · c collect · d disclosures"
|
"p pause · r resume · c collect · d disclosures"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,318 @@
|
|||||||
|
"""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()
|
||||||
+14
-5
@@ -379,7 +379,10 @@ class TestDenseScreen:
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_service_strip_has_four_facts(self, tmp_path):
|
async def test_service_strip_has_four_facts(self, tmp_path):
|
||||||
"""Service strip has four separate facts (boot, timer, collect, freshness)."""
|
"""Service strip has four separate facts (boot, timer, collect, freshness).
|
||||||
|
|
||||||
|
Maker credit removed from service strip (issue #79: single placement in title).
|
||||||
|
"""
|
||||||
conn = init_store(tmp_path / "test.db")
|
conn = init_store(tmp_path / "test.db")
|
||||||
_insert_segment(conn)
|
_insert_segment(conn)
|
||||||
_open_period(conn)
|
_open_period(conn)
|
||||||
@@ -396,7 +399,8 @@ class TestDenseScreen:
|
|||||||
assert "timer:" in strip
|
assert "timer:" in strip
|
||||||
assert "last collect:" in strip
|
assert "last collect:" in strip
|
||||||
assert "freshness:" in strip
|
assert "freshness:" in strip
|
||||||
assert "by Bongbetic" in strip
|
# Maker credit now in titlebox only (issue #79)
|
||||||
|
assert "by Bongbetic" not in strip
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_service_strip_shows_continuity_and_separate_quit_rail(self, tmp_path):
|
async def test_service_strip_shows_continuity_and_separate_quit_rail(self, tmp_path):
|
||||||
@@ -525,7 +529,10 @@ class TestDenseScreen:
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_branding_and_one_time_auth_banner(self, tmp_path):
|
async def test_branding_and_one_time_auth_banner(self, tmp_path):
|
||||||
"""Identity is visible at launch; auth notice clears once per session."""
|
"""Identity is visible at launch; auth notice clears once per session.
|
||||||
|
|
||||||
|
Maker credit in titlebox only (issue #79: Fenris by Bongbetic).
|
||||||
|
"""
|
||||||
app = FenrisTuiApp(
|
app = FenrisTuiApp(
|
||||||
store_path=tmp_path / "nonexistent.db",
|
store_path=tmp_path / "nonexistent.db",
|
||||||
refresh_interval_s=0.2,
|
refresh_interval_s=0.2,
|
||||||
@@ -534,9 +541,11 @@ class TestDenseScreen:
|
|||||||
|
|
||||||
async with app.run_test() as pilot:
|
async with app.run_test() as pilot:
|
||||||
headline = str(app.query_one("#headline-band").render())
|
headline = str(app.query_one("#headline-band").render())
|
||||||
assert "Fenris — NVMe endurance monitor" in headline
|
# Identity now shows Fenris by Bongbetic (issue #79)
|
||||||
|
assert "Fenris by Bongbetic" in headline
|
||||||
assert auth_notice in headline
|
assert auth_notice in headline
|
||||||
assert "by Bongbetic" in str(app.query_one("#service-strip").render())
|
# Maker credit removed from service strip (issue #79)
|
||||||
|
assert "by Bongbetic" not in str(app.query_one("#service-strip").render())
|
||||||
|
|
||||||
await pilot.pause(0.25)
|
await pilot.pause(0.25)
|
||||||
assert auth_notice not in str(app.query_one("#headline-band").render())
|
assert auth_notice not in str(app.query_one("#headline-band").render())
|
||||||
|
|||||||
Reference in New Issue
Block a user