Files

119 lines
4.9 KiB
Python
Raw Permalink Normal View History

"""WebAuthn passkey integration tests.
Covers WebAuthn registration, login, and credential management.
These tests mock the cryptographic operations since real WebAuthn
requires a browser environment.
"""
import pytest
from unittest.mock import patch, MagicMock
from tests.integration.client.base import ApiError
def assert_success(response: dict, message_contains: str = "") -> dict:
data = response.get("data", {})
assert response.get("success") is not False, (
f"Expected success but got error: {response.get('message')}"
)
if message_contains:
assert message_contains.lower() in response.get("message", "").lower()
return data
class TestWebAuthnRegistration:
"""Test WebAuthn passkey registration."""
def test_begin_registration_positive(self, integration_client, create_test_user):
"""TEST: WEBAUTHN-01 — Begin passkey registration.
WHAT: POST /auth/webauthn/register/begin.
WHY: First step of passkey enrollment.
EXPECTED: 200 OK with challenge options.
"""
user = create_test_user(password="MyPassword123!")
integration_client.auth.login(email=user["email"], password="MyPassword123!")
result = integration_client.post("/auth/webauthn/register/begin")
# Endpoint returns jsonify directly, not api_response wrapper
assert "rp" in result or result.get("success") is not False
def test_complete_registration_mocked_positive(self, integration_app, integration_client, create_test_user):
"""TEST: WEBAUTHN-02 — Complete passkey registration (mocked).
WHAT: POST /auth/webauthn/register/complete with mocked verification.
WHY: Full registration flow requires mocking crypto.
EXPECTED: 201 Created when verification succeeds.
"""
from gatehouse_app.models.auth.authentication_method import AuthenticationMethod
user = create_test_user(password="MyPassword123!")
integration_client.auth.login(email=user["email"], password="MyPassword123!")
with patch("gatehouse_app.api.v1.auth.webauthn.WebAuthnService.verify_registration_response") as mock_verify:
mock_auth_method = MagicMock()
mock_auth_method.to_webauthn_dict.return_value = {"id": "cred-123", "type": "public-key"}
mock_verify.return_value = mock_auth_method
import base64
client_data = base64.urlsafe_b64encode(b'{"challenge":"test-challenge"}').rstrip(b"=").decode()
result = integration_client.post(
"/auth/webauthn/register/complete",
data={
"id": "cred-123",
"rawId": "raw-123",
"response": {
"clientDataJSON": client_data,
"attestationObject": "o2Nmb",
},
"type": "public-key",
},
)
# Mock path may return 201 or wrapped response depending on flow
assert result.get("success") is not False or result.get("code") == 201
def test_list_credentials_positive(self, integration_client, create_test_user):
"""TEST: WEBAUTHN-03 — List WebAuthn credentials.
WHAT: GET /auth/webauthn/credentials.
WHY: Security page displays registered passkeys.
EXPECTED: 200 OK with credentials array.
"""
user = create_test_user(password="MyPassword123!")
integration_client.auth.login(email=user["email"], password="MyPassword123!")
result = integration_client.get("/auth/webauthn/credentials")
assert_success(result)
class TestWebAuthnLogin:
"""Test WebAuthn login flow."""
def test_begin_login_positive(self, integration_client, create_test_user):
"""TEST: WEBAUTHN-04 — Begin WebAuthn login.
WHAT: POST /auth/webauthn/login/begin with email.
WHY: First step of passkey authentication.
EXPECTED: 200 OK with challenge options (or 404 if no passkeys).
"""
user = create_test_user(password="MyPassword123!")
try:
result = integration_client.post("/auth/webauthn/login/begin", data={"email": user["email"]})
assert "challenge" in result
except ApiError as exc:
# Accept 404 when user has no passkeys registered
assert exc.status_code == 404, f"Expected 200 or 404, got {exc.status_code}"
def test_get_webauthn_status_positive(self, integration_client, create_test_user):
"""TEST: WEBAUTHN-05 — Get WebAuthn status.
WHAT: GET /auth/webauthn/status.
WHY: Security page shows whether passkeys are enabled.
EXPECTED: 200 OK.
"""
user = create_test_user(password="MyPassword123!")
integration_client.auth.login(email=user["email"], password="MyPassword123!")
result = integration_client.get("/auth/webauthn/status")
assert_success(result)