diff --git a/WebHostLib/templates/siteMap.html b/WebHostLib/templates/siteMap.html
index b7db8227dc..4b764c341a 100644
--- a/WebHostLib/templates/siteMap.html
+++ b/WebHostLib/templates/siteMap.html
@@ -11,32 +11,32 @@
Site Map
Base Pages
Tutorials
Game Info Pages
diff --git a/test/general/__init__.py b/test/general/__init__.py
index 34df741a8c..92ffc77ee6 100644
--- a/test/general/__init__.py
+++ b/test/general/__init__.py
@@ -3,7 +3,7 @@ from typing import List, Optional, Tuple, Type, Union
from BaseClasses import CollectionState, Item, ItemClassification, Location, MultiWorld, Region
from worlds import network_data_package
-from worlds.AutoWorld import World, call_all
+from worlds.AutoWorld import World, WebWorld, call_all
gen_steps = (
"generate_early",
@@ -17,7 +17,7 @@ gen_steps = (
def setup_solo_multiworld(
- world_type: Type[World], steps: Tuple[str, ...] = gen_steps, seed: Optional[int] = None
+ world_type: Type[World], steps: Tuple[str, ...] = gen_steps, seed: Optional[int] = None
) -> MultiWorld:
"""
Creates a multiworld with a single player of `world_type`, sets default options, and calls provided gen steps.
@@ -62,11 +62,16 @@ def setup_multiworld(worlds: Union[List[Type[World]], Type[World]], steps: Tuple
return multiworld
+class TestWebWorld(WebWorld):
+ tutorials = []
+
+
class TestWorld(World):
game = f"Test Game"
item_name_to_id = {}
location_name_to_id = {}
hidden = True
+ web = TestWebWorld()
# add our test world to the data package, so we can test it later
diff --git a/test/webhost/test_sitemap.py b/test/webhost/test_sitemap.py
new file mode 100644
index 0000000000..930aa32415
--- /dev/null
+++ b/test/webhost/test_sitemap.py
@@ -0,0 +1,63 @@
+import urllib.parse
+import html
+import re
+from flask import url_for
+
+import WebHost
+from . import TestBase
+
+
+class TestSitemap(TestBase):
+
+ # Codes for OK and some redirects that we use
+ valid_status_codes = [200, 302, 308]
+
+ @classmethod
+ def setUpClass(cls) -> None:
+ super().setUpClass()
+ WebHost.copy_tutorials_files_to_static()
+
+ def test_sitemap_route(self) -> None:
+ """Verify that the sitemap route works correctly and renders the template without errors."""
+ with self.app.test_request_context():
+ # Test the /sitemap route
+ with self.client.open("/sitemap") as response:
+ self.assertEqual(response.status_code, 200)
+ self.assertIn(b"Site Map", response.data)
+
+ # Test the /index route which should also serve the sitemap
+ with self.client.open("/index") as response:
+ self.assertEqual(response.status_code, 200)
+ self.assertIn(b"Site Map", response.data)
+
+ # Test using url_for with the function name
+ with self.client.open(url_for('get_sitemap')) as response:
+ self.assertEqual(response.status_code, 200)
+ self.assertIn(b'Site Map', response.data)
+
+ def test_sitemap_links(self) -> None:
+ """
+ Verify that all links in the sitemap are valid by making a request to each one.
+ """
+ with self.app.test_request_context():
+ with self.client.open(url_for("get_sitemap")) as response:
+ self.assertEqual(response.status_code, 200)
+ html_content = response.data.decode()
+
+ # Extract all href links using regex
+ href_pattern = re.compile(r'href=["\'](.*?)["\']')
+ links = href_pattern.findall(html_content)
+
+ self.assertTrue(len(links) > 0, "No links found in sitemap")
+
+ # Test each link
+ for link in links:
+ # Skip external links
+ if link.startswith(("http://", "https://")):
+ continue
+
+ link = urllib.parse.unquote(html.unescape(link))
+
+ with self.client.open(link) as response, self.subTest(link=link):
+ self.assertIn(response.status_code, self.valid_status_codes,
+ f"Link {link} returned invalid status code {response.status_code}")