#!/usr/bin/env python3 """ Idempotency tests for economy_import.atlas's names-only pool populators (T-964 — Phase-4 test hardening). `populate_atlas_city_names` and `populate_atlas_feature_names` both clear their target table before reinserting (see each function's own docstring: "there is no UNIQUE(body_id, name[, feature_type]) — without the clear a re-run would accumulate duplicates"). This test proves that contract holds in practice: two back-to-back runs against the same DB must yield an IDENTICAL row count, not a doubled one, and Sol ('GJ 0') must remain permanently exempt (D-223) on both runs. Runs against a scratch COPY of the committed `server/data/systems.db` (real schema + real `bodies`/FK data — not a hand-rolled in-memory schema, which would drift from the real FK web this populator depends on). Wiki content (`wiki/star-systems/*/bodies/*/markers.json`) is read directly from the repo — read-only input, safe to reuse as-is; only the DB connection is scratch. Deliberately calls the two populator functions directly rather than the full `reach ledger import`: its first step shells out to the Rust `generate_brands` binary and overwrites `generated_brands.toml` on disk (`brands.regenerate_brands`), a real side effect on shared repo content that has nothing to do with atlas name-pool idempotency and would make this test depend on a Rust build. Stdlib only (unittest) — run directly or via `make test-tooling`: .venv/bin/python tooling/test_ledger_atlas_idempotency.py """ import shutil import sqlite3 import sys import tempfile import unittest from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from tooling.domains.ledger.economy_import import atlas # noqa: E402 from tooling.domains.ledger.economy_import.paths import DB_PATH # noqa: E402 class AtlasNamePoolIdempotencyTests(unittest.TestCase): """Two runs on a scratch DB copy must yield stable, non-doubled counts.""" @classmethod def setUpClass(cls): if not DB_PATH.exists(): raise unittest.SkipTest(f"committed systems.db not found at {DB_PATH}") def setUp(self): self._tmp = tempfile.TemporaryDirectory() scratch_path = Path(self._tmp.name) / "systems_scratch.db" shutil.copyfile(DB_PATH, scratch_path) self.conn = sqlite3.connect(str(scratch_path)) self.conn.execute("PRAGMA foreign_keys=ON") def tearDown(self): self.conn.close() self._tmp.cleanup() def test_atlas_city_names_count_stable_across_two_runs(self): first = atlas.populate_atlas_city_names(self.conn, dry_run=False) first_count = self.conn.execute( "SELECT COUNT(*) FROM atlas_city_names" ).fetchone()[0] self.assertEqual( first, first_count, "populator return value must match the rows it wrote" ) second = atlas.populate_atlas_city_names(self.conn, dry_run=False) second_count = self.conn.execute( "SELECT COUNT(*) FROM atlas_city_names" ).fetchone()[0] self.assertEqual( first_count, second_count, "a second run must yield an IDENTICAL atlas_city_names count, not " "a doubled one — the clear-before-reinsert contract must hold", ) self.assertEqual( first, second, "the populator's own return value must also be stable" ) self.assertGreater( first_count, 0, "fixture sanity: the committed wiki content must yield rows" ) def test_atlas_feature_names_count_stable_across_two_runs(self): # T-1169: atlas_feature_names landed alongside atlas_city_names but # is a separate table/populator — must be checked independently, not # assumed to share the city-name populator's idempotency by proximity. first = atlas.populate_atlas_feature_names(self.conn, dry_run=False) first_count = self.conn.execute( "SELECT COUNT(*) FROM atlas_feature_names" ).fetchone()[0] self.assertEqual(first, first_count) second = atlas.populate_atlas_feature_names(self.conn, dry_run=False) second_count = self.conn.execute( "SELECT COUNT(*) FROM atlas_feature_names" ).fetchone()[0] self.assertEqual( first_count, second_count, "a second run must yield an IDENTICAL atlas_feature_names count, not " "a doubled one — the clear-before-reinsert contract must hold", ) self.assertEqual(first, second) self.assertGreater( first_count, 0, "fixture sanity: the committed wiki content must yield rows" ) def test_sol_system_gj0_gets_zero_rows_in_both_pools_across_two_runs(self): # D-223 permanent exemption: Sol ('GJ 0') keeps authored, # geometry-bearing markers.json via sol_import.py — never the names # pool. Must hold on the FIRST run (not just "never accumulates") and # remain zero on the second. for run in (1, 2): atlas.populate_atlas_city_names(self.conn, dry_run=False) atlas.populate_atlas_feature_names(self.conn, dry_run=False) sol_cities = self.conn.execute( """SELECT COUNT(*) FROM atlas_city_names WHERE body_id IN (SELECT body_id FROM bodies WHERE system_id = 'GJ 0')""" ).fetchone()[0] sol_features = self.conn.execute( """SELECT COUNT(*) FROM atlas_feature_names WHERE body_id IN (SELECT body_id FROM bodies WHERE system_id = 'GJ 0')""" ).fetchone()[0] self.assertEqual( sol_cities, 0, f"run {run}: Sol (GJ 0) must have zero atlas_city_names rows" ) self.assertEqual( sol_features, 0, f"run {run}: Sol (GJ 0) must have zero atlas_feature_names rows", ) # Fixture sanity: GJ 0 must actually have bodies in this DB, or the # zero-rows assertions above would be vacuously true. gj0_body_count = self.conn.execute( "SELECT COUNT(*) FROM bodies WHERE system_id = 'GJ 0'" ).fetchone()[0] self.assertGreater( gj0_body_count, 0, "fixture sanity: GJ 0 (Sol) must have bodies in the committed DB, or " "the exemption checks above are vacuous", ) if __name__ == "__main__": unittest.main(verbosity=2)