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>
171 lines
5.7 KiB
Python
171 lines
5.7 KiB
Python
"""
|
|
Integration tests for h-app parser service.
|
|
|
|
Tests client metadata fetching with mocked HTTP responses.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import MagicMock, Mock, patch
|
|
|
|
|
|
class TestHAppParserIntegration:
|
|
"""Integration tests for h-app metadata parsing."""
|
|
|
|
@pytest.fixture
|
|
def happ_parser_with_mock_fetcher(self):
|
|
"""Create h-app parser with mocked HTML fetcher."""
|
|
from gondulf.services.happ_parser import HAppParser
|
|
|
|
html = '''
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head><title>Test App</title></head>
|
|
<body>
|
|
<div class="h-app">
|
|
<h1 class="p-name">Example Application</h1>
|
|
<img class="u-logo" src="https://app.example.com/logo.png" alt="Logo">
|
|
<a class="u-url" href="https://app.example.com">Home</a>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
'''
|
|
|
|
mock_fetcher = Mock()
|
|
mock_fetcher.fetch = Mock(return_value=html)
|
|
|
|
return HAppParser(html_fetcher=mock_fetcher)
|
|
|
|
def test_fetch_and_parse_happ_metadata(self, happ_parser_with_mock_fetcher):
|
|
"""Test fetching and parsing h-app microformat."""
|
|
import asyncio
|
|
result = asyncio.get_event_loop().run_until_complete(
|
|
happ_parser_with_mock_fetcher.fetch_and_parse("https://app.example.com")
|
|
)
|
|
|
|
assert result is not None
|
|
assert result.name == "Example Application"
|
|
assert result.logo == "https://app.example.com/logo.png"
|
|
|
|
def test_parse_page_without_happ(self, mock_urlopen):
|
|
"""Test parsing page without h-app returns fallback."""
|
|
from gondulf.services.happ_parser import HAppParser
|
|
from gondulf.services.html_fetcher import HTMLFetcherService
|
|
|
|
# Setup mock to return page without h-app
|
|
html = b'<html><head><title>Plain Page</title></head><body>No h-app</body></html>'
|
|
mock_response = MagicMock()
|
|
mock_response.read.return_value = html
|
|
mock_response.status = 200
|
|
mock_response.__enter__ = Mock(return_value=mock_response)
|
|
mock_response.__exit__ = Mock(return_value=False)
|
|
mock_urlopen.return_value = mock_response
|
|
|
|
fetcher = HTMLFetcherService()
|
|
parser = HAppParser(html_fetcher=fetcher)
|
|
|
|
import asyncio
|
|
result = asyncio.get_event_loop().run_until_complete(
|
|
parser.fetch_and_parse("https://app.example.com")
|
|
)
|
|
|
|
# Should return fallback metadata using domain
|
|
assert result is not None
|
|
assert "example.com" in result.name.lower() or result.name == "Plain Page"
|
|
|
|
def test_fetch_timeout_returns_fallback(self, mock_urlopen_timeout):
|
|
"""Test HTTP timeout returns fallback metadata."""
|
|
from gondulf.services.happ_parser import HAppParser
|
|
from gondulf.services.html_fetcher import HTMLFetcherService
|
|
|
|
fetcher = HTMLFetcherService()
|
|
parser = HAppParser(html_fetcher=fetcher)
|
|
|
|
import asyncio
|
|
result = asyncio.get_event_loop().run_until_complete(
|
|
parser.fetch_and_parse("https://slow-app.example.com")
|
|
)
|
|
|
|
# Should return fallback metadata
|
|
assert result is not None
|
|
# Should use domain as fallback name
|
|
assert "slow-app.example.com" in result.name or result.url == "https://slow-app.example.com"
|
|
|
|
|
|
class TestClientMetadataCaching:
|
|
"""Tests for client metadata caching behavior."""
|
|
|
|
def test_metadata_fetched_from_url(self, mock_urlopen_with_happ):
|
|
"""Test metadata is actually fetched from URL."""
|
|
from gondulf.services.happ_parser import HAppParser
|
|
from gondulf.services.html_fetcher import HTMLFetcherService
|
|
|
|
fetcher = HTMLFetcherService()
|
|
parser = HAppParser(html_fetcher=fetcher)
|
|
|
|
import asyncio
|
|
result = asyncio.get_event_loop().run_until_complete(
|
|
parser.fetch_and_parse("https://app.example.com")
|
|
)
|
|
|
|
# urlopen should have been called
|
|
mock_urlopen_with_happ.assert_called()
|
|
|
|
|
|
class TestHAppMicroformatVariants:
|
|
"""Tests for various h-app microformat formats."""
|
|
|
|
@pytest.fixture
|
|
def create_parser_with_html(self):
|
|
"""Factory to create parser with specific HTML content."""
|
|
def _create(html_content):
|
|
from gondulf.services.happ_parser import HAppParser
|
|
|
|
mock_fetcher = Mock()
|
|
mock_fetcher.fetch = Mock(return_value=html_content)
|
|
|
|
return HAppParser(html_fetcher=mock_fetcher)
|
|
return _create
|
|
|
|
def test_parse_happ_with_minimal_data(self, create_parser_with_html):
|
|
"""Test parsing h-app with only name."""
|
|
html = '''
|
|
<html>
|
|
<body>
|
|
<div class="h-app">
|
|
<span class="p-name">Minimal App</span>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
'''
|
|
parser = create_parser_with_html(html)
|
|
|
|
import asyncio
|
|
result = asyncio.get_event_loop().run_until_complete(
|
|
parser.fetch_and_parse("https://minimal.example.com")
|
|
)
|
|
|
|
assert result.name == "Minimal App"
|
|
|
|
def test_parse_happ_with_logo_relative_url(self, create_parser_with_html):
|
|
"""Test parsing h-app with relative logo URL."""
|
|
html = '''
|
|
<html>
|
|
<body>
|
|
<div class="h-app">
|
|
<span class="p-name">Relative Logo App</span>
|
|
<img class="u-logo" src="/logo.png">
|
|
</div>
|
|
</body>
|
|
</html>
|
|
'''
|
|
parser = create_parser_with_html(html)
|
|
|
|
import asyncio
|
|
result = asyncio.get_event_loop().run_until_complete(
|
|
parser.fetch_and_parse("https://relative.example.com")
|
|
)
|
|
|
|
assert result.name == "Relative Logo App"
|
|
# Logo should be resolved to absolute URL
|
|
assert result.logo is not None
|