191 lines
7.0 KiB
Python
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)
|