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()
|
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")
|
||||||
|
|||||||
Reference in New Issue
Block a user