fbd6602814
---ci--- phase: 0 milestone: v0.1 status: complete ---/ci---
121 lines
4.1 KiB
Python
121 lines
4.1 KiB
Python
"""Unit tests for the SQLite schema + async store (TASK-04-01, TASK-04-02)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import sqlite3
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from db.migrate import apply_migrations
|
|
from db.store import PraxisStore, HARDCODED_LEARNER_ID
|
|
|
|
|
|
@pytest.fixture
|
|
def tmp_db(tmp_path: Path) -> Path:
|
|
return tmp_path / "test_praxis.db"
|
|
|
|
|
|
def test_migration_creates_all_tables(tmp_db: Path):
|
|
"""TASK-04-01: migration creates learner, sessions, turns, progress."""
|
|
applied = apply_migrations(tmp_db)
|
|
assert "0001_init" in applied
|
|
|
|
conn = sqlite3.connect(str(tmp_db))
|
|
tables = {
|
|
r[0] for r in conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
|
|
}
|
|
conn.close()
|
|
assert {"learner", "sessions", "turns", "progress"} <= tables
|
|
|
|
|
|
def test_hardcoded_learner_row_exists(tmp_db: Path):
|
|
"""TASK-04-01: the hardcoded learner-1 'Alex' row exists (D-007, no auth)."""
|
|
apply_migrations(tmp_db)
|
|
conn = sqlite3.connect(str(tmp_db))
|
|
row = conn.execute(
|
|
"SELECT id, display_name FROM learner WHERE id = ?", (HARDCODED_LEARNER_ID,)
|
|
).fetchone()
|
|
conn.close()
|
|
assert row is not None
|
|
assert row[0] == "learner-1"
|
|
assert row[1] == "Alex"
|
|
|
|
|
|
def test_migrations_are_idempotent(tmp_db: Path):
|
|
"""Re-running migrations doesn't re-apply."""
|
|
apply_migrations(tmp_db)
|
|
applied = apply_migrations(tmp_db)
|
|
assert applied == []
|
|
|
|
|
|
def test_store_start_log_end_session(tmp_db: Path):
|
|
"""TASK-04-02: start session → log 3 turns → end session → query returns full session."""
|
|
store = PraxisStore(tmp_db)
|
|
|
|
async def _run():
|
|
await store.init()
|
|
sid = await store.start_session(HARDCODED_LEARNER_ID, "cs_refund_ca_v01")
|
|
await store.log_turn(sid, 0, "assistant", tts_text="Hi, I want a refund.", latency_ms=None)
|
|
await store.log_turn(sid, 1, "user", asr_text="I'm sorry, I can help.", latency_ms=450.0)
|
|
await store.log_turn(sid, 2, "assistant", tts_text="Okay, what's the issue?", latency_ms=520.0)
|
|
await store.end_session(
|
|
sid,
|
|
branch_path=["accept_resolution"],
|
|
outcome="success",
|
|
cost_cents=12,
|
|
cost_breakdown={"tokens": 500, "minutes": 1.2, "chars": 320},
|
|
debrief_text="You did well acknowledging the customer.",
|
|
)
|
|
sess = await store.get_session(sid)
|
|
turns = await store.get_turns(sid)
|
|
return sess, turns
|
|
|
|
sess, turns = asyncio.run(_run())
|
|
assert sess is not None
|
|
assert sess.learner_id == "learner-1"
|
|
assert sess.scenario_id == "cs_refund_ca_v01"
|
|
assert sess.outcome == "success"
|
|
assert sess.branch_path == ["accept_resolution"]
|
|
assert sess.cost_estimated_cents == 12
|
|
assert sess.debrief_text == "You did well acknowledging the customer."
|
|
assert sess.cost_breakdown["tokens"] == 500
|
|
assert len(turns) == 3
|
|
assert turns[0].role == "assistant"
|
|
assert turns[1].asr_text == "I'm sorry, I can help."
|
|
assert turns[2].latency_ms == 520.0
|
|
|
|
|
|
def test_store_update_progress(tmp_db: Path):
|
|
"""TASK-04-02: update_progress increments attempts + sets last_outcome."""
|
|
store = PraxisStore(tmp_db)
|
|
|
|
async def _run():
|
|
await store.init()
|
|
await store.update_progress(HARDCODED_LEARNER_ID, "cs_refund_ca_v01", "success")
|
|
await store.update_progress(HARDCODED_LEARNER_ID, "cs_refund_ca_v01", "failure")
|
|
|
|
async with store._connect() as db:
|
|
cur = await db.execute(
|
|
"SELECT attempts, last_outcome FROM progress WHERE learner_id = ? AND scenario_id = ?",
|
|
(HARDCODED_LEARNER_ID, "cs_refund_ca_v01"),
|
|
)
|
|
return await cur.fetchone()
|
|
|
|
row = asyncio.run(_run())
|
|
assert row is not None
|
|
assert row[0] == 2 # two attempts
|
|
assert row[1] == "failure" # last outcome
|
|
|
|
|
|
def test_store_get_learner(tmp_db: Path):
|
|
store = PraxisStore(tmp_db)
|
|
|
|
async def _run():
|
|
await store.init()
|
|
return await store.get_learner()
|
|
|
|
learner = asyncio.run(_run())
|
|
assert learner["id"] == "learner-1"
|
|
assert learner["display_name"] == "Alex" |