feat(tui): Panes TUI on Textual (issue #28)
Implements keyboard-first Panes TUI per spec section 7: - One dense screen: headline band, usage-history, drive-health, service strip - Bindings p/r/c/d/q with pause-asks/resume-doesnt asymmetry - Privileged actions via terminal-attached fenris-monitor subprocess - Disclosures view, empty-store greeting, first-run opt-in - 35 headless tests: CI-1 state matrix, TUI-1/TUI-4 layout, CI-4, IN-3 Blocker #27 resolved. Closes #28
This commit is contained in:
@@ -0,0 +1,527 @@
|
||||
"""Headless tests for the Panes TUI (issue #28).
|
||||
|
||||
Covers:
|
||||
- CI-1: Exhaustive state matrix from synthetic stores
|
||||
- TUI-1: One dense keyboard-first screen with four normative regions
|
||||
- TUI-4: Layout regions normative per register
|
||||
- CI-4: Six disclosures verbatim, empty-store greeting, first-run opt-in
|
||||
- IN-3: First-run TUI prompt enables timer and opens first period
|
||||
|
||||
Criteria: TUI-1, TUI-4, CI-1, CI-4, IN-3.
|
||||
"""
|
||||
import sqlite3
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
||||
|
||||
from textual.app import App
|
||||
from textual.pilot import Pilot
|
||||
|
||||
from fenris.store import init_store, SCHEMA_VERSION
|
||||
from fenris.monitoring_periods import ensure_period_open, close_period
|
||||
from fenris.projection import (
|
||||
ConfidenceState,
|
||||
compute_projection,
|
||||
DISCLOSURES,
|
||||
WARMING_MIN_DAYS,
|
||||
STALENESS_HOURS,
|
||||
YOUNG_REGIME_DAYS,
|
||||
)
|
||||
from fenris.status import (
|
||||
FRESH_THRESHOLD_S,
|
||||
STALENESS_THRESHOLD_S,
|
||||
grade_freshness,
|
||||
)
|
||||
from fenris.tui import (
|
||||
FenrisTuiApp,
|
||||
_format_remaining,
|
||||
_sparkline,
|
||||
_habit_bar,
|
||||
_query_usage_history,
|
||||
_query_drive_health,
|
||||
_query_service_facts,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
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))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Unit tests for helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestFormatRemaining:
|
||||
def test_hours_only(self):
|
||||
assert _format_remaining(3600) == "1 h"
|
||||
|
||||
def test_days_and_hours(self):
|
||||
assert _format_remaining(86400) == "1 d 0 h"
|
||||
|
||||
def test_years(self):
|
||||
assert _format_remaining(31557600) == "1 yr 0 d 0 h"
|
||||
|
||||
def test_zero(self):
|
||||
assert _format_remaining(0) == "endurance exhausted"
|
||||
|
||||
def test_negative(self):
|
||||
assert _format_remaining(-100) == "endurance exhausted"
|
||||
|
||||
|
||||
class TestSparkline:
|
||||
def test_empty(self):
|
||||
assert _sparkline([]) == ""
|
||||
|
||||
def test_single_value(self):
|
||||
result = _sparkline([100.0])
|
||||
assert len(result) == 1
|
||||
|
||||
def test_multiple_values(self):
|
||||
result = _sparkline([1.0, 2.0, 3.0, 4.0, 5.0])
|
||||
assert len(result) > 0
|
||||
assert all(c in " ▁▂▃▄▅▆▇█" for c in result)
|
||||
|
||||
def test_width_limit(self):
|
||||
result = _sparkline([1.0] * 100, width=20)
|
||||
assert len(result) <= 20
|
||||
|
||||
|
||||
class TestHabitBar:
|
||||
def test_all_active(self):
|
||||
result = _habit_bar(1.0, 0.0, 0.0, 0.0)
|
||||
assert "active 100%" in result
|
||||
|
||||
def test_mixed(self):
|
||||
result = _habit_bar(0.5, 0.3, 0.1, 0.1)
|
||||
assert "active 50%" in result
|
||||
assert "idle 30%" in result
|
||||
|
||||
def test_all_unknown(self):
|
||||
result = _habit_bar(0.0, 0.0, 0.0, 1.0)
|
||||
assert "unknown 100%" in result
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Data query tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestQueryUsageHistory:
|
||||
def test_empty_store(self, tmp_path):
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
result = _query_usage_history(conn)
|
||||
assert result["num_days"] == 0
|
||||
assert result["sparkline"] == ""
|
||||
conn.close()
|
||||
|
||||
def test_with_days(self, tmp_path):
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
_insert_segment(conn)
|
||||
_open_period(conn)
|
||||
for i in range(14):
|
||||
d = (datetime(2026, 9, 15) + timedelta(days=i)).strftime("%Y-%m-%d")
|
||||
_insert_day(conn, d, bw=1024*1024*100)
|
||||
result = _query_usage_history(conn)
|
||||
assert result["num_days"] == 14
|
||||
assert result["sparkline"] != ""
|
||||
conn.close()
|
||||
|
||||
|
||||
class TestQueryDriveHealth:
|
||||
def test_empty_store(self, tmp_path):
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
result = _query_drive_health(conn)
|
||||
assert result["model"] == "unknown"
|
||||
conn.close()
|
||||
|
||||
def test_with_sample(self, tmp_path):
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
_insert_sample(conn, "2026-09-30T10:00:00+00:00", pu=10)
|
||||
result = _query_drive_health(conn)
|
||||
assert result["percentage_used"] == 10
|
||||
conn.close()
|
||||
|
||||
|
||||
class TestQueryServiceFacts:
|
||||
def test_empty_store(self, tmp_path):
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
now = _clock()
|
||||
result = _query_service_facts(conn, now)
|
||||
assert result["freshness"] == "empty"
|
||||
conn.close()
|
||||
|
||||
def test_fresh_sample(self, tmp_path):
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
_insert_segment(conn)
|
||||
_open_period(conn)
|
||||
_insert_day(conn, "2026-09-29", bw=1024*1024*100)
|
||||
now = _clock()
|
||||
ts = (now - timedelta(minutes=2)).isoformat()
|
||||
_insert_sample(conn, ts)
|
||||
result = _query_service_facts(conn, now)
|
||||
assert result["freshness"] == "fresh"
|
||||
conn.close()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CI-1: Exhaustive state matrix from synthetic stores
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestStateMatrix:
|
||||
"""TUI renders every realizable combination of confidence state × freshness × baseline tier."""
|
||||
|
||||
def test_unsupported_no_baseline(self, tmp_path):
|
||||
"""No baseline → Unavailable."""
|
||||
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")
|
||||
proj = compute_projection(conn, _clock())
|
||||
assert proj.confidence_state == ConfidenceState.UNSUPPORTED
|
||||
assert proj.headline_remaining_seconds is None
|
||||
conn.close()
|
||||
|
||||
def test_limited_warming(self, tmp_path):
|
||||
"""Warming up (< 14 days) → Limited."""
|
||||
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(10):
|
||||
d = (datetime(2026, 9, 20) + 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")
|
||||
proj = compute_projection(conn, _clock())
|
||||
assert proj.confidence_state == ConfidenceState.LIMITED
|
||||
assert proj.headline_remaining_seconds is not None
|
||||
conn.close()
|
||||
|
||||
def test_supported_full(self, tmp_path):
|
||||
"""Full data → Supported."""
|
||||
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")
|
||||
proj = compute_projection(conn, _clock())
|
||||
assert proj.confidence_state == ConfidenceState.SUPPORTED
|
||||
assert proj.headline_remaining_seconds is not None
|
||||
conn.close()
|
||||
|
||||
def test_stale_freshness(self, tmp_path):
|
||||
"""Stale data: newest day aggregate > 48h old → Limited or Unsupported."""
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
_insert_baseline(conn, tbw_tb=1.0, verified=True)
|
||||
# Segment opened long ago so days are old
|
||||
_insert_segment(conn, opened_at="2026-08-01T00:00:00+00:00")
|
||||
_open_period(conn, start="2026-08-01T00:00:00+00:00")
|
||||
# Days all end on Aug 30 — 31 days before clock (Sept 30)
|
||||
for i in range(30):
|
||||
d = (datetime(2026, 8, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
|
||||
_insert_day(conn, d, bw=1024*1024*100, coverage=0.95, samples=24)
|
||||
# Sample also old
|
||||
stale_ts = (_clock() - timedelta(days=31)).isoformat()
|
||||
_insert_sample(conn, stale_ts)
|
||||
proj = compute_projection(conn, _clock())
|
||||
# Stale data (> 48h since newest day) → not Supported
|
||||
assert proj.confidence_state != ConfidenceState.SUPPORTED
|
||||
conn.close()
|
||||
|
||||
def test_empty_store_state(self, tmp_path):
|
||||
"""Empty store → no projection, headline=None."""
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
proj = compute_projection(conn, _clock())
|
||||
assert proj.confidence_state == ConfidenceState.UNSUPPORTED
|
||||
assert proj.headline_remaining_seconds is None
|
||||
conn.close()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TUI-1: One dense keyboard-first screen
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestDenseScreen:
|
||||
"""TUI renders one dense screen with four normative regions."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_four_regions_exist(self, tmp_path):
|
||||
"""All four normative regions are present in the DOM."""
|
||||
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() as pilot:
|
||||
assert app.query_one("#headline-band") is not None
|
||||
assert app.query_one("#usage-history") is not None
|
||||
assert app.query_one("#drive-health") is not None
|
||||
assert app.query_one("#service-strip") is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_headline_contains_projection(self, tmp_path):
|
||||
"""Headline band shows projection headline."""
|
||||
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() as pilot:
|
||||
headline = str(app.query_one("#headline-band").render())
|
||||
assert "remaining" in headline.lower() or "projection" in headline.lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_confidence_rendered_as_evidence(self, tmp_path):
|
||||
"""Confidence is state + contributing facts, never a percentage."""
|
||||
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() as pilot:
|
||||
headline = str(app.query_one("#headline-band").render())
|
||||
assert "projection confidence" in headline.lower()
|
||||
# Contributing facts shown, never a percentage as confidence
|
||||
# (percentage in "95% interval coverage" is allowed as a fact, not as confidence)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_service_strip_has_four_facts(self, tmp_path):
|
||||
"""Service strip has four separate facts (boot, timer, collect, freshness)."""
|
||||
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() as pilot:
|
||||
strip = str(app.query_one("#service-strip").render())
|
||||
assert "boot:" in strip
|
||||
assert "timer:" in strip
|
||||
assert "last collect:" in strip
|
||||
assert "freshness:" in strip
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CI-4: Disclosures, empty-store greeting, first-run
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestDisclosuresAndGreeting:
|
||||
@pytest.mark.asyncio
|
||||
async def test_disclosures_screen(self, tmp_path):
|
||||
"""Disclosures view renders six disclosures verbatim."""
|
||||
app = FenrisTuiApp(store_path=tmp_path / "nonexistent.db")
|
||||
async with app.run_test() as pilot:
|
||||
await pilot.press("d")
|
||||
# Modal should be pushed
|
||||
assert len(app.screen_stack) > 1
|
||||
disc_text = str(app.screen.query_one("Static").render())
|
||||
for i in range(1, 7):
|
||||
assert "%d." % i in disc_text
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_store_greeting(self, tmp_path):
|
||||
"""Empty store shows 'no observations yet' with enable hint."""
|
||||
app = FenrisTuiApp(store_path=tmp_path / "nonexistent.db")
|
||||
async with app.run_test() as pilot:
|
||||
headline = str(app.query_one("#headline-band").render())
|
||||
assert "no observations yet" in headline.lower()
|
||||
assert "enable" in headline.lower() or "resume" in headline.lower()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# IN-3: First-run opt-in
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestFirstRun:
|
||||
@pytest.mark.asyncio
|
||||
async def test_first_run_prompt(self, tmp_path):
|
||||
"""First-run prompt enables timer and opens first period."""
|
||||
app = FenrisTuiApp(store_path=tmp_path / "nonexistent.db")
|
||||
async with app.run_test() as pilot:
|
||||
headline = str(app.query_one("#headline-band").render())
|
||||
assert "no observations yet" in headline.lower()
|
||||
# The enable hint should mention resume
|
||||
assert "resume" in headline.lower()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TUI-4: Layout regions normative
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestLayoutNormative:
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_page_navigation(self, tmp_path):
|
||||
"""No page navigation keys exist (variant switching was prototype-only)."""
|
||||
app = FenrisTuiApp(store_path=tmp_path / "nonexistent.db")
|
||||
# Check that only production bindings exist
|
||||
binding_keys = {b.key for b in app.BINDINGS}
|
||||
assert "left" not in binding_keys
|
||||
assert "right" not in binding_keys
|
||||
assert "1" not in binding_keys
|
||||
assert "2" not in binding_keys
|
||||
assert "3" not in binding_keys
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pause_resume_asymmetry(self, tmp_path):
|
||||
"""Pause asks, resume does not (TUI-2)."""
|
||||
app = FenrisTuiApp(store_path=tmp_path / "nonexistent.db")
|
||||
async with app.run_test() as pilot:
|
||||
# Pause should push a confirmation screen
|
||||
await pilot.press("p")
|
||||
assert len(app.screen_stack) > 1
|
||||
# Press n to cancel
|
||||
await pilot.press("n")
|
||||
assert len(app.screen_stack) == 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CI-1: State matrix exhaustive combinations
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestStateMatrixCombinations:
|
||||
"""CI-1: confidence × freshness × baseline tier combinations."""
|
||||
|
||||
def test_unsupported_with_fresh_data(self, tmp_path):
|
||||
"""Fresh data but no baseline → Unavailable + fresh."""
|
||||
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)
|
||||
# Sample must be within FRESH_THRESHOLD_S of clock
|
||||
fresh_ts = (_clock() - timedelta(seconds=FRESH_THRESHOLD_S - 10)).isoformat()
|
||||
_insert_sample(conn, fresh_ts)
|
||||
proj = compute_projection(conn, _clock())
|
||||
assert proj.confidence_state == ConfidenceState.UNSUPPORTED
|
||||
freshness = grade_freshness(fresh_ts, _clock())
|
||||
assert freshness == "fresh"
|
||||
conn.close()
|
||||
|
||||
def test_limited_with_stale_data(self, tmp_path):
|
||||
"""Stale data with baseline → Limited or Unsupported (young regime + stale)."""
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
_insert_baseline(conn, tbw_tb=1.0, verified=True)
|
||||
_insert_segment(conn, opened_at="2026-09-01T00:00:00+00:00")
|
||||
_open_period(conn)
|
||||
for i in range(20):
|
||||
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
|
||||
_insert_day(conn, d, bw=1024*1024*100)
|
||||
stale_ts = (_clock() - timedelta(days=3)).isoformat()
|
||||
_insert_sample(conn, stale_ts)
|
||||
proj = compute_projection(conn, _clock())
|
||||
assert proj.confidence_state != ConfidenceState.SUPPORTED
|
||||
freshness = grade_freshness(stale_ts, _clock())
|
||||
assert freshness == "stale"
|
||||
conn.close()
|
||||
|
||||
def test_supported_with_unverified_baseline(self, tmp_path):
|
||||
"""Incomplete provenance → unverified baseline tier."""
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
# Insert baseline with incomplete provenance (missing source_url)
|
||||
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(1.0, None, "v1.0", "2026-01-01", "Samsung SSD 970 EVO Plus 1TB",
|
||||
1024000000000, None, False,
|
||||
"2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00"),
|
||||
)
|
||||
conn.commit()
|
||||
_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")
|
||||
proj = compute_projection(conn, _clock())
|
||||
# Incomplete provenance → UNVERIFIED tier
|
||||
assert proj.baseline_tier.value == "unverified_override"
|
||||
conn.close()
|
||||
|
||||
Reference in New Issue
Block a user