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:
@@ -600,3 +600,95 @@ async def test_disable_totp_revokes_other_sessions(authed_client, db_session: As
|
||||
)
|
||||
rows = result2.scalars().all()
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user