portfolio-optimiser/tests/test_ingest_golden_sql.py
Kjell Tore Guttormsen 4f45fe6037 test(ingest): SQL connector + load-bearing + golden, detach-proven (I4)
New: test_ingest_sql.py (connector mechanics + typed rendering + dispatch), test_ingest_sql_loadbearing.py (SQL seams: typed-NULL, typed-REAL, navigability via unchanged okf), test_ingest_golden_sql.py + examples/ingest-golden-sql/ (byte-frozen sqlite golden; NULL->empty, 4200.0->4200.0 and escape discriminate a typed impl from naive ones). Updated: sql-source refusal test repointed to http (I6); manifest schema-breadth comment. Each new seam proven RED at its detach point.

Refs: shared/ingest-spec.md 11 golden + load-bearing table
2026-07-04 06:56:02 +02:00

47 lines
2 KiB
Python

"""I4 golden regression — the §11 golden extraction case for `sql`, byte-for-byte.
Mirrors tests/test_ingest_golden.py for the second source type. The case pins the §5 SQL type
rules AS RULES: SQL NULL → empty cell (never ``None``), REAL 4200.0 → ``4200.0`` (float form
preserved, not integer-coerced), non-integral 1200.5/89.9, and the escape order on the sql path
(``|`` → ``\\|``, ``\\`` → ``\\\\``). RED when ANY byte of the expected bundle diverges (spec
§11 "Golden regression" seam).
The fixture db is committed under ``fixture/`` (rebuild:
``.claude/projects/2026-07-04-i4-ingest-sql-maf/build_fixture.py``); the ``connection_ref`` env
var is set to it (the §4 run-time resolution). The db PATH never enters the bundle, so the
golden is checkout-location independent.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from portfolio_optimiser.ingest import materialize
GOLDEN = Path(__file__).resolve().parents[1] / "examples" / "ingest-golden-sql"
def _bundle_bytes(directory: Path) -> dict[str, bytes]:
return {p.name: p.read_bytes() for p in sorted(directory.iterdir()) if p.is_file()}
def test_golden_sql_extraction_is_bit_deterministic(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
ingested_at = (GOLDEN / "ingested-at.txt").read_text(encoding="utf-8").strip()
monkeypatch.setenv("PORTEFOLJE_SQL_DSN", str(GOLDEN / "fixture" / "portefolje.sqlite"))
out = tmp_path / "bundle"
materialize(GOLDEN / "manifest.json", out, ingested_at=ingested_at)
expected = _bundle_bytes(GOLDEN / "expected-bundle")
actual = _bundle_bytes(out)
# File-SET equality first — catches extra AND missing files, not just diverging bytes.
assert actual.keys() == expected.keys()
for name, content in expected.items():
assert actual[name] == content, f"golden byte divergence in {name}"
# §10: a second run over the same output leaves every byte unchanged.
materialize(GOLDEN / "manifest.json", out, ingested_at=ingested_at)
assert _bundle_bytes(out) == expected