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>
244 lines
8.7 KiB
Python
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
|