Files
Gondulf/tests/integration/api/test_verification_flow.py
Phil Skentelbery e1f79af347 feat(test): add Phase 5b integration and E2E tests
Add comprehensive integration and end-to-end test suites:
- Integration tests for API flows (authorization, token, verification)
- Integration tests for middleware chain and security headers
- Integration tests for domain verification services
- E2E tests for complete authentication flows
- E2E tests for error scenarios and edge cases
- Shared test fixtures and utilities in conftest.py
- Rename Dockerfile to Containerfile for Podman compatibility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 22:22:04 -07:00

244 lines
8.7 KiB
Python

"""
Integration tests for domain verification flow.
Tests the complete domain verification flow including DNS verification,
email discovery, and code verification.
"""
import pytest
from fastapi.testclient import TestClient
from unittest.mock import Mock
@pytest.fixture
def verification_app(monkeypatch, tmp_path):
"""Create app for verification testing."""
db_path = tmp_path / "test.db"
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
monkeypatch.setenv("GONDULF_DATABASE_URL", f"sqlite:///{db_path}")
monkeypatch.setenv("GONDULF_DEBUG", "true")
from gondulf.main import app
return app
@pytest.fixture
def verification_client(verification_app):
"""Create test client for verification tests."""
with TestClient(verification_app) as client:
yield client
@pytest.fixture
def mock_verification_deps(verification_app, mock_dns_service, mock_email_service, mock_html_fetcher_with_email, mock_rate_limiter, test_code_storage):
"""Setup mock dependencies for verification."""
from gondulf.dependencies import get_verification_service, get_rate_limiter
from gondulf.services.domain_verification import DomainVerificationService
from gondulf.services.relme_parser import RelMeParser
service = DomainVerificationService(
dns_service=mock_dns_service,
email_service=mock_email_service,
code_storage=test_code_storage,
html_fetcher=mock_html_fetcher_with_email,
relme_parser=RelMeParser()
)
verification_app.dependency_overrides[get_verification_service] = lambda: service
verification_app.dependency_overrides[get_rate_limiter] = lambda: mock_rate_limiter
yield service, test_code_storage
verification_app.dependency_overrides.clear()
class TestStartVerification:
"""Tests for starting domain verification."""
def test_start_verification_success(self, verification_client, mock_verification_deps):
"""Test successful start of domain verification."""
response = verification_client.post(
"/api/verify/start",
data={"me": "https://user.example.com"}
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert "email" in data
# Email should be masked
assert "*" in data["email"]
def test_start_verification_invalid_me_url(self, verification_client, mock_verification_deps):
"""Test verification fails with invalid me URL."""
response = verification_client.post(
"/api/verify/start",
data={"me": "not-a-valid-url"}
)
assert response.status_code == 200
data = response.json()
assert data["success"] is False
assert data["error"] == "invalid_me_url"
def test_start_verification_rate_limited(self, verification_app, verification_client, mock_rate_limiter_exceeded, verification_service):
"""Test verification fails when rate limited."""
from gondulf.dependencies import get_rate_limiter, get_verification_service
verification_app.dependency_overrides[get_rate_limiter] = lambda: mock_rate_limiter_exceeded
verification_app.dependency_overrides[get_verification_service] = lambda: verification_service
response = verification_client.post(
"/api/verify/start",
data={"me": "https://user.example.com"}
)
assert response.status_code == 200
data = response.json()
assert data["success"] is False
assert data["error"] == "rate_limit_exceeded"
verification_app.dependency_overrides.clear()
def test_start_verification_dns_failure(self, verification_app, verification_client, verification_service_dns_failure, mock_rate_limiter):
"""Test verification fails when DNS check fails."""
from gondulf.dependencies import get_rate_limiter, get_verification_service
verification_app.dependency_overrides[get_rate_limiter] = lambda: mock_rate_limiter
verification_app.dependency_overrides[get_verification_service] = lambda: verification_service_dns_failure
response = verification_client.post(
"/api/verify/start",
data={"me": "https://user.example.com"}
)
assert response.status_code == 200
data = response.json()
assert data["success"] is False
assert data["error"] == "dns_verification_failed"
verification_app.dependency_overrides.clear()
class TestVerifyCode:
"""Tests for verifying email code."""
def test_verify_code_success(self, verification_client, mock_verification_deps):
"""Test successful code verification."""
service, code_storage = mock_verification_deps
# First start verification to store the code
verification_client.post(
"/api/verify/start",
data={"me": "https://example.com/"}
)
# Get the stored code
stored_code = code_storage.get("email_verify:example.com")
assert stored_code is not None
# Verify the code
response = verification_client.post(
"/api/verify/code",
data={"domain": "example.com", "code": stored_code}
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert "email" in data
def test_verify_code_invalid_code(self, verification_client, mock_verification_deps):
"""Test verification fails with invalid code."""
response = verification_client.post(
"/api/verify/code",
data={"domain": "example.com", "code": "000000"}
)
assert response.status_code == 200
data = response.json()
assert data["success"] is False
assert data["error"] == "invalid_code"
def test_verify_code_wrong_domain(self, verification_client, mock_verification_deps):
"""Test verification fails with wrong domain."""
service, code_storage = mock_verification_deps
# Start verification for one domain
verification_client.post(
"/api/verify/start",
data={"me": "https://example.com/"}
)
# Get the stored code
stored_code = code_storage.get("email_verify:example.com")
# Try to verify with different domain
response = verification_client.post(
"/api/verify/code",
data={"domain": "other.example.com", "code": stored_code}
)
assert response.status_code == 200
data = response.json()
assert data["success"] is False
class TestVerificationSecurityHeaders:
"""Security tests for verification endpoints."""
def test_start_verification_security_headers(self, verification_client, mock_verification_deps):
"""Test verification endpoints include security headers."""
response = verification_client.post(
"/api/verify/start",
data={"me": "https://user.example.com"}
)
assert "X-Frame-Options" in response.headers
assert "X-Content-Type-Options" in response.headers
def test_verify_code_security_headers(self, verification_client, mock_verification_deps):
"""Test code verification endpoint includes security headers."""
response = verification_client.post(
"/api/verify/code",
data={"domain": "example.com", "code": "123456"}
)
assert "X-Frame-Options" in response.headers
assert "X-Content-Type-Options" in response.headers
class TestVerificationResponseFormat:
"""Tests for verification endpoint response formats."""
def test_start_verification_returns_json(self, verification_client, mock_verification_deps):
"""Test start verification returns JSON."""
response = verification_client.post(
"/api/verify/start",
data={"me": "https://user.example.com"}
)
assert "application/json" in response.headers["content-type"]
def test_verify_code_returns_json(self, verification_client, mock_verification_deps):
"""Test code verification returns JSON."""
response = verification_client.post(
"/api/verify/code",
data={"domain": "example.com", "code": "123456"}
)
assert "application/json" in response.headers["content-type"]
def test_success_response_includes_method(self, verification_client, mock_verification_deps):
"""Test successful verification includes verification method."""
response = verification_client.post(
"/api/verify/start",
data={"me": "https://user.example.com"}
)
data = response.json()
assert data["success"] is True
assert "verification_method" in data