"""Tests for the additive send-state migration on ``package_outbox``. The store schema version must NOT move when these columns are added: the existing loader raises on any version it does not recognise, so bumping it would hard-fail an older Hermes (a second profile on an older build, or a rollback) against the same database file. """ from __future__ import annotations import json import sqlite3 import pytest from hermes_cli.observability.shared_metrics import SharedMetricsStore SEND_COLUMNS = { "sent_at", "send_state", "send_attempts", "next_attempt_at", "last_error", "sent_install_id", } def _columns(db_path): connection = sqlite3.connect(db_path) try: return {row[1] for row in connection.execute("PRAGMA table_info(package_outbox)")} finally: connection.close() def _schema_version(db_path): connection = sqlite3.connect(db_path) try: row = connection.execute( "SELECT value FROM telemetry_state WHERE key = 'schema_version'" ).fetchone() return row[0] if row else None finally: connection.close() @pytest.fixture def store(tmp_path): return SharedMetricsStore( database_path=tmp_path / "metrics.sqlite3", outbox_directory=tmp_path / "outbox", ) class TestFreshDatabase: def test_send_columns_exist(self, store): assert SEND_COLUMNS <= _columns(store.database_path) def test_original_columns_survive(self, store): assert { "package_id", "period_start", "period_end", "payload_json", "created_at", "exported_at", } <= _columns(store.database_path) def test_send_attempts_defaults_to_zero(self, store): connection = sqlite3.connect(store.database_path) try: connection.execute( """ INSERT INTO package_outbox( package_id, period_start, period_end, payload_json, created_at ) VALUES ('p', '2026-01-01', '2026-01-02', '{}', '2026-01-01T00:00:00Z') """ ) connection.commit() row = connection.execute( "SELECT send_attempts, send_state, sent_install_id FROM package_outbox" ).fetchone() finally: connection.close() assert row[0] == 0 assert row[1] is None assert row[2] is None class TestUpgradeFromPreSendDatabase: """The real-world case: a database written before this feature existed.""" @pytest.fixture def legacy_db(self, tmp_path): path = tmp_path / "metrics.sqlite3" connection = sqlite3.connect(path) try: connection.execute( """ CREATE TABLE telemetry_state ( key TEXT PRIMARY KEY, value TEXT NOT NULL ) """ ) connection.execute( "INSERT INTO telemetry_state(key, value) VALUES ('schema_version', '2')" ) connection.execute( """ CREATE TABLE package_outbox ( package_id TEXT PRIMARY KEY, period_start TEXT NOT NULL, period_end TEXT NOT NULL, payload_json TEXT NOT NULL, created_at TEXT NOT NULL, exported_at TEXT ) """ ) connection.execute( """ CREATE TABLE counter_aggregates ( period_start TEXT NOT NULL, metric_name TEXT NOT NULL, hermes_version TEXT NOT NULL, os_family TEXT NOT NULL, architecture TEXT NOT NULL, install_method TEXT NOT NULL, dimensions_json TEXT NOT NULL, value INTEGER NOT NULL, packaged_value INTEGER NOT NULL, PRIMARY KEY ( period_start, metric_name, hermes_version, os_family, architecture, install_method, dimensions_json ) ) """ ) for i in range(3): connection.execute( """ INSERT INTO package_outbox( package_id, period_start, period_end, payload_json, created_at, exported_at ) VALUES (?, ?, ?, ?, ?, ?) """, ( f"pkg-{i}", "2026-08-2%d" % i, "2026-08-2%d" % (i + 1), json.dumps({"package_id": f"pkg-{i}"}), "2026-08-2%dT00:00:00Z" % i, "2026-08-2%dT01:00:00Z" % i, ), ) connection.commit() finally: connection.close() return path def test_upgrade_preserves_every_row(self, legacy_db, tmp_path): SharedMetricsStore( database_path=legacy_db, outbox_directory=tmp_path / "outbox" ) connection = sqlite3.connect(legacy_db) try: count = connection.execute("SELECT COUNT(*) FROM package_outbox").fetchone()[0] payloads = connection.execute( "SELECT package_id, payload_json FROM package_outbox ORDER BY package_id" ).fetchall() finally: connection.close() assert count == 3 assert payloads == [ ("pkg-0", '{"package_id": "pkg-0"}'), ("pkg-1", '{"package_id": "pkg-1"}'), ("pkg-2", '{"package_id": "pkg-2"}'), ] def test_upgrade_adds_the_send_columns(self, legacy_db, tmp_path): SharedMetricsStore( database_path=legacy_db, outbox_directory=tmp_path / "outbox" ) assert SEND_COLUMNS <= _columns(legacy_db) def test_upgrade_does_not_move_the_schema_version(self, legacy_db, tmp_path): """Bumping would make older builds refuse the same file.""" SharedMetricsStore( database_path=legacy_db, outbox_directory=tmp_path / "outbox" ) assert _schema_version(legacy_db) == "2" def test_migration_is_idempotent(self, legacy_db, tmp_path): for _ in range(3): SharedMetricsStore( database_path=legacy_db, outbox_directory=tmp_path / "outbox" ) columns = [ row[1] for row in sqlite3.connect(legacy_db).execute( "PRAGMA table_info(package_outbox)" ) ] assert len(columns) == len(set(columns)), "columns were added more than once" def test_queries_written_before_this_change_still_work(self, legacy_db, tmp_path): """The shipped export query selects named columns; it must be unaffected.""" SharedMetricsStore( database_path=legacy_db, outbox_directory=tmp_path / "outbox" ) connection = sqlite3.connect(legacy_db) try: rows = connection.execute( """ SELECT package_id, payload_json FROM package_outbox WHERE exported_at IS NULL ORDER BY created_at, package_id """ ).fetchall() finally: connection.close() assert rows == []