🚧 Add sqlite extraction

This commit is contained in:
Jonas Bengtsson
2026-09-12 23:32:19 +02:00
parent e773703ed3
commit 2e24683347
11 changed files with 988 additions and 19 deletions
+126
View File
@@ -169,6 +169,132 @@ class TestRunCycle:
assert "Station PS101/räumliche Datei.xml" in files
class TestExtraction:
"""Section 45: derived artifacts flow through the full collection."""
def _make_sqlite(self, git_repo: Path) -> Path:
import sqlite3
history = git_repo / "history"
history.mkdir(exist_ok=True)
db = history / "alarms.sqlite"
connection = sqlite3.connect(db)
connection.execute("CREATE TABLE alarms (id INTEGER PRIMARY KEY, text TEXT)")
connection.execute("INSERT INTO alarms VALUES (1, 'alpha')")
connection.commit()
connection.close()
return db
def _config_with_extraction(self, tracker_config: dict) -> dict:
tracker_config["extraction"] = {
"enabled": True,
"output_root": "extracted",
"format": "jsonl",
"sources": [{"path": "history/alarms.sqlite"}],
}
tracker_config["files"]["exclude"].append("**/*.sqlite")
return tracker_config
def test_first_run_commits_artifacts(
self, git_repo: Path, tracker_config: dict, tmp_path: Path
) -> None:
self._make_sqlite(git_repo)
config = self._config_with_extraction(tracker_config)
outcome = _run(config, tmp_path)
# artifacts only: raw DB excluded, no other changes
assert outcome.change_count == 1
assert outcome.commit_hash is not None
body = _git_out("log", "-1", "--format=%b", cwd=git_repo)
assert "extracted/alarms/alarms.jsonl" in body
tracked = _git_out("ls-files", "-z", cwd=git_repo)
assert "alarms.sqlite" not in tracked # raw DB excluded
content = (git_repo / "extracted" / "alarms" / "alarms.jsonl").read_text(
encoding="utf-8"
)
assert '{"id":1,"text":"alpha"}' in content
def test_unchanged_db_no_second_commit(
self, git_repo: Path, tracker_config: dict, tmp_path: Path
) -> None:
self._make_sqlite(git_repo)
config = self._config_with_extraction(tracker_config)
_run(config, tmp_path)
outcome = _run(config, tmp_path)
assert outcome.change_count == 0 # FR-031: byte-identical artifacts
assert outcome.commit_hash is None
def test_changed_row_produces_artifact_commit(
self, git_repo: Path, tracker_config: dict, tmp_path: Path
) -> None:
import sqlite3
db = self._make_sqlite(git_repo)
config = self._config_with_extraction(tracker_config)
_run(config, tmp_path)
connection = sqlite3.connect(db)
connection.execute("INSERT INTO alarms VALUES (2, 'beta')")
connection.commit()
connection.close()
outcome = _run(config, tmp_path)
assert outcome.change_count == 1
body = _git_out("log", "-1", "--format=%b", cwd=git_repo)
assert "M ; extracted/alarms/alarms.jsonl" in body
def test_failed_source_skipped_with_warning_others_extracted(
self, git_repo: Path, tracker_config: dict, tmp_path: Path
) -> None:
"""Section 45.7: per-source isolation - one bad file, others proceed."""
self._make_sqlite(git_repo)
# A second, non-database source alongside the valid one
(git_repo / "history" / "impostor.db").write_text(
"definitely not a database", encoding="utf-8"
)
config = self._config_with_extraction(tracker_config)
config["extraction"]["sources"].append({"path": "history/impostor.db"})
config["files"]["exclude"].append("history/impostor.db") # raw file not tracked
outcome = _run(config, tmp_path)
# The valid source was still extracted and committed
assert outcome.change_count == 1
assert outcome.commit_hash is not None
body = _git_out("log", "-1", "--format=%b", cwd=git_repo)
assert "extracted/alarms/alarms.jsonl" in body
# Nothing was extracted for the bad source
assert not (git_repo / "extracted" / "impostor").exists()
def test_non_sqlite_file_not_committed_as_artifact(
self, git_repo: Path, tracker_config: dict, tmp_path: Path
) -> None:
"""A non-database source is skipped, never dumped or committed."""
history = git_repo / "history"
history.mkdir(exist_ok=True)
(git_repo / "history" / "notadb.sqlite").write_text(
"plain text wearing a database name", encoding="utf-8"
)
config = self._config_with_extraction(tracker_config)
config["extraction"]["sources"] = [{"path": "history/notadb.sqlite"}]
outcome = _run(config, tmp_path)
assert outcome.change_count == 0 # nothing extracted, nothing committed
assert not (git_repo / "extracted" / "notadb").exists()
def test_dry_run_skips_refresh(
self, git_repo: Path, tracker_config: dict, tmp_path: Path, caplog
) -> None:
self._make_sqlite(git_repo)
config = self._config_with_extraction(tracker_config)
before = _git_out("status", "--porcelain", "-z", cwd=git_repo)
outcome = _run(config, tmp_path, dry_run=True)
assert outcome.dry_run
# Artifacts never created: the refresh is the step that writes them
assert (git_repo / "extracted").exists() is False # tree untouched
after = _git_out("status", "--porcelain", "-z", cwd=git_repo)
assert before == after
class TestSafetyAborts:
def test_pre_existing_staged_changes_abort(
self, git_repo: Path, tracker_config: dict, tmp_path: Path