Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Normalize line endings: LF in the repo, regardless of OS checkout.
# A Windows clone with core.autocrlf=true would otherwise silently
# recode the golden fixtures to CRLF, breaking the byte-identical
# digest this repo's contract tests pin against (ADR 013 §6 RC5,
# tests/fixtures/querydescriptor_golden.json.sha256).

* text=auto eol=lf
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ jobs:
- name: Sync dev dependencies
run: uv sync --frozen
- name: Run pytest
run: uv run pytest --color=yes
# -v names every collected test in the log with its outcome
# (PASSED/FAILED/SKIPPED). Plain `pytest --color=yes` only prints a
# dot per file: a test that runs and a test that is present but
# never collected are indiscernible in that output (ADR 013 §1.3.2,
# RC5) — -v is the minimum needed to tell them apart.
run: uv run pytest --color=yes -v

deps-audit:
name: Dependency audit (pip-audit)
Expand Down
15 changes: 15 additions & 0 deletions tests/fixtures/querydescriptor_golden.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"table": "players",
"where": [
{ "column": "summoner_name", "op": "=", "value": "GIDEON" },
{ "column": "deleted_at", "op": "IS NULL", "value": null }
],
"joins": [
{ "table": "scores", "on": ["id", "player_id"], "select": ["points"] }
],
"select": ["summoner_name", "region"],
"order": [
{ "column": "points", "direction": "desc" }
],
"limit": 5
}
1 change: 1 addition & 0 deletions tests/fixtures/querydescriptor_golden.json.sha256
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
23e10e33f0ebbb01a212c038b3ec24c3b546e81f7ddc4251009856907026c478 querydescriptor_golden.json
79 changes: 79 additions & 0 deletions tests/test_descriptor_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Cross-repo QueryDescriptor contract test — the QueryMe arm (ADR 013 §6 RC5).

QueryMe owns the ``QueryDescriptor`` schema, so this arm is the reference
point the other two representations are pinned against:

1. **QueryMe** ``QueryDescriptor`` Pydantic model (this file) — the golden
must validate and round-trip through it.
2. **Blue's preview executor** ``core.db.*`` builder chain
(``Blue/tests/test_querydescriptor_contract.py``) — pinned against a
byte-identical copy of the same golden.
3. **Orion** ``queryDescriptor`` Go struct
(``Orion/internal/runtime/compute_db_test.go::TestQueryDescriptor_GoldenParity``)
— pinned against its own byte-identical copy.

A field rename on any side breaks its own arm loudly, so the three
representations can never silently drift apart on the fields they carry.
Key order is not part of that guarantee: this arm compares parsed dicts,
never raw text, so a reordered-but-equal golden re-hashed into a fresh
sidecar would still pass — the contract this test pins is field names and
values, not on-disk byte order of an object's keys (only the golden
file's own bytes are pinned byte-for-byte, by the sidecar check above).

QueryMe does not consume the ``blue-runtime-go`` module (``pyproject.toml``
has no path into it — Python cannot import a Go artefact), so this arm has
no digest-pinned authenticity channel back to Blue's published fixture
table: it is the ADR 013 G7 case, its inventory entry is **attested**, not
verified, and this test is the whole of its local control. The one thing
it *can* check on its own is that the copy of the golden it ships has not
drifted from the digest recorded alongside it
(``tests/fixtures/querydescriptor_golden.json.sha256``) — the same
self-consistency channel §3.4.1 gives every implementer, computed here
rather than trusted from a hand-copied literal.
"""

from __future__ import annotations

import hashlib
import json
from pathlib import Path
from typing import Any

from queryme.descriptor import QueryDescriptor

_FIXTURES_DIR = Path(__file__).parent / "fixtures"
_GOLDEN_PATH = _FIXTURES_DIR / "querydescriptor_golden.json"
_SIDECAR_PATH = _FIXTURES_DIR / "querydescriptor_golden.json.sha256"

_GOLDEN_BYTES = _GOLDEN_PATH.read_bytes()
_GOLDEN = json.loads(_GOLDEN_BYTES)


def _canonical(d: dict[str, Any]) -> dict[str, Any]:
"""Normalise a descriptor dict for cross-arm comparison: drop a
top-level ``offset`` that is absent/None. The golden carries no
``offset`` key at all; Pydantic fills the field with its ``None``
default on validation, so a direct dict comparison would fail on that
field alone without touching the contract this test actually guards.
"""
return {k: v for k, v in d.items() if not (k == "offset" and v is None)}


def test_golden_fixture_matches_its_sidecar_digest() -> None:
"""Local self-consistency channel (§3.4.1): the copy of the golden this
repo ships has not drifted from the digest recorded next to it. This is
QueryMe's only local control — it has no module-pinned digest to check
against (G7) — so the sidecar must be produced from the real file, not
hand-copied, or this test would pass while proving nothing.
"""
want = hashlib.sha256(_GOLDEN_BYTES).hexdigest()
got_line = _SIDECAR_PATH.read_text(encoding="utf-8").strip()
got = got_line.split()[0]
assert got == want, f"sidecar records {got}, golden file actually hashes to {want}"


def test_queryme_model_round_trips_golden() -> None:
"""The schema owner accepts the golden and re-emits the same shape."""
desc = QueryDescriptor.model_validate(_GOLDEN)
dumped = desc.model_dump(mode="json")
assert _canonical(dumped) == _canonical(_GOLDEN)
Loading