Files
Gondulf/tests/unit/test_domain_verification.py
Phil Skentelbery 074f74002c feat(phase-2): implement domain verification system
Implements complete domain verification flow with:
- rel=me link verification service
- HTML fetching with security controls
- Rate limiting to prevent abuse
- Email validation utilities
- Authorization and verification API endpoints
- User-facing templates for authorization and verification flows

This completes Phase 2: Domain Verification as designed.

Tests:
- All Phase 2 unit tests passing
- Coverage: 85% overall
- Migration tests updated

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 13:44:33 -07:00

237 lines
8.7 KiB
Python

"""Tests for domain verification service."""
import pytest
from unittest.mock import Mock, MagicMock
from gondulf.services.domain_verification import DomainVerificationService
from gondulf.dns import DNSService
from gondulf.email import EmailService
from gondulf.storage import CodeStore
from gondulf.services.html_fetcher import HTMLFetcherService
from gondulf.services.relme_parser import RelMeParser
class TestDomainVerificationService:
"""Tests for DomainVerificationService."""
@pytest.fixture
def mock_dns(self):
"""Mock DNS service."""
return Mock(spec=DNSService)
@pytest.fixture
def mock_email(self):
"""Mock email service."""
return Mock(spec=EmailService)
@pytest.fixture
def mock_storage(self):
"""Mock code storage."""
return Mock(spec=CodeStore)
@pytest.fixture
def mock_fetcher(self):
"""Mock HTML fetcher."""
return Mock(spec=HTMLFetcherService)
@pytest.fixture
def mock_parser(self):
"""Mock rel=me parser."""
return Mock(spec=RelMeParser)
@pytest.fixture
def service(self, mock_dns, mock_email, mock_storage, mock_fetcher, mock_parser):
"""Create domain verification service with mocks."""
return DomainVerificationService(
dns_service=mock_dns,
email_service=mock_email,
code_storage=mock_storage,
html_fetcher=mock_fetcher,
relme_parser=mock_parser
)
def test_generate_verification_code(self, service):
"""Test verification code generation."""
code = service.generate_verification_code()
assert isinstance(code, str)
assert len(code) == 6
assert code.isdigit()
def test_generate_verification_code_unique(self, service):
"""Test that generated codes are different."""
code1 = service.generate_verification_code()
code2 = service.generate_verification_code()
# Very unlikely to be the same, but possible
# Just check they're both valid
assert code1.isdigit()
assert code2.isdigit()
def test_start_verification_dns_fails(self, service, mock_dns):
"""Test start_verification when DNS verification fails."""
mock_dns.verify_txt_record.return_value = False
result = service.start_verification("example.com", "https://example.com/")
assert result["success"] is False
assert result["error"] == "dns_verification_failed"
def test_start_verification_email_discovery_fails(
self, service, mock_dns, mock_fetcher, mock_parser
):
"""Test start_verification when email discovery fails."""
mock_dns.verify_txt_record.return_value = True
mock_fetcher.fetch.return_value = "<html></html>"
mock_parser.find_email.return_value = None
result = service.start_verification("example.com", "https://example.com/")
assert result["success"] is False
assert result["error"] == "email_discovery_failed"
def test_start_verification_invalid_email_format(
self, service, mock_dns, mock_fetcher, mock_parser
):
"""Test start_verification with invalid email format."""
mock_dns.verify_txt_record.return_value = True
mock_fetcher.fetch.return_value = "<html></html>"
mock_parser.find_email.return_value = "not-an-email"
result = service.start_verification("example.com", "https://example.com/")
assert result["success"] is False
assert result["error"] == "invalid_email_format"
def test_start_verification_email_send_fails(
self, service, mock_dns, mock_fetcher, mock_parser, mock_email
):
"""Test start_verification when email sending fails."""
mock_dns.verify_txt_record.return_value = True
mock_fetcher.fetch.return_value = "<html></html>"
mock_parser.find_email.return_value = "user@example.com"
mock_email.send_verification_code.side_effect = Exception("SMTP error")
result = service.start_verification("example.com", "https://example.com/")
assert result["success"] is False
assert result["error"] == "email_send_failed"
def test_start_verification_success(
self, service, mock_dns, mock_fetcher, mock_parser, mock_email, mock_storage
):
"""Test successful verification start."""
mock_dns.verify_txt_record.return_value = True
mock_fetcher.fetch.return_value = "<html></html>"
mock_parser.find_email.return_value = "user@example.com"
result = service.start_verification("example.com", "https://example.com/")
assert result["success"] is True
assert result["email"] == "u***@example.com" # Masked
assert result["verification_method"] == "email"
mock_email.send_verification_code.assert_called_once()
assert mock_storage.store.call_count == 2 # Code and email stored
def test_verify_email_code_invalid(self, service, mock_storage):
"""Test verify_email_code with invalid code."""
mock_storage.verify.return_value = False
result = service.verify_email_code("example.com", "123456")
assert result["success"] is False
assert result["error"] == "invalid_code"
def test_verify_email_code_email_not_found(self, service, mock_storage):
"""Test verify_email_code when email not in storage."""
mock_storage.verify.return_value = True
mock_storage.get.return_value = None
result = service.verify_email_code("example.com", "123456")
assert result["success"] is False
assert result["error"] == "email_not_found"
def test_verify_email_code_success(self, service, mock_storage):
"""Test successful email code verification."""
mock_storage.verify.return_value = True
mock_storage.get.return_value = "user@example.com"
result = service.verify_email_code("example.com", "123456")
assert result["success"] is True
assert result["email"] == "user@example.com"
mock_storage.delete.assert_called_once()
def test_create_authorization_code(self, service, mock_storage):
"""Test authorization code creation."""
code = service.create_authorization_code(
client_id="https://client.example.com/",
redirect_uri="https://client.example.com/callback",
state="test_state",
code_challenge="challenge",
code_challenge_method="S256",
scope="profile",
me="https://user.example.com/"
)
assert isinstance(code, str)
assert len(code) > 0
mock_storage.store.assert_called_once()
def test_verify_dns_record_success(self, service, mock_dns):
"""Test DNS record verification success."""
mock_dns.verify_txt_record.return_value = True
result = service._verify_dns_record("example.com")
assert result is True
mock_dns.verify_txt_record.assert_called_with("example.com", "gondulf-verify-domain")
def test_verify_dns_record_failure(self, service, mock_dns):
"""Test DNS record verification failure."""
mock_dns.verify_txt_record.return_value = False
result = service._verify_dns_record("example.com")
assert result is False
def test_verify_dns_record_exception(self, service, mock_dns):
"""Test DNS record verification handles exceptions."""
mock_dns.verify_txt_record.side_effect = Exception("DNS error")
result = service._verify_dns_record("example.com")
assert result is False
def test_discover_email_success(self, service, mock_fetcher, mock_parser):
"""Test email discovery success."""
mock_fetcher.fetch.return_value = "<html></html>"
mock_parser.find_email.return_value = "user@example.com"
email = service._discover_email("https://example.com/")
assert email == "user@example.com"
def test_discover_email_fetch_fails(self, service, mock_fetcher):
"""Test email discovery when fetch fails."""
mock_fetcher.fetch.return_value = None
email = service._discover_email("https://example.com/")
assert email is None
def test_discover_email_no_email_found(self, service, mock_fetcher, mock_parser):
"""Test email discovery when no email found."""
mock_fetcher.fetch.return_value = "<html></html>"
mock_parser.find_email.return_value = None
email = service._discover_email("https://example.com/")
assert email is None
def test_discover_email_exception(self, service, mock_fetcher):
"""Test email discovery handles exceptions."""
mock_fetcher.fetch.side_effect = Exception("Fetch error")
email = service._discover_email("https://example.com/")
assert email is None