- New: test_security_headers.py — verify all headers present, HSTS conditional on HTTPS - New: test_passkey_error_handling.py — generic vs user-facing error messages - New: test_poi_validation.py — field length and coordinate range constraints - Extend test_rate_limiter.py — client IP depth selection, in-memory fallback enforcement - Extend test_models.py — sqm range validation - Extend test_task_service.py — IDOR 404, ownership 200, traceback suppression in production
71 lines
3 KiB
Python
71 lines
3 KiB
Python
"""Unit tests for passkey route error handling."""
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
import pytest
|
|
from starlette.testclient import TestClient
|
|
|
|
# We need to test through the FastAPI app or build a minimal test client
|
|
from api.passkey_routes import passkey_router
|
|
from fastapi import FastAPI
|
|
|
|
|
|
def _build_app() -> FastAPI:
|
|
app = FastAPI()
|
|
app.include_router(passkey_router)
|
|
return app
|
|
|
|
|
|
class TestPasskeyErrorHandling:
|
|
"""Tests that passkey routes return generic error messages for internal exceptions."""
|
|
|
|
@patch("api.passkey_routes.passkey_service")
|
|
@patch("api.passkey_routes.UserRepository")
|
|
def test_register_begin_internal_error_returns_generic_message(
|
|
self, mock_user_repo: MagicMock, mock_service: MagicMock
|
|
) -> None:
|
|
mock_service.begin_registration.side_effect = RuntimeError("DB connection lost")
|
|
client = TestClient(_build_app())
|
|
resp = client.post("/api/passkey/register/begin", json={"email": "test@example.com"})
|
|
assert resp.status_code == 400
|
|
assert resp.json()["detail"] == "Registration failed. Please try again."
|
|
assert "DB connection lost" not in resp.json()["detail"]
|
|
|
|
@patch("api.passkey_routes.passkey_service")
|
|
@patch("api.passkey_routes.UserRepository")
|
|
def test_register_begin_value_error_returns_user_message(
|
|
self, mock_user_repo: MagicMock, mock_service: MagicMock
|
|
) -> None:
|
|
mock_service.begin_registration.side_effect = ValueError("Email already registered")
|
|
client = TestClient(_build_app())
|
|
resp = client.post("/api/passkey/register/begin", json={"email": "test@example.com"})
|
|
assert resp.status_code == 400
|
|
assert resp.json()["detail"] == "Email already registered"
|
|
|
|
@patch("api.passkey_routes.passkey_service")
|
|
@patch("api.passkey_routes.UserRepository")
|
|
def test_login_complete_internal_error_returns_generic_message(
|
|
self, mock_user_repo: MagicMock, mock_service: MagicMock
|
|
) -> None:
|
|
mock_service.complete_authentication.side_effect = RuntimeError("Crypto failure")
|
|
client = TestClient(_build_app())
|
|
resp = client.post(
|
|
"/api/passkey/login/complete",
|
|
json={"session_id": "abc", "credential": {"id": "x"}},
|
|
)
|
|
assert resp.status_code == 400
|
|
assert resp.json()["detail"] == "Login could not be completed."
|
|
assert "Crypto failure" not in resp.json()["detail"]
|
|
|
|
@patch("api.passkey_routes.passkey_service")
|
|
@patch("api.passkey_routes.UserRepository")
|
|
def test_login_complete_value_error_returns_user_message(
|
|
self, mock_user_repo: MagicMock, mock_service: MagicMock
|
|
) -> None:
|
|
mock_service.complete_authentication.side_effect = ValueError("Invalid credential")
|
|
client = TestClient(_build_app())
|
|
resp = client.post(
|
|
"/api/passkey/login/complete",
|
|
json={"session_id": "abc", "credential": {"id": "x"}},
|
|
)
|
|
assert resp.status_code == 400
|
|
assert resp.json()["detail"] == "Invalid credential"
|