""" Tests for ATOM feed generation module Tests cover: - ATOM feed generation with various note counts - RFC 3339 date formatting - Feed structure and required elements - Entry ordering (newest first) - XML escaping """ import pytest from datetime import datetime, timezone from xml.etree import ElementTree as ET import time from starpunk import create_app from starpunk.feeds.atom import generate_atom, generate_atom_streaming from starpunk.notes import create_note, list_notes from tests.helpers.feed_ordering import assert_feed_newest_first @pytest.fixture def app(tmp_path): """Create test application""" test_data_dir = tmp_path / "data" test_data_dir.mkdir(parents=True, exist_ok=True) test_config = { "TESTING": True, "DATABASE_PATH": test_data_dir / "starpunk.db", "DATA_PATH": test_data_dir, "NOTES_PATH": test_data_dir / "notes", "SESSION_SECRET": "test-secret-key", "ADMIN_ME": "https://test.example.com", "SITE_URL": "https://example.com", "SITE_NAME": "Test Blog", "SITE_DESCRIPTION": "A test blog", "DEV_MODE": False, } app = create_app(config=test_config) yield app @pytest.fixture def sample_notes(app): """Create sample published notes""" with app.app_context(): notes = [] for i in range(5): note = create_note( content=f"# Test Note {i}\n\nThis is test content for note {i}.", published=True, ) notes.append(note) time.sleep(0.01) # Ensure distinct timestamps return list_notes(published_only=True, limit=10) class TestGenerateAtom: """Test generate_atom() function""" def test_generate_atom_basic(self, app, sample_notes): """Test basic ATOM feed generation with notes""" with app.app_context(): feed_xml = generate_atom( site_url="https://example.com", site_name="Test Blog", site_description="A test blog", notes=sample_notes, ) # Should return XML string assert isinstance(feed_xml, str) assert feed_xml.startswith("" in content_text def test_generate_atom_xml_escaping(self, app): """Test ATOM feed escapes special XML characters""" with app.app_context(): note = create_note( content="# Test & Special \n\nContent with 'quotes' and \"doubles\".", published=True, ) feed_xml = generate_atom( site_url="https://example.com", site_name="Test Blog & More", site_description="A test ", notes=[note], ) # Should produce valid XML (no parse errors) root = ET.fromstring(feed_xml) assert root is not None # Check title is properly escaped in XML ns = {'atom': 'http://www.w3.org/2005/Atom'} title = root.find('atom:title', ns) assert title.text == "Test Blog & More" class TestGenerateAtomStreaming: """Test generate_atom_streaming() function""" def test_generate_atom_streaming_basic(self, app, sample_notes): """Test streaming ATOM feed generation""" with app.app_context(): generator = generate_atom_streaming( site_url="https://example.com", site_name="Test Blog", site_description="A test blog", notes=sample_notes, ) # Collect all chunks chunks = list(generator) assert len(chunks) > 0 # Join and verify valid XML feed_xml = ''.join(chunks) root = ET.fromstring(feed_xml) ns = {'atom': 'http://www.w3.org/2005/Atom'} entries = root.findall('atom:entry', ns) assert len(entries) == 5 def test_generate_atom_streaming_yields_chunks(self, app, sample_notes): """Test streaming yields multiple chunks""" with app.app_context(): generator = generate_atom_streaming( site_url="https://example.com", site_name="Test Blog", site_description="A test blog", notes=sample_notes, limit=3, ) chunks = list(generator) # Should have multiple chunks (at least XML declaration + feed + entries + closing) assert len(chunks) >= 4