Files
praxis/tests/test_credential_status_techdebt.py
T
Praxis CI ec397f2c65 docs(milestone): complete v0.5-live-assist — v0.1.13 tagged, milestone release, merged to main
v0.5 (Live Assist — on-the-job voice companion) milestone complete.
4 phases: P0 (pre-execution, v0.1.10) → P1 (assist core + guardrail,
v0.1.11) → P2 (integration + tech-debt + NFR, v0.1.12) → P3 (final
review + ship, v0.1.13 = milestone release).

16/16 REQs covered (3 ASSIST + 4 NFR + 9 IDEATE). 4 v0.6 backlog.
469 tests passed, 0 failed. 1 P0 fixed (guardrail processor safety).
8 P1+ flagged for v0.6. 8 v0.4 P1+ tech-debt addressed.
G-049 + G-067 grill MUSTs resolved. ESCALATION-01 (PIPEDA) OPEN for
human legal review before assist surface go-live.

---ci---
project: praxis
phase: 3
milestone: v0.5
status: complete
requirements:
  covered: [REQ-ASSIST-01, REQ-ASSIST-02, REQ-ASSIST-03, REQ-NFR-ASSIST-01, REQ-NFR-ASSIST-02, REQ-NFR-ASSIST-03, REQ-NFR-ASSIST-04, REQ-IDEATE-01, REQ-IDEATE-02, REQ-IDEATE-03, REQ-IDEATE-04, REQ-IDEATE-05, REQ-IDEATE-06, REQ-IDEATE-07, REQ-IDEATE-08, REQ-IDEATE-09]
  partial: []
---/ci---
2026-08-04 22:35:56 +00:00

106 lines
4.1 KiB
Python

"""Mock-based tests for set_credential_status enum + f-string SQL fix
(TASK-12-03, P1+ #4/#8 from v0.4 REVIEW).
These tests do NOT require Postgres (they use a mock asyncpg pool). They
verify:
- 'revoked' uses a parameterized query with revoked_at=now() (no f-string).
- 'active' clears revoked_at=NULL (re-activation).
- Invalid status → ValueError (enum validation — P1+ #4).
- No f-string interpolation in the SQL (P1+ #8 code smell fix).
"""
from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock
import pytest
from db.pg_store import PgStore
def _mock_pool_with_conn():
"""Build a mock asyncpg pool + conn that records execute() calls."""
pool = MagicMock()
conn = MagicMock()
conn.execute = AsyncMock()
cm = MagicMock()
cm.__aenter__ = AsyncMock(return_value=conn)
cm.__aexit__ = AsyncMock(return_value=None)
pool.acquire = MagicMock(return_value=cm)
return pool, conn
@pytest.mark.asyncio
async def test_set_credential_status_revoked_uses_parameterized_query():
"""TASK-12-03 (P1+ #8): 'revoked' uses a parameterized query (no f-string)."""
pool, conn = _mock_pool_with_conn()
store = PgStore(pool)
await store.set_credential_status("cred-1", "revoked")
# Exactly one execute call.
assert conn.execute.await_count == 1
sql, status_arg, cred_arg = conn.execute.await_args.args
# No f-string interpolation — the SQL is a literal with $1, $2.
assert "revoked_at = now()" in sql
assert "$1" in sql and "$2" in sql
assert status_arg == "revoked"
assert cred_arg == "cred-1"
@pytest.mark.asyncio
async def test_set_credential_status_active_clears_revoked_at():
"""TASK-12-03: 'active' clears revoked_at=NULL (re-activation)."""
pool, conn = _mock_pool_with_conn()
store = PgStore(pool)
await store.set_credential_status("cred-1", "active")
assert conn.execute.await_count == 1
sql, status_arg, cred_arg = conn.execute.await_args.args
assert "revoked_at = NULL" in sql
assert status_arg == "active"
assert cred_arg == "cred-1"
@pytest.mark.asyncio
async def test_set_credential_status_invalid_raises_value_error():
"""TASK-12-03 (P1+ #4): invalid status → ValueError (enum validation)."""
pool, conn = _mock_pool_with_conn()
store = PgStore(pool)
for bad_status in ("pending", "suspended", "deleted", "", "REVOKED", "active "):
with pytest.raises(ValueError, match="Invalid credential status"):
await store.set_credential_status("cred-1", bad_status)
# No execute call should have been made (validation happens before the query).
assert conn.execute.await_count == 0
@pytest.mark.asyncio
async def test_set_credential_status_no_fstring_in_sql():
"""TASK-12-03 (P1+ #8): no f-string interpolation in the SQL (code smell fix).
The SQL must be a literal string (no f-string {extra} interpolation). The
status + cred_id are bound parameters ($1, $2), not interpolated.
"""
pool, conn = _mock_pool_with_conn()
store = PgStore(pool)
await store.set_credential_status("cred-1", "revoked")
sql = conn.execute.await_args.args[0]
# The SQL must NOT contain an f-string-interpolated extra clause. The old
# code had f"UPDATE ... SET status = $1{extra} WHERE id = $2" where extra
# was ', revoked_at = now()' or ''. The new code has two explicit queries.
# Verify the SQL is a literal (no {extra}-style interpolation artifacts).
assert "{extra}" not in sql
assert "UPDATE issued_credentials SET status = $1, revoked_at = now()" in sql
@pytest.mark.asyncio
async def test_set_credential_status_revoked_then_active():
"""TASK-12-03: revoke then re-activate (active clears revoked_at)."""
pool, conn = _mock_pool_with_conn()
store = PgStore(pool)
# Revoke.
await store.set_credential_status("cred-1", "revoked")
revoke_sql = conn.execute.await_args.args[0]
assert "revoked_at = now()" in revoke_sql
# Re-activate (active clears revoked_at).
conn.execute.reset_mock()
await store.set_credential_status("cred-1", "active")
active_sql = conn.execute.await_args.args[0]
assert "revoked_at = NULL" in active_sql