Files
praxis/tests/test_learner_ability_db.py
T
Praxis CI 813bd586d6 docs(milestone): merge v0.3-mastery-scoring → main
v0.3 milestone merged to main. Mastery scoring + competency rubrics +
verifiable credentials (formative-tier) shipped. 13/13 REQ-IDs covered.
Next milestone: v0.4 (operator tier — cohort dashboard + auth + Postgres).

---ci---
project: praxis
phase: 2
milestone: v0.3
status: complete
milestone_complete: true
milestone_merged_to_main: true
---/ci---
2026-08-04 00:14:59 +00:00

232 lines
7.0 KiB
Python

"""Integration tests for theta persistence (SLICE-04, TASK-04-04)."""
from __future__ import annotations
import asyncio
import json
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 _await(coro):
return asyncio.run(coro)
def test_migrations_apply_0003(tmp_db: Path):
applied = apply_migrations(tmp_db)
assert "0003_mastery" 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_ability", "mastery_progress"} <= tables
def test_migration_idempotent_run_twice(tmp_db: Path):
apply_migrations(tmp_db)
apply_migrations(tmp_db)
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_ability", "mastery_progress"} <= tables
def test_get_ability_returns_none_for_new_learner(tmp_db: Path):
store = PraxisStore(tmp_db)
async def _run():
await store.init()
return await store.get_ability(HARDCODED_LEARNER_ID, "customer_service")
assert _await(_run()) is None
def test_upsert_ability_round_trip(tmp_db: Path):
store = PraxisStore(tmp_db)
async def _run():
await store.init()
await store.upsert_ability(HARDCODED_LEARNER_ID, "customer_service", 0.5, 0.8, 7)
return await store.get_ability(HARDCODED_LEARNER_ID, "customer_service")
row = _await(_run())
assert row is not None
assert row["learner_id"] == HARDCODED_LEARNER_ID
assert row["path"] == "customer_service"
assert row["theta"] == pytest.approx(0.5)
assert row["sigma_sq"] == pytest.approx(0.8)
assert row["observations"] == 7
assert row["updated_at"] is not None
def test_upsert_ability_updates_existing(tmp_db: Path):
store = PraxisStore(tmp_db)
async def _run():
await store.init()
await store.upsert_ability(HARDCODED_LEARNER_ID, "customer_service", 0.0, 1.0, 1)
await store.upsert_ability(HARDCODED_LEARNER_ID, "customer_service", 1.2, 0.4, 8)
return await store.get_ability(HARDCODED_LEARNER_ID, "customer_service")
row = _await(_run())
assert row is not None
assert row["theta"] == pytest.approx(1.2)
assert row["sigma_sq"] == pytest.approx(0.4)
assert row["observations"] == 8
def test_default_values_for_new_learner_via_sql(tmp_db: Path):
apply_migrations(tmp_db)
conn = sqlite3.connect(str(tmp_db))
conn.execute(
"INSERT INTO learner_ability (learner_id, path) VALUES (?, ?)",
(HARDCODED_LEARNER_ID, "customer_service"),
)
conn.commit()
row = conn.execute(
"SELECT theta, sigma_sq, observations FROM learner_ability "
"WHERE learner_id = ? AND path = ?",
(HARDCODED_LEARNER_ID, "customer_service"),
).fetchone()
conn.close()
assert row is not None
assert row[0] == 0.0
assert row[1] == 1.0
assert row[2] == 0
def test_get_progress_returns_none_for_new_learner(tmp_db: Path):
store = PraxisStore(tmp_db)
async def _run():
await store.init()
return await store.get_progress(HARDCODED_LEARNER_ID, "customer_service")
assert _await(_run()) is None
def test_upsert_progress_round_trip(tmp_db: Path):
store = PraxisStore(tmp_db)
async def _run():
await store.init()
await store.upsert_progress(
HARDCODED_LEARNER_ID,
"customer_service",
current_week=3,
scenarios_passed=["cs_refund_ca_v01", "cs_escalation_ca_v02"],
mastery_score=3.7,
gate_open=False,
)
return await store.get_progress(HARDCODED_LEARNER_ID, "customer_service")
row = _await(_run())
assert row is not None
assert row["learner_id"] == HARDCODED_LEARNER_ID
assert row["path"] == "customer_service"
assert row["current_week"] == 3
assert json.loads(row["scenarios_passed_json"]) == [
"cs_refund_ca_v01",
"cs_escalation_ca_v02",
]
assert row["mastery_score"] == pytest.approx(3.7)
assert row["gate_open"] == 0
assert row["updated_at"] is not None
def test_upsert_progress_gate_open_true(tmp_db: Path):
store = PraxisStore(tmp_db)
async def _run():
await store.init()
await store.upsert_progress(
HARDCODED_LEARNER_ID,
"customer_service",
current_week=6,
scenarios_passed=["s1", "s2", "s3"],
mastery_score=4.0,
gate_open=True,
)
return await store.get_progress(HARDCODED_LEARNER_ID, "customer_service")
row = _await(_run())
assert row is not None
assert row["gate_open"] == 1
assert row["current_week"] == 6
def test_upsert_progress_updates_existing(tmp_db: Path):
store = PraxisStore(tmp_db)
async def _run():
await store.init()
await store.upsert_progress(
HARDCODED_LEARNER_ID,
"customer_service",
current_week=1,
scenarios_passed=[],
mastery_score=0.0,
gate_open=False,
)
await store.upsert_progress(
HARDCODED_LEARNER_ID,
"customer_service",
current_week=4,
scenarios_passed=["s1", "s2", "s3", "s4"],
mastery_score=3.9,
gate_open=True,
)
return await store.get_progress(HARDCODED_LEARNER_ID, "customer_service")
row = _await(_run())
assert row is not None
assert row["current_week"] == 4
assert json.loads(row["scenarios_passed_json"]) == ["s1", "s2", "s3", "s4"]
assert row["mastery_score"] == pytest.approx(3.9)
assert row["gate_open"] == 1
def test_ability_and_progress_isolated_per_path(tmp_db: Path):
store = PraxisStore(tmp_db)
async def _run():
await store.init()
await store.upsert_ability(HARDCODED_LEARNER_ID, "customer_service", 1.0, 0.5, 10)
await store.upsert_ability(HARDCODED_LEARNER_ID, "sales", -0.5, 0.9, 2)
await store.upsert_progress(
HARDCODED_LEARNER_ID, "customer_service", 2, ["s1"], 3.2, False
)
await store.upsert_progress(
HARDCODED_LEARNER_ID, "sales", 1, [], 0.0, False
)
a_cs = await store.get_ability(HARDCODED_LEARNER_ID, "customer_service")
a_sales = await store.get_ability(HARDCODED_LEARNER_ID, "sales")
p_cs = await store.get_progress(HARDCODED_LEARNER_ID, "customer_service")
p_sales = await store.get_progress(HARDCODED_LEARNER_ID, "sales")
return a_cs, a_sales, p_cs, p_sales
a_cs, a_sales, p_cs, p_sales = _await(_run())
assert a_cs["theta"] == pytest.approx(1.0)
assert a_sales["theta"] == pytest.approx(-0.5)
assert p_cs["current_week"] == 2
assert p_sales["current_week"] == 1