Files
6krrt/tests/test_no_header_snapshot.py

191 lines
7.0 KiB
Python

"""Baseline no-header route_decisions snapshot (Task 3, pre-wiring fixture).
A committed test cannot read a gitignored ``.omo/evidence/`` path on a clean
checkout, so the pre-wiring ``route_decisions`` row for a HEADERLESS request
is frozen as an ordinary JSON fixture under ``tests/fixtures/`` and committed
as its OWN commit BEFORE the wiring that adds conversation identity.
The Success Criteria [H6] for identity wiring requires this snapshot to
reproduce byte-identically after wiring: a no-header request must still land
under the same session fingerprint with NULL agent/parent_key, so nothing
about the row may change. Columns that record measured wall-clock time are
excluded (``classifier_ms``, ``router_wall_seconds``, ``router_ttft_seconds``,
``observed_at``); ``id`` is excluded as it is an autoincrement.
This module owns the shared dump/compare helpers so ``test_conversation_wiring.py``
can reuse the exact same normalization when it asserts the byte-identical
snapshot as acceptance case 3.
"""
import json
import sqlite3
from pathlib import Path
import pytest
from starlette.testclient import TestClient
import dispatcher
from config import RoutingProfile
from dispatcher import Classification, app
FIXTURES_DIR = Path(__file__).parent / "fixtures"
SNAPSHOT_PATH = FIXTURES_DIR / "route_decision_no_header.json"
# Columns that record measured wall-clock time (or an autoincrement id) must
# never appear in the snapshot, or the frozen row would drift run to run.
# [H6] requires router_wall_seconds and router_ttft_seconds in this list.
EXCLUDED_COLS = (
"id",
"observed_at",
"classifier_ms",
"router_wall_seconds",
"router_ttft_seconds",
)
CHEAP = "cheap-model"
DEAR = "dear-model"
class FakeResponse:
"""Just enough of requests.Response for the dispatcher path."""
def __init__(self, payload=None, *, status_code=200):
self.status_code = status_code
self._payload = payload or {}
self.text = json.dumps(self._payload)
self.headers = {}
self.request = None
def json(self):
return self._payload
def _completion(model):
return {
"id": "chatcmpl-test",
"object": "chat.completion",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "ok"},
"finish_reason": "stop",
}
],
"usage": {"prompt_tokens": 30, "completion_tokens": 5},
"model": model,
}
def make_router(tmp_path, monkeypatch):
"""Point the dispatcher at a throwaway catalog with nothing dialled out.
Mirrors the ``router`` fixture in ``test_chat_completions.py``: two models
with a classifying stub and a fake requests.post, so one routed POST is
deterministic and offline. Returns ``(TestClient, db_path)``.
"""
db_path = tmp_path / "test.db"
conn = sqlite3.connect(db_path)
schema = (Path(__file__).resolve().parent.parent / "config" / "schema.sql").read_text()
conn.executescript(schema)
for model_id, completion_price, vision in (
(CHEAP, 0.30, 1),
(DEAR, 9.00, 0),
):
conn.execute(
"""
INSERT INTO models (
model_id, provider, base_model_id, tier, context_window,
effective_context_window, max_output_tokens,
cost_per_1m_prompt, cost_per_1m_completion,
supports_vision, supports_json_mode,
latency_class, reasoning_mode, context_variant,
access_level, availability, last_updated
) VALUES (?, 'neuralwatt', ?, 2, 262128, 192500, 16384, ?, ?,
?, 1, 'standard', 'default', 'full', 'public', 'active',
'2026-08-22T00:00:00+00:00')
""",
(model_id, model_id, completion_price / 3, completion_price, vision),
)
conn.commit()
conn.close()
monkeypatch.setattr(dispatcher.cfg.database, "path", str(db_path))
monkeypatch.setattr(dispatcher.cfg.verification, "local_llm_enabled", False)
monkeypatch.setattr(dispatcher.cfg.local_vision, "enabled", False)
monkeypatch.setattr(dispatcher.cfg.session_cache, "enabled", False)
monkeypatch.setattr(dispatcher.cfg.objective, "incumbent_cache_pricing", False)
monkeypatch.setattr(dispatcher.cfg.exploration, "epsilon", 0.0)
monkeypatch.setenv("NEURALWATT_API_KEY", "test-key")
def fake_post(url, headers=None, json=None, stream=False, timeout=None):
return FakeResponse(_completion(json["model"]))
monkeypatch.setattr(dispatcher.requests, "post", fake_post)
monkeypatch.setattr(
dispatcher, "classify",
lambda task, context: Classification(
task_category="coding_general", task_tier=2,
required_context_tokens=100, confidence=0.9,
),
)
return TestClient(app), db_path
def no_header_payload():
return {"model": "auto", "messages": [{"role": "user", "content": "write a function"}]}
def post_no_header(client):
"""POST one headerless routed request, returning the raw response."""
return client.post("/v1/chat/completions", json=no_header_payload())
def normalized_route_decision(db_path):
"""The single route_decisions row as a normalized dict for snapshotting.
Returns the row with ``id``/``observed_at``/``classifier_ms``/
``router_wall_seconds``/``router_ttft_seconds`` dropped and None-values
kept (so agent/parent_key NULL is visible and byte-stable).
"""
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
row = conn.execute(
"SELECT * FROM route_decisions ORDER BY id DESC LIMIT 1"
).fetchone()
conn.close()
assert row is not None, "expected exactly one route_decisions row"
return {
key: row[key]
for key in row.keys()
if key not in EXCLUDED_COLS and not key.startswith("__")
}
def serialize(data):
"""Byte-stable JSON serialization for the snapshot comparison."""
return json.dumps(data, sort_keys=True, indent=2, ensure_ascii=True) + "\n"
def assert_matches_snapshot(db_path):
"""Assert the current no-header row reproduces the committed snapshot."""
current = normalized_route_decision(db_path)
assert SNAPSHOT_PATH.exists(), (
f"snapshot fixture missing; run the generator to write {SNAPSHOT_PATH}"
)
expected = json.loads(SNAPSHOT_PATH.read_text())
assert current == expected, (
"no-header route_decisions row drifted from committed snapshot:\n"
f"current={serialize(current)}\n"
f"expected={serialize(expected)}"
)
# Byte-identical: the serialized forms must match exactly.
assert serialize(current) == SNAPSHOT_PATH.read_text()
def test_no_header_route_decision_matches_committed_snapshot(tmp_path, monkeypatch):
"""A headerless request reproduces the committed pre-wiring snapshot."""
client, db_path = make_router(tmp_path, monkeypatch)
resp = post_no_header(client)
assert resp.status_code == 200, resp.text
assert_matches_snapshot(db_path)