diff --git a/backend/tests/test_sitemap_api.py b/backend/tests/test_sitemap_api.py
new file mode 100644
index 0000000..40720c4
--- /dev/null
+++ b/backend/tests/test_sitemap_api.py
@@ -0,0 +1,55 @@
+"""Tests for sitemap API endpoints — /api/sitemap/sitemap.xml, /api/sitemap/robots.txt, /api/sitemap/regenerate."""
+
+import pytest
+from httpx import AsyncClient
+from starlette.testclient import TestClient
+
+from app.main import app
+
+
+class TestSitemapEndpoints:
+ """Tests for sitemap API routes."""
+
+ def _client(self):
+ return TestClient(app)
+
+ def test_sitemap_xml_status(self):
+ client = self._client()
+ resp = client.get("/api/sitemap/sitemap.xml")
+ assert resp.status_code == 200
+
+ def test_sitemap_xml_content_type(self):
+ client = self._client()
+ resp = client.get("/api/sitemap/sitemap.xml")
+ assert resp.headers["content-type"].startswith("application/xml")
+
+ def test_sitemap_xml_contains_routes(self):
+ client = self._client()
+ resp = client.get("/api/sitemap/sitemap.xml")
+ body = resp.text
+ for path in ["/", "/about", "/schedule", "/events", "/contact"]:
+ assert f"https://kmountainflower.org{path}" in body
+
+ def test_robots_txt_status(self):
+ client = self._client()
+ resp = client.get("/api/sitemap/robots.txt")
+ assert resp.status_code == 200
+
+ def test_robots_txt_content_type(self):
+ client = self._client()
+ resp = client.get("/api/sitemap/robots.txt")
+ assert resp.headers["content-type"].startswith("text/plain")
+
+ def test_robots_txt_contains_rules(self):
+ client = self._client()
+ resp = client.get("/api/sitemap/robots.txt")
+ body = resp.text
+ assert "User-agent: *" in body
+ assert "Disallow: /admin" in body
+ assert "Disallow: /login" in body
+ assert "Sitemap: https://kmountainflower.org/sitemap.xml" in body
+
+ def test_regenerate_requires_auth(self):
+ client = self._client()
+ resp = client.post("/api/sitemap/regenerate")
+ assert resp.status_code in (401, 403)
diff --git a/backend/tests/test_sitemap_export.py b/backend/tests/test_sitemap_export.py
new file mode 100644
index 0000000..4b5f6f8
--- /dev/null
+++ b/backend/tests/test_sitemap_export.py
@@ -0,0 +1,113 @@
+"""Tests for sitemap_export — sitemap.xml and robots.txt generation."""
+
+import pytest
+from xml.etree import ElementTree as ET
+
+from app.sitemap_export import (
+ SEOMAP_ROUTES,
+ build_sitemap_xml,
+ build_robots_txt,
+)
+
+
+class TestSitemapRoutes:
+ """Verify the route configuration constants."""
+
+ def test_route_count(self):
+ assert len(SEOMAP_ROUTES) == 5
+
+ def test_home_route(self):
+ assert SEOMAP_ROUTES[0] == {
+ "path": "/",
+ "changefreq": "daily",
+ "priority": 1.0,
+ }
+
+ def test_all_paths_present(self):
+ paths = [r["path"] for r in SEOMAP_ROUTES]
+ expected = ["/", "/about", "/schedule", "/events", "/contact"]
+ assert paths == expected
+
+ def test_priority_range(self):
+ for route in SEOMAP_ROUTES:
+ assert 0.0 <= route["priority"] <= 1.0
+
+ def test_valid_changefreq_values(self):
+ valid = {"always", "hourly", "daily", "weekly", "monthly", "yearly", "never"}
+ for route in SEOMAP_ROUTES:
+ assert route["changefreq"] in valid
+
+
+class TestBuildSitemapXml:
+ """Tests for the sitemap XML builder."""
+
+ def test_returns_string(self):
+ result = build_sitemap_xml("https://example.com")
+ assert isinstance(result, str)
+
+ def test_contains_xml_declaration(self):
+ result = build_sitemap_xml("https://example.com")
+ assert '' in result
+
+ def test_contains_urlset_namespace(self):
+ result = build_sitemap_xml("https://example.com")
+ assert 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"' in result
+
+ def test_contains_all_routes(self):
+ result = build_sitemap_xml("https://example.com")
+ for route in SEOMAP_ROUTES:
+ assert f"https://example.com{route['path']}" in result
+
+ def test_strips_trailing_slash_from_base_url(self):
+ result = build_sitemap_xml("https://example.com/")
+ assert "https://example.com//about" not in result
+ assert "https://example.com/about" in result
+
+ def test_different_base_url(self):
+ result = build_sitemap_xml("https://mystation.org")
+ assert "https://mystation.org/schedule" in result
+ assert "https://example.com" not in result
+
+ def test_valid_xml(self):
+ result = build_sitemap_xml("https://example.com")
+ # Should parse without error
+ root = ET.fromstring(result)
+ # The root tag includes the namespace
+ assert "urlset" in root.tag
+
+ def test_contains_changefreq_and_priority(self):
+ result = build_sitemap_xml("https://example.com")
+ assert "daily" in result
+ assert "1.0" in result
+ assert "0.5" in result
+
+
+class TestBuildRobotsTxt:
+ """Tests for the robots.txt builder."""
+
+ def test_returns_string(self):
+ result = build_robots_txt("https://example.com")
+ assert isinstance(result, str)
+
+ def test_user_agent_allows_all(self):
+ result = build_robots_txt("https://example.com")
+ assert "User-agent: *" in result
+ assert "Allow: /" in result
+
+ def test_disallows_admin_and_login(self):
+ result = build_robots_txt("https://example.com")
+ assert "Disallow: /admin" in result
+ assert "Disallow: /login" in result
+
+ def test_sitemap_reference(self):
+ result = build_robots_txt("https://example.com")
+ assert "Sitemap: https://example.com/sitemap.xml" in result
+
+ def test_strips_trailing_slash(self):
+ result = build_robots_txt("https://example.com/")
+ assert "Sitemap: https://example.com//sitemap.xml" not in result
+ assert "Sitemap: https://example.com/sitemap.xml" in result
+
+ def test_different_base_url(self):
+ result = build_robots_txt("https://mystation.org")
+ assert "Sitemap: https://mystation.org/sitemap.xml" in result