diff --git a/tests/test_sitemap_and_utilities.py b/tests/test_sitemap_and_utilities.py new file mode 100644 index 0000000..4d922a1 --- /dev/null +++ b/tests/test_sitemap_and_utilities.py @@ -0,0 +1,140 @@ +"""Tests for sitemap, robots.txt, and utility endpoints.""" +import time +import xml.etree.ElementTree as ET +import pytest + + +class TestSitemap: + """Tests for sitemap.xml generation""" + + def test_sitemap_exists(self, client): + """Sitemap should return 200 and valid XML""" + response = client.get("/sitemap.xml") + assert response.status_code == 200 + assert response.headers["content-type"] == "application/xml" + + def test_sitemap_valid_xml(self, client): + """Sitemap should be valid XML that can be parsed""" + response = client.get("/sitemap.xml") + content = response.content.decode("utf-8") + + # Should be parseable XML + try: + root = ET.fromstring(content) + assert root.tag.endswith("urlset") + except ET.ParseError as e: + pytest.fail(f"Sitemap is not valid XML: {e}") + + def test_sitemap_performance(self, client): + """Sitemap should generate quickly (under 1 second)""" + start_time = time.time() + response = client.get("/sitemap.xml") + duration = time.time() - start_time + + assert response.status_code == 200 + assert duration < 1.0, f"Sitemap took {duration:.2f}s to generate (should be <1s)" + + def test_sitemap_url_count(self, client): + """Sitemap should stay under Google's 50k URL recommendation""" + response = client.get("/sitemap.xml") + content = response.content.decode("utf-8") + + # Count tags + url_count = content.count("") + assert url_count > 0, "Sitemap should contain URLs" + assert url_count < 50000, f"Sitemap has {url_count} URLs (should be <50k for Google)" + + # Verify it's a reasonable number (not all 31k verses) + assert url_count < 5000, f"Sitemap has {url_count} URLs (seems too high - did you include all verses?)" + + def test_sitemap_contains_critical_urls(self, client): + """Sitemap should include critical pages""" + response = client.get("/sitemap.xml") + content = response.content.decode("utf-8") + + critical_urls = [ + "https://kjvstudy.org/", + "https://kjvstudy.org/books", + "https://kjvstudy.org/search", + "https://kjvstudy.org/topics", + "https://kjvstudy.org/reading-plans", + "https://kjvstudy.org/resources", + ] + + for url in critical_urls: + assert url in content, f"Sitemap missing critical URL: {url}" + + def test_sitemap_contains_book_urls(self, client): + """Sitemap should include book URLs""" + response = client.get("/sitemap.xml") + content = response.content.decode("utf-8") + + # Check for some book URLs + assert "https://kjvstudy.org/book/Genesis" in content + assert "https://kjvstudy.org/book/John" in content + assert "https://kjvstudy.org/book/Revelation" in content + + def test_sitemap_contains_chapter_urls(self, client): + """Sitemap should include chapter URLs""" + response = client.get("/sitemap.xml") + content = response.content.decode("utf-8") + + # Check for some chapter URLs + assert "https://kjvstudy.org/book/Genesis/chapter/1" in content + assert "https://kjvstudy.org/book/John/chapter/3" in content + + def test_sitemap_excludes_verse_urls(self, client): + """Sitemap should NOT include individual verse URLs (too many)""" + response = client.get("/sitemap.xml") + content = response.content.decode("utf-8") + + # Should NOT contain individual verse URLs + assert "/verse/1" not in content, "Sitemap should exclude individual verse URLs" + + def test_sitemap_caching(self, client): + """Sitemap should return the same content on repeated requests (cache working)""" + response1 = client.get("/sitemap.xml") + response2 = client.get("/sitemap.xml") + + assert response1.content == response2.content + assert response1.status_code == 200 + assert response2.status_code == 200 + + +class TestRobotsTxt: + """Tests for robots.txt""" + + def test_robots_txt_exists(self, client): + """Robots.txt should exist and return 200""" + response = client.get("/robots.txt") + assert response.status_code == 200 + assert response.headers["content-type"] == "text/plain; charset=utf-8" + + def test_robots_txt_content(self, client): + """Robots.txt should have proper directives""" + response = client.get("/robots.txt") + content = response.content.decode("utf-8") + + assert "User-agent: *" in content + assert "Allow: /" in content + assert "Sitemap: https://kjvstudy.org/sitemap.xml" in content + + def test_robots_txt_disallows_api(self, client): + """Robots.txt should disallow /api/ endpoints""" + response = client.get("/robots.txt") + content = response.content.decode("utf-8") + + assert "Disallow: /api/" in content + + +class TestHealthCheck: + """Tests for health check endpoint""" + + def test_health_check(self, client): + """Health check should return healthy status""" + response = client.get("/health") + assert response.status_code == 200 + + data = response.json() + assert data["status"] == "healthy" + assert data["service"] == "kjv-study" diff --git a/tests/test_web_routes.py b/tests/test_web_routes.py index ac07070..7ea1e10 100644 --- a/tests/test_web_routes.py +++ b/tests/test_web_routes.py @@ -303,5 +303,91 @@ class TestContentTypes: assert "application/json" in content_type or "json" in content_type +class TestStoryRoutes: + """Tests for Bible stories routes""" + + def test_stories_index(self, client): + """Test stories index page loads""" + response = client.get("/stories") + assert response.status_code == 200 + content = response.content.decode() + assert "Bible Stories" in content or "stories" in content.lower() + + def test_stories_kids_index(self, client): + """Test kids stories index page loads""" + response = client.get("/stories/kids") + assert response.status_code == 200 + content = response.content.decode() + assert "kids" in content.lower() or "children" in content.lower() + + def test_story_detail_page(self, client): + """Test individual story page loads""" + # Try to get a story - we don't know the exact slugs without loading data + # so we'll test the index and ensure it has story links + response = client.get("/stories") + content = response.content.decode() + + # Should have links to individual stories + assert "/stories/" in content or response.status_code == 200 + + def test_story_counts_work(self, client): + """Test story counts are displayed and cached""" + response = client.get("/stories") + content = response.content.decode() + + # Story count should be displayed somewhere (or page should load) + assert response.status_code == 200 + + +class TestMarkdownRendering: + """Tests for Mistune markdown rendering in templates""" + + def test_markdown_renders_in_pages(self, client): + """Test that pages using markdown filters render successfully""" + # Resources pages use markdown for descriptions + response = client.get("/resources") + assert response.status_code == 200 + content = response.content.decode() + + # Should render successfully (not have raw markdown) + # If we see **text** or *text* that hasn't been converted, markdown failed + # Note: This is a basic smoke test - detailed markdown tests would need fixtures + + def test_resource_pages_with_markdown(self, client): + """Test resource pages that use markdown rendering""" + resource_pages = [ + "/biblical-angels", + "/biblical-prophets", + "/parables", + ] + + for page in resource_pages: + response = client.get(page) + assert response.status_code == 200, f"Page {page} failed to load" + + +class TestResourceSlugLookups: + """Tests for optimized resource slug index lookups""" + + def test_resource_detail_pages_load(self, client): + """Test that resource detail pages load successfully with slug lookups""" + # Test some known resource detail pages + test_pages = [ + "/biblical-angels/michael", + "/biblical-prophets/moses", + "/names-of-god/yahweh", + ] + + for page in test_pages: + response = client.get(page) + # Should either load (200) or be a valid 404 if that specific item doesn't exist + assert response.status_code in [200, 404] + + def test_invalid_resource_slug_returns_404(self, client): + """Test that invalid resource slugs return 404""" + response = client.get("/biblical-angels/this-angel-does-not-exist") + assert response.status_code == 404 + + if __name__ == "__main__": pytest.main([__file__, "-v"])