Files
praxis/tests/test_store.py
T
Praxis CI fbd6602814 docs(milestone): complete v0.1 foundation
---ci---
phase: 0
milestone: v0.1
status: complete
---/ci---
2026-08-01 13:32:48 +00:00

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"