From e9937b29cac6a91616aab104095d95165fd2c4fc Mon Sep 17 00:00:00 2001 From: Hayden Haines Date: Wed, 29 Apr 2026 09:47:33 -0500 Subject: [PATCH] fix: route dashboard requests without query strings --- dashboard.py | 9 ++++++--- tests/test_dashboard.py | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/dashboard.py b/dashboard.py index ebf8d5f..a660b48 100644 --- a/dashboard.py +++ b/dashboard.py @@ -8,6 +8,7 @@ from http.server import HTTPServer, BaseHTTPRequestHandler from pathlib import Path from datetime import datetime +from urllib.parse import urlparse DB_PATH = Path.home() / ".claude" / "usage.db" @@ -1242,13 +1243,14 @@ def log_message(self, format, *args): pass def do_GET(self): - if self.path in ("/", "/index.html"): + path = urlparse(self.path).path + if path in ("/", "/index.html"): self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") self.end_headers() self.wfile.write(HTML_TEMPLATE.encode("utf-8")) - elif self.path == "/api/data": + elif path == "/api/data": data = get_dashboard_data() body = json.dumps(data).encode("utf-8") self.send_response(200) @@ -1262,7 +1264,8 @@ def do_GET(self): self.end_headers() def do_POST(self): - if self.path == "/api/rescan": + path = urlparse(self.path).path + if path == "/api/rescan": # Full rebuild: delete DB and rescan from scratch. # Pass DB_PATH / DEFAULT_PROJECTS_DIRS explicitly so tests that # patch the module globals are honored (scan's defaults are diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index 76287d1..b601805 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -166,6 +166,13 @@ def test_index_returns_html(self): self.assertEqual(resp.status, 200) self.assertIn("text/html", resp.headers["Content-Type"]) + def test_index_with_query_string_returns_html(self): + url = f"http://127.0.0.1:{self.port}/?range=30d" + with urllib.request.urlopen(url) as resp: + self.assertEqual(resp.status, 200) + self.assertIn("text/html", resp.headers["Content-Type"]) + self.assertIn(b"Claude Code Usage Dashboard", resp.read()) + def test_api_data_returns_json(self): url = f"http://127.0.0.1:{self.port}/api/data" with urllib.request.urlopen(url) as resp: @@ -175,6 +182,14 @@ def test_api_data_returns_json(self): # Should have expected keys (or error if no DB) self.assertTrue("all_models" in data or "error" in data) + def test_api_data_with_query_string_returns_json(self): + url = f"http://127.0.0.1:{self.port}/api/data?range=all" + with urllib.request.urlopen(url) as resp: + self.assertEqual(resp.status, 200) + self.assertIn("application/json", resp.headers["Content-Type"]) + data = json.loads(resp.read()) + self.assertTrue("all_models" in data or "error" in data) + def test_api_rescan_returns_json(self): url = f"http://127.0.0.1:{self.port}/api/rescan" req = urllib.request.Request(url, method="POST") @@ -186,6 +201,17 @@ def test_api_rescan_returns_json(self): self.assertIn("updated", data) self.assertIn("skipped", data) + def test_api_rescan_with_query_string_returns_json(self): + url = f"http://127.0.0.1:{self.port}/api/rescan?source=dashboard" + req = urllib.request.Request(url, method="POST") + with urllib.request.urlopen(req) as resp: + self.assertEqual(resp.status, 200) + self.assertIn("application/json", resp.headers["Content-Type"]) + data = json.loads(resp.read()) + self.assertIn("new", data) + self.assertIn("updated", data) + self.assertIn("skipped", data) + def test_404_for_unknown_path(self): url = f"http://127.0.0.1:{self.port}/nonexistent" try: