"""Collector tracer bullet test. Tests the thinnest complete write path through the system: - Input: smartctl-JSON fixture, sysfs fixture tree, config fixture, injected clock - Output: resulting store contents, run outcome Seam: write side of the observation store database file. """ import json import os import sqlite3 import tempfile from datetime import datetime, timezone from pathlib import Path from typing import Any, Dict, Generator import pytest # Add src to path for imports import sys sys.path.insert(0, str(Path(__file__).parent.parent / "src")) from fenris.collector import run_collection, AcquisitionError, InvariantViolationError from fenris.store import init_store, get_store_path # Fixtures @pytest.fixture def smartctl_fixture() -> Dict[str, Any]: """Minimal smartctl -a -j output with required fields.""" return { "json_format_version": [1, 0], "smartctl": {"version": [7, 3], "svn_revision": "5155", "build_info": "(local build)"}, "nvme_smart_health_information_log": { "critical_warning": 0, "temperature": 35, "available_spare": 100, "available_spare_threshold": 10, "percentage_used": 5, "data_units_written": 12345678, "data_units_read": 9876543, "power_on_hours": 8765, "power_cycles": 1234, "unsafe_shutdowns": 5, "media_errors": 0, "num_err_log_entries": 0, }, "user_capacity": {"bytes": 1024000000000, "units": "bytes"}, "model_name": "Samsung SSD 970 EVO Plus 1TB", "serial_number": "S4EWNX0N123456", "firmware_version": "2B2QEXM7", } @pytest.fixture def sysfs_fixture_tree(tmp_path: Path) -> Path: """Create a minimal sysfs fixture tree with controller identity.""" ctrl_dir = tmp_path / "sys" / "class" / "nvme" / "nvme0" ctrl_dir.mkdir(parents=True) # Controller identity files (ctrl_dir / "subsysnqn").write_text("nqn.2014-08.org.nvmexpress:uuid:12345678-1234-1234-1234-123456789abc\n") (ctrl_dir / "model").write_text("Samsung SSD 970 EVO Plus 1TB\n") (ctrl_dir / "serial").write_text("S4EWNX0N123456\n") (ctrl_dir / "firmware_rev").write_text("2B2QEXM7\n") # Transport info (optional, but we'll include it) transport_dir = ctrl_dir / "transport" transport_dir.mkdir() (transport_dir / "address").write_text("0000:03:00.0") (transport_dir / "trstring").write_text("pcie") return tmp_path @pytest.fixture def config_fixture(tmp_path: Path) -> Dict[str, Any]: """Configuration fixture naming the device.""" return { "device": "/dev/nvme0", "store_path": str(tmp_path / "observations.db"), } @pytest.fixture def clock_fixture(): """Injected clock returning fixed time.""" class FakeClock: def __init__(self): self.now = datetime(2026, 9, 1, 12, 0, 0, tzinfo=timezone.utc) def utcnow(self): return self.now return FakeClock() # Test: Collector writes one well-formed sample def test_collector_writes_one_sample( smartctl_fixture: Dict[str, Any], sysfs_fixture_tree: Path, config_fixture: Dict[str, Any], clock_fixture, ): """Given a smartctl fixture and sysfs fixture tree, when the collector runs, then one well-formed sample is written to the observation store.""" result = run_collection( smartctl_data=smartctl_fixture, sysfs_path=sysfs_fixture_tree / "sys" / "class" / "nvme" / "nvme0", config=config_fixture, clock=clock_fixture, ) # Verify run succeeded assert result["ok"] is True, f"Collection failed: {result.get('error')}" # Verify store contents conn = sqlite3.connect(config_fixture["store_path"]) cursor = conn.execute("SELECT COUNT(*) FROM samples") count = cursor.fetchone()[0] assert count == 1 cursor = conn.execute("SELECT * FROM samples") row = cursor.fetchone() assert row is not None # Verify row contents match fixtures # Row structure: id, ts, device, subnqn, sn, mn, fr, capacity_bytes, # percentage_used, available_spare, media_errors, power_on_hours, power_cycles, # unsafe_shutdowns, temperature_c, data_units_written, data_units_read, # bytes_written, bytes_read, critical_warning assert row[2] == "/dev/nvme0" # device assert row[3] == "nqn.2014-08.org.nvmexpress:uuid:12345678-1234-1234-1234-123456789abc" # subnqn assert row[4] == "S4EWNX0N123456" # sn assert row[5] == "Samsung SSD 970 EVO Plus 1TB" # mn assert row[6] == "2B2QEXM7" # fr assert row[7] == 1024000000000 # capacity_bytes assert row[8] == 5 # percentage_used assert row[15] == 12345678 # data_units_written assert row[17] == 12345678 * 512000 # bytes_written conn.close() # Test: Identity normalization applied exactly once at write time def test_identity_normalization( smartctl_fixture: Dict[str, Any], sysfs_fixture_tree: Path, config_fixture: Dict[str, Any], clock_fixture, ): """Given sysfs identity with trailing spaces/newlines, when the collector writes, then identity is normalized exactly once at write time.""" # Create identity with trailing whitespace identity = { "subnqn": "nqn.2014-08.org.nvmexpress:uuid:12345678-1234-1234-1234-123456789abc \n", "mn": "Samsung SSD 970 EVO Plus 1TB\n", "sn": "S4EWNX0N123456\n", "fr": "2B2QEXM7", "transport": "pcie", } from fenris.collector import normalize_identity # Normalize once key1 = normalize_identity(identity) # Normalize again - should be identical key2 = normalize_identity(identity) assert key1 == key2 assert key1 == "nqn.2014-08.org.nvmexpress:uuid:12345678-1234-1234-1234-123456789abc" assert "\n" not in key1 assert key1 == key1.rstrip() # No trailing whitespace # Test: Any acquisition failure fails the whole run def test_acquisition_failure_fails_run( sysfs_fixture_tree: Path, config_fixture: Dict[str, Any], clock_fixture, ): """Given a smartctl fixture with missing fields, when the collector runs, then the whole run fails and writes nothing.""" # Missing required field bad_smartctl = { "json_format_version": [1, 0], "smartctl": {"version": [7, 3]}, # Missing nvme_smart_health_information_log } result = run_collection( smartctl_data=bad_smartctl, sysfs_path=sysfs_fixture_tree / "sys" / "class" / "nvme" / "nvme0", config=config_fixture, clock=clock_fixture, ) # Verify run failed assert result["ok"] is False assert "Missing required field" in result["error"] # Verify nothing was written if os.path.exists(config_fixture["store_path"]): conn = sqlite3.connect(config_fixture["store_path"]) cursor = conn.execute("SELECT COUNT(*) FROM samples") count = cursor.fetchone()[0] assert count == 0 conn.close() else: # Store wasn't even created - also valid pass # Test: Store initializes with six entities def test_store_initialization(config_fixture: Dict[str, Any]): """Given no existing store, when the collector runs, then the store is initialized with six entities.""" store_path = Path(config_fixture["store_path"]) # Store shouldn't exist yet assert not store_path.exists() # Initialize store conn = init_store(store_path) # Verify the observation, derived-history, and metadata tables exist. cursor = conn.execute("SELECT name FROM sqlite_master WHERE type='table'") tables = {row[0] for row in cursor.fetchall()} expected_tables = { "samples", "hour_observations", "day_aggregates", "monitoring_periods", "controller_segments", "endurance_baseline", } # sqlite_sequence is a system table created by AUTOINCREMENT expected_tables.add("sqlite_sequence") expected_tables.add("store_metadata") expected_tables.add("local_days") expected_tables.add("pending_publications") assert expected_tables == tables conn.close() # Test: Schema versioning with PRAGMA user_version def test_schema_versioning(config_fixture: Dict[str, Any]): """Given a store with unknown newer version, when the collector runs, then it refuses to proceed.""" store_path = Path(config_fixture["store_path"]) # Create a store with newer version conn = sqlite3.connect(str(store_path)) conn.execute("PRAGMA journal_mode=WAL") conn.execute("PRAGMA user_version=999") # Unknown newer version conn.close() # Try to initialize - should fail with pytest.raises(ValueError, match="newer Fenris"): init_store(store_path) def test_schema_version_current(config_fixture: Dict[str, Any]): """Given a store with current version, when the collector runs, then it proceeds without migration.""" from fenris.store import SCHEMA_VERSION store_path = Path(config_fixture["store_path"]) # Initialize store conn1 = init_store(store_path) conn1.close() # Open again - should succeed conn2 = init_store(store_path) # Verify version is current cursor = conn2.execute("PRAGMA user_version") version = cursor.fetchone()[0] assert version == SCHEMA_VERSION conn2.close() def test_schema_version_older(config_fixture: Dict[str, Any]): """Given a store with older version, when the collector runs, then it applies migrations and proceeds.""" from fenris.store import SCHEMA_VERSION store_path = Path(config_fixture["store_path"]) # Create a store with older version conn = sqlite3.connect(str(store_path)) conn.execute("PRAGMA journal_mode=WAL") conn.execute("PRAGMA user_version=0") # Older version conn.close() # Initialize store - should apply migrations conn = init_store(store_path) # Verify version is current cursor = conn.execute("PRAGMA user_version") version = cursor.fetchone()[0] assert version == SCHEMA_VERSION conn.close() # Test: Invariant-violating run writes nothing def test_invariant_violation_writes_nothing( smartctl_fixture: Dict[str, Any], sysfs_fixture_tree: Path, config_fixture: Dict[str, Any], clock_fixture, ): """Given a smartctl fixture that would violate store invariants, when the collector runs, then it writes nothing and fails visibly.""" # Create a fixture that would cause invariant violation # (negative bytes_written - we'll mock this) bad_smartctl = smartctl_fixture.copy() bad_smartctl["nvme_smart_health_information_log"] = { **smartctl_fixture["nvme_smart_health_information_log"], "data_units_written": -1, # This will cause negative bytes_written } result = run_collection( smartctl_data=bad_smartctl, sysfs_path=sysfs_fixture_tree / "sys" / "class" / "nvme" / "nvme0", config=config_fixture, clock=clock_fixture, ) # Verify run failed assert result["ok"] is False assert "InvariantViolation" in result["error_type"] # Verify nothing was written if os.path.exists(config_fixture["store_path"]): conn = sqlite3.connect(config_fixture["store_path"]) cursor = conn.execute("SELECT COUNT(*) FROM samples") count = cursor.fetchone()[0] assert count == 0 conn.close()