dos-app-backend / tests /test_console_explorer.py
Tahasaif3's picture
Add console explorer routes and freeze dataset snapshots into items
bc0eb0e
Raw
History Blame Contribute Delete
9.37 kB
"""Covers the read/browse half of the console — conversations, dataset
snapshots and training jobs (`/v1/console/sessions|datasets|training-jobs`).
The chat/voice turns that produce this data are covered in
test_console_chat.py / test_console_voice.py."""
from app.database import SessionLocal
from app.models import Dataset, DatasetItem, TrainingRun
from app.services import model_client
async def _fake_get_reply(messages, language):
return f"echo:{messages[-1]['content']}", "groq"
def _signup(client, email: str) -> dict:
resp = client.post("/v1/auth/signup", json={
"name": "Rumi", "email": email, "company": "DOS", "password": "trainmodel123",
})
assert resp.status_code == 200, resp.text
return {"Authorization": f"Bearer {resp.json()['access_token']}"}
def _console_user(client, monkeypatch, email: str) -> dict:
"""Signs up an allowlisted operator and burns one chat turn so the
self-serve promotion to console_admin has actually happened."""
monkeypatch.setattr(model_client, "get_reply", _fake_get_reply)
monkeypatch.setenv("CONSOLE_ADMIN_EMAILS", email)
headers = _signup(client, email)
return headers
# ---------------------------------------------------------------------------
# Auth gating — same door as chat/voice
# ---------------------------------------------------------------------------
def test_explorer_routes_require_login(client):
for path in ("/v1/console/sessions", "/v1/console/datasets", "/v1/console/training-jobs"):
resp = client.get(path)
assert resp.status_code == 401, path
assert resp.json()["error"]["code"] == "UNAUTHORIZED"
def test_explorer_routes_forbidden_for_non_console_user(client, auth_headers, monkeypatch):
monkeypatch.delenv("CONSOLE_ADMIN_EMAILS", raising=False)
headers = auth_headers("+15557770001", "Driver Dana")
for path in ("/v1/console/sessions", "/v1/console/datasets", "/v1/console/training-jobs"):
resp = client.get(path, headers=headers)
assert resp.status_code == 403, path
# ---------------------------------------------------------------------------
# Conversations
# ---------------------------------------------------------------------------
def test_sessions_summarise_turns(client, monkeypatch):
headers = _console_user(client, monkeypatch, "explorer1@example.com")
first = client.post(
"/v1/console/chat", headers=headers, json={"text": "turn one"}
).json()
client.post(
"/v1/console/chat",
headers=headers,
json={"text": "turn two", "session_id": first["session_id"]},
)
rows = client.get("/v1/console/sessions", headers=headers).json()
assert len(rows) == 1
assert rows[0]["id"] == first["session_id"]
assert rows[0]["turn_count"] == 2
# Preview is the first thing said, not the most recent.
assert rows[0]["preview"] == "turn one"
assert rows[0]["last_activity"] >= rows[0]["started_at"]
def test_session_detail_returns_turns_in_order(client, monkeypatch):
headers = _console_user(client, monkeypatch, "explorer2@example.com")
started = client.post(
"/v1/console/chat", headers=headers, json={"text": "first"}
).json()
client.post(
"/v1/console/chat",
headers=headers,
json={"text": "second", "session_id": started["session_id"]},
)
detail = client.get(
f"/v1/console/sessions/{started['session_id']}", headers=headers
).json()
assert [t["input_text"] for t in detail["turns"]] == ["first", "second"]
assert detail["turns"][0]["output_text"] == "echo:first"
assert detail["turns"][0]["latency_ms"] is not None
def test_session_detail_404s_for_unknown_id(client, monkeypatch):
headers = _console_user(client, monkeypatch, "explorer3@example.com")
client.post("/v1/console/chat", headers=headers, json={"text": "hi"})
resp = client.get("/v1/console/sessions/not-a-real-session", headers=headers)
assert resp.status_code == 404
def test_sessions_are_shared_between_console_users(client, monkeypatch):
"""Console data is org-wide, not per-account: datasets and model versions
are built from every operator's turns, so every operator can review them."""
monkeypatch.setattr(model_client, "get_reply", _fake_get_reply)
monkeypatch.setenv("CONSOLE_ADMIN_EMAILS", "op-a@example.com,op-b@example.com")
a = _signup(client, "op-a@example.com")
b = _signup(client, "op-b@example.com")
client.post("/v1/console/chat", headers=a, json={"text": "logged by A"})
rows = client.get("/v1/console/sessions", headers=b).json()
assert len(rows) == 1
assert rows[0]["preview"] == "logged by A"
# ---------------------------------------------------------------------------
# Datasets
# ---------------------------------------------------------------------------
def test_build_dataset_rejects_empty_corpus(client, monkeypatch):
headers = _console_user(client, monkeypatch, "explorer4@example.com")
resp = client.post("/v1/console/datasets", headers=headers)
assert resp.status_code == 400
assert client.get("/v1/console/datasets", headers=headers).json() == []
def test_build_dataset_freezes_turns_into_items(client, monkeypatch):
"""The snapshot must copy the turns into dataset_item rows, not just
record a count — a dataset that re-queries interactions at train time
isn't frozen and can't be reproduced (C-12)."""
headers = _console_user(client, monkeypatch, "explorer5@example.com")
client.post("/v1/console/chat", headers=headers, json={"text": "keep me"})
client.post("/v1/console/chat", headers=headers, json={"text": "and me"})
resp = client.post("/v1/console/datasets", headers=headers)
assert resp.status_code == 201, resp.text
body = resp.json()
assert body["size"] == 2
assert body["frozen"] is True
assert body["language_mix"] == {"en": 2}
assert body["source_breakdown"] == {"console": 2}
db = SessionLocal()
try:
items = (
db.query(DatasetItem)
.filter(DatasetItem.dataset_id == body["id"])
.all()
)
assert len(items) == 2
assert {i.prompt for i in items} == {"keep me", "and me"}
assert {i.response for i in items} == {"echo:keep me", "echo:and me"}
finally:
db.close()
def test_build_dataset_snapshot_does_not_grow_with_later_turns(client, monkeypatch):
headers = _console_user(client, monkeypatch, "explorer6@example.com")
client.post("/v1/console/chat", headers=headers, json={"text": "before"})
snapshot = client.post("/v1/console/datasets", headers=headers).json()
client.post("/v1/console/chat", headers=headers, json={"text": "after"})
db = SessionLocal()
try:
items = (
db.query(DatasetItem).filter(DatasetItem.dataset_id == snapshot["id"]).all()
)
assert [i.prompt for i in items] == ["before"]
finally:
db.close()
def test_datasets_list_newest_first(client, monkeypatch):
headers = _console_user(client, monkeypatch, "explorer7@example.com")
client.post("/v1/console/chat", headers=headers, json={"text": "hi"})
first = client.post("/v1/console/datasets", headers=headers).json()
second = client.post("/v1/console/datasets", headers=headers).json()
rows = client.get("/v1/console/datasets", headers=headers).json()
assert [r["id"] for r in rows] == [second["id"], first["id"]]
assert rows[1]["name"] == "Console snapshot 1"
# ---------------------------------------------------------------------------
# Training jobs (read-only until the runner lands in v2)
# ---------------------------------------------------------------------------
def test_training_jobs_empty_by_default(client, monkeypatch):
headers = _console_user(client, monkeypatch, "explorer8@example.com")
assert client.get("/v1/console/training-jobs", headers=headers).json() == []
def test_training_jobs_resolve_dataset_name(client, monkeypatch):
headers = _console_user(client, monkeypatch, "explorer9@example.com")
client.post("/v1/console/chat", headers=headers, json={"text": "hi"})
dataset = client.post("/v1/console/datasets", headers=headers).json()
db = SessionLocal()
try:
db.add(TrainingRun(dataset_id=dataset["id"], method="lora", status="queued"))
db.commit()
finally:
db.close()
rows = client.get("/v1/console/training-jobs", headers=headers).json()
assert len(rows) == 1
assert rows[0]["dataset_id"] == dataset["id"]
assert rows[0]["dataset_name"] == dataset["name"]
assert rows[0]["status"] == "queued"
assert rows[0]["started_at"] is None
def test_dataset_rows_are_visible_to_every_console_user(client, monkeypatch):
monkeypatch.setattr(model_client, "get_reply", _fake_get_reply)
monkeypatch.setenv("CONSOLE_ADMIN_EMAILS", "op-c@example.com,op-d@example.com")
c = _signup(client, "op-c@example.com")
d = _signup(client, "op-d@example.com")
client.post("/v1/console/chat", headers=c, json={"text": "shared corpus"})
client.post("/v1/console/datasets", headers=c)
rows = client.get("/v1/console/datasets", headers=d).json()
assert len(rows) == 1
db = SessionLocal()
try:
assert db.query(Dataset).count() == 1
finally:
db.close()