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
This commit is contained in:
parent
d7e5f2fec7
commit
4f45fe6037
11 changed files with 446 additions and 5 deletions
140
tests/test_ingest_sql_loadbearing.py
Normal file
140
tests/test_ingest_sql_loadbearing.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
"""I4 load-bearing seams — the ``sql`` path end-to-end (ingest spec §11's detach-RED regime).
|
||||
|
||||
The ``file`` seams (tests/test_ingest_loadbearing.py) are source-agnostic where they can be;
|
||||
these tests prove the SEAMS the ``sql`` source introduces — typed §5 rendering (the ``file``
|
||||
path is all-strings) and that a SQL-generated bundle flows through the SAME navigability and
|
||||
provenance machinery. Each test is built so the seam under test is the ONLY thing preventing
|
||||
the observable artifact (the Fase-2 green-but-dead trap).
|
||||
|
||||
- NAVIGABILITY (§2/§11): a SQL-generated bundle is consumable by the UNCHANGED
|
||||
``okf.navigate_bundle``/``bundle_context`` — reachable via index cross-links, classified by
|
||||
``okf_type``, provenance riding through. RED when index linking is detached.
|
||||
- TYPED NULL (§5): SQL NULL renders as the empty string, NEVER ``"None"``. RED when the None
|
||||
branch degrades to ``str(value)``.
|
||||
- TYPED REAL (§5): a REAL value keeps its shortest round-trip float form (4200.0 → ``4200.0``),
|
||||
NEVER integer-coerced. RED when the float branch degrades to ``str(int(value))``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from portfolio_optimiser import okf
|
||||
from portfolio_optimiser.ingest import materialize
|
||||
|
||||
_INGESTED_AT = "2026-07-03T12:00:00Z"
|
||||
|
||||
|
||||
def _make_db(path: Path, script: str) -> Path:
|
||||
conn = sqlite3.connect(path)
|
||||
try:
|
||||
conn.executescript(script)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
return path
|
||||
|
||||
|
||||
def _sql_manifest(tmp_path: Path, extractions: list[dict[str, Any]]) -> Path:
|
||||
manifest = {
|
||||
"manifest_version": 1,
|
||||
"source": {"type": "sql", "id": "db", "connection_ref": "PROJ_DB"},
|
||||
"bundle_summary": "SQL extracts from the project database.",
|
||||
"extractions": extractions,
|
||||
}
|
||||
path = tmp_path / "manifest.json"
|
||||
path.write_text(json.dumps(manifest), encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
def test_sql_generated_bundle_is_navigable_via_unchanged_okf(
|
||||
tmp_path: Path, monkeypatch: Any
|
||||
) -> None:
|
||||
"""LOAD-BEARING NAVIGABILITY: the SQL path produces a bundle the untouched OKF navigation
|
||||
reaches through index cross-links (classified by okf_type, provenance preserved) and
|
||||
``bundle_context`` renders. RED when index linking is detached (file exists, unreachable)."""
|
||||
db = _make_db(
|
||||
tmp_path / "s.sqlite",
|
||||
"CREATE TABLE costs (item TEXT, amount REAL);"
|
||||
" INSERT INTO costs VALUES ('led', 120.0), ('hvac', 80.0);",
|
||||
)
|
||||
monkeypatch.setenv("PROJ_DB", str(db))
|
||||
manifest = _sql_manifest(
|
||||
tmp_path,
|
||||
[
|
||||
{
|
||||
"id": "costs",
|
||||
"title": "Project costs",
|
||||
"query": "SELECT item, amount FROM costs ORDER BY item",
|
||||
"okf_type": "dataset",
|
||||
"max_rows": 100,
|
||||
}
|
||||
],
|
||||
)
|
||||
bundle_dir = tmp_path / "bundle"
|
||||
materialize(manifest, bundle_dir, ingested_at=_INGESTED_AT)
|
||||
|
||||
bundle = okf.navigate_bundle(str(bundle_dir))
|
||||
by_name = {f.name: f for f in bundle.files}
|
||||
assert "ingest-costs.md" in by_name
|
||||
assert by_name["ingest-costs.md"].type == "dataset"
|
||||
assert by_name["ingest-costs.md"].frontmatter["ingest_manifest"] # provenance rides through
|
||||
context = okf.bundle_context(bundle)
|
||||
assert "Project costs" in context and "| hvac | 80.0 |" in context
|
||||
|
||||
|
||||
def test_sql_null_renders_as_empty_not_none(tmp_path: Path, monkeypatch: Any) -> None:
|
||||
"""LOAD-BEARING TYPED NULL: detaching the None branch to ``str(value)`` would render the
|
||||
string ``None`` into the bundle; the empty cell is the ONLY correct §5 form."""
|
||||
db = _make_db(
|
||||
tmp_path / "s.sqlite",
|
||||
"CREATE TABLE costs (item TEXT, note TEXT); INSERT INTO costs VALUES ('sensor', NULL);",
|
||||
)
|
||||
monkeypatch.setenv("PROJ_DB", str(db))
|
||||
manifest = _sql_manifest(
|
||||
tmp_path,
|
||||
[
|
||||
{
|
||||
"id": "costs",
|
||||
"title": "Project costs",
|
||||
"query": "SELECT item, note FROM costs ORDER BY item",
|
||||
"okf_type": "dataset",
|
||||
"max_rows": 100,
|
||||
}
|
||||
],
|
||||
)
|
||||
bundle_dir = tmp_path / "bundle"
|
||||
materialize(manifest, bundle_dir, ingested_at=_INGESTED_AT)
|
||||
body = (bundle_dir / "ingest-costs.md").read_text(encoding="utf-8")
|
||||
assert "| sensor | |" in body # NULL → empty cell
|
||||
assert "None" not in body # RED if the None branch degrades to str()
|
||||
|
||||
|
||||
def test_sql_real_preserves_float_form(tmp_path: Path, monkeypatch: Any) -> None:
|
||||
"""LOAD-BEARING TYPED REAL: a REAL keeps its shortest round-trip float form; integer
|
||||
coercion (``4200``) is the detach this test catches."""
|
||||
db = _make_db(
|
||||
tmp_path / "s.sqlite",
|
||||
"CREATE TABLE costs (item TEXT, amount REAL); INSERT INTO costs VALUES ('kabel', 4200.0);",
|
||||
)
|
||||
monkeypatch.setenv("PROJ_DB", str(db))
|
||||
manifest = _sql_manifest(
|
||||
tmp_path,
|
||||
[
|
||||
{
|
||||
"id": "costs",
|
||||
"title": "Project costs",
|
||||
"query": "SELECT item, amount FROM costs ORDER BY item",
|
||||
"okf_type": "dataset",
|
||||
"max_rows": 100,
|
||||
}
|
||||
],
|
||||
)
|
||||
bundle_dir = tmp_path / "bundle"
|
||||
materialize(manifest, bundle_dir, ingested_at=_INGESTED_AT)
|
||||
body = (bundle_dir / "ingest-costs.md").read_text(encoding="utf-8")
|
||||
assert "| kabel | 4200.0 |" in body # float form preserved
|
||||
assert "| kabel | 4200 |" not in body # RED if integer-coerced
|
||||
Loading…
Add table
Add a link
Reference in a new issue