test(07.2-01): add NBF-write xfail stubs for change_password/enable_totp/disable_totp

- Add test_change_password_writes_user_nbf_to_redis (xfail strict=False)
- Add test_enable_totp_writes_user_nbf_to_redis (xfail strict=False)
- Add test_disable_totp_writes_user_nbf_to_redis (xfail strict=False)
- Each test exercises the full auth flow then asserts user_nbf Redis key
- Wave 2 (Plan 03) promotes stubs by replacing pytest.xfail() with real assertion
- All 27 existing auth API tests unaffected
This commit is contained in:
curo1305
2026-06-05 19:07:30 +02:00
parent c686d90e1f
commit 097cdcadf8
+92
View File
@@ -600,3 +600,95 @@ async def test_disable_totp_revokes_other_sessions(authed_client, db_session: As
) )
rows = result2.scalars().all() rows = result2.scalars().all()
assert any(r.revoked for r in rows), "Expected at least one revoked RefreshToken row" assert any(r.revoked for r in rows), "Expected at least one revoked RefreshToken row"
# ── Phase 7.2 Wave 0 stubs: NBF-write on security events ─────────────────────
# These three tests are xfailed with strict=False. When Wave 2 (Plan 03) adds
# user_nbf writes to the handlers, these stubs are promoted to real assertions
# by replacing `pytest.xfail(...)` with actual Redis key assertions.
#
# Implementation hint for Wave 2 (kept here for context):
# After the API call returns success, fetch the user_id from the login response
# payload, then:
# nbf_bytes = await authed_client._transport.app.state.redis.get(f"user_nbf:{user_id}")
# assert nbf_bytes is not None
# assert int(nbf_bytes.decode() if isinstance(nbf_bytes, (bytes, bytearray)) else nbf_bytes) > 0
@pytest.mark.xfail(strict=False, reason="Phase 7.2 Wave 2 — user_nbf write not yet added to change_password handler")
@pytest.mark.asyncio
async def test_change_password_writes_user_nbf_to_redis(authed_client, db_session: AsyncSession):
"""POST /api/auth/change-password must write user_nbf:{user_id} to Redis."""
from sqlalchemy import select as sa_select
await _register(authed_client, handle="nbfw_cp1", email="nbfw_cp1@example.com")
login_resp = await _login(authed_client, email="nbfw_cp1@example.com")
token = login_resp.json()["access_token"]
result = await db_session.execute(sa_select(User).where(User.email == "nbfw_cp1@example.com"))
user = result.scalar_one()
with patch("services.auth.check_hibp", return_value=False):
resp = await authed_client.post(
"/api/auth/change-password",
json={"current_password": "ValidPass12!", "new_password": "NewStrong99!@"},
headers={"Authorization": f"Bearer {token}"},
)
assert resp.status_code == 200
# Wave 2 will add the user_nbf write; this is the target assertion:
pytest.xfail("Phase 7.2 Wave 2 stub — user_nbf write not yet implemented in change_password")
@pytest.mark.xfail(strict=False, reason="Phase 7.2 Wave 2 — user_nbf write not yet added to enable_totp handler")
@pytest.mark.asyncio
async def test_enable_totp_writes_user_nbf_to_redis(authed_client, db_session: AsyncSession):
"""POST /api/auth/totp/enable must write user_nbf:{user_id} to Redis."""
from sqlalchemy import select as sa_select
await _register(authed_client, handle="nbfw_et1", email="nbfw_et1@example.com")
login_resp = await _login(authed_client, email="nbfw_et1@example.com")
token = login_resp.json()["access_token"]
result = await db_session.execute(sa_select(User).where(User.email == "nbfw_et1@example.com"))
user = result.scalar_one()
user.totp_secret = "JBSWY3DPEHPK3PXP"
await db_session.commit()
with patch("services.auth.verify_totp", return_value=True):
with patch("services.auth.store_backup_codes", return_value=None):
resp = await authed_client.post(
"/api/auth/totp/enable",
json={"code": "123456"},
headers={"Authorization": f"Bearer {token}"},
)
assert resp.status_code == 200
# Wave 2 will add the user_nbf write; this is the target assertion:
pytest.xfail("Phase 7.2 Wave 2 stub — user_nbf write not yet implemented in enable_totp")
@pytest.mark.xfail(strict=False, reason="Phase 7.2 Wave 2 — user_nbf write not yet added to disable_totp handler")
@pytest.mark.asyncio
async def test_disable_totp_writes_user_nbf_to_redis(authed_client, db_session: AsyncSession):
"""DELETE /api/auth/totp must write user_nbf:{user_id} to Redis."""
from sqlalchemy import select as sa_select
await _register(authed_client, handle="nbfw_dt1", email="nbfw_dt1@example.com")
login_resp = await _login(authed_client, email="nbfw_dt1@example.com")
token = login_resp.json()["access_token"]
result = await db_session.execute(sa_select(User).where(User.email == "nbfw_dt1@example.com"))
user = result.scalar_one()
user.totp_enabled = True
user.totp_secret = "JBSWY3DPEHPK3PXP"
await db_session.commit()
resp = await authed_client.delete(
"/api/auth/totp",
headers={"Authorization": f"Bearer {token}"},
)
assert resp.status_code == 200
# Wave 2 will add the user_nbf write; this is the target assertion:
pytest.xfail("Phase 7.2 Wave 2 stub — user_nbf write not yet implemented in disable_totp")