#!/usr/bin/env python3 """Test ALL web UI routes and report any returning 500 errors. Runs against the Flask test client with dev=True so admin routes are accessible. Queries SQLite DB for real IDs needed by dynamic routes. """ import os import sys import sqlite3 import traceback # Must run from src/ directory src_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'src') sys.path.insert(0, src_dir) DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'data', 'drafts.db') def get_test_ids(): """Query the database for real IDs to use in dynamic routes.""" conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row ids = {} # Get a draft name row = conn.execute("SELECT name FROM drafts LIMIT 1").fetchone() ids["draft_name"] = row["name"] if row else "draft-nonexistent" # Get a person_id from authors row = conn.execute("SELECT person_id FROM authors WHERE person_id IS NOT NULL LIMIT 1").fetchone() ids["person_id"] = row["person_id"] if row else 1 # Get a gap_id row = conn.execute("SELECT id FROM gaps LIMIT 1").fetchone() ids["gap_id"] = row["id"] if row else 1 # Get an idea_id row = conn.execute("SELECT id FROM ideas LIMIT 1").fetchone() ids["idea_id"] = row["id"] if row else 1 # Get a proposal_id (may not exist) try: row = conn.execute("SELECT id FROM proposals LIMIT 1").fetchone() ids["proposal_id"] = row["id"] if row else None except Exception: ids["proposal_id"] = None # Get a blog_draft_id (may not exist) try: row = conn.execute("SELECT id FROM blog_drafts LIMIT 1").fetchone() ids["blog_draft_id"] = row["id"] if row else None except Exception: ids["blog_draft_id"] = None # Get two draft names for compare rows = conn.execute("SELECT name FROM drafts LIMIT 2").fetchall() ids["two_drafts"] = ",".join(r["name"] for r in rows) if len(rows) >= 2 else "" conn.close() return ids def main(): ids = get_test_ids() print(f"Test IDs: draft={ids['draft_name']}, person={ids['person_id']}, " f"gap={ids['gap_id']}, idea={ids['idea_id']}, " f"proposal={ids['proposal_id']}, blog={ids['blog_draft_id']}") print() from webui.app import create_app app = create_app(dev=True) client = app.test_client() # Define all routes to test: (method, url, description) routes = [ # === Pages (public) === ("GET", "/", "overview"), ("GET", "/drafts", "drafts list"), ("GET", f"/drafts/{ids['draft_name']}", "draft detail"), ("GET", "/ideas", "ideas"), ("GET", "/ratings", "ratings"), ("GET", "/timeline", "timeline"), ("GET", "/idea-clusters", "idea clusters"), ("GET", "/architecture", "architecture"), ("GET", f"/authors/{ids['person_id']}", "author detail"), ("GET", "/authors", "authors"), ("GET", "/citations", "citations"), ("GET", "/about", "about"), ("GET", "/impressum", "impressum"), ("GET", "/datenschutz", "datenschutz"), ("GET", "/search", "search (empty)"), ("GET", "/search?q=agent", "search (query)"), ("GET", "/ask", "ask (empty)"), # === API (public) === ("GET", "/api/drafts", "api drafts"), ("GET", "/api/stats", "api stats"), ("GET", "/api/authors/network", "api author network"), ("GET", "/api/citations", "api citations"), ("GET", "/api/search?q=agent", "api search"), ("GET", "/api/ideas", "api ideas"), ("GET", "/api/ratings", "api ratings"), ("GET", "/api/timeline", "api timeline"), ("GET", "/api/idea-clusters", "api idea clusters"), ("GET", "/api/categories", "api categories"), ("GET", f"/api/drafts/{ids['draft_name']}", "api draft detail"), ("GET", "/api/architecture", "api architecture"), # === Admin pages === ("GET", "/gaps", "gaps"), ("GET", "/gaps/demo", "gaps demo"), ("GET", f"/gaps/{ids['gap_id']}", "gap detail"), ("GET", "/api/gaps", "api gaps"), ("GET", f"/api/gaps/{ids['gap_id']}", "api gap detail"), ("GET", "/monitor", "monitor"), ("GET", "/api/monitor", "api monitor"), ("GET", "/admin/analytics", "analytics"), ("GET", "/landscape", "landscape"), ("GET", "/api/landscape", "api landscape"), ("GET", "/similarity", "similarity"), ("GET", "/api/similarity", "api similarity"), ("GET", "/compare", "compare (empty)"), ("GET", f"/compare?drafts={ids['two_drafts']}", "compare (with drafts)"), ("GET", "/sources", "sources"), ("GET", "/false-positives", "false positives"), ("GET", "/api/sources", "api sources"), ("GET", "/api/false-positives", "api false positives"), ("GET", "/api/citations/influence", "api citation influence"), ("GET", "/api/citations/bcp", "api bcp analysis"), ("GET", "/idea-analysis", "idea analysis"), ("GET", "/api/idea-analysis", "api idea analysis"), ("GET", f"/ideas/{ids['idea_id']}", "idea detail"), ("GET", "/trends", "trends"), ("GET", "/complexity", "complexity"), ("GET", "/api/trends", "api trends"), ("GET", "/api/complexity", "api complexity"), ("GET", "/proposals", "proposals"), ("GET", "/proposals/new", "proposal new (GET)"), ("GET", "/api/proposals", "api proposals"), ("GET", "/proposals/intake", "proposal intake (GET)"), ("GET", "/blog", "blog drafts"), ("GET", "/blog/generate", "blog generate (GET)"), ("GET", "/export/obsidian", "obsidian export"), ] # Add conditional routes that need existing records if ids["proposal_id"]: routes.extend([ ("GET", f"/proposals/{ids['proposal_id']}", "proposal detail"), ("GET", f"/proposals/{ids['proposal_id']}/edit", "proposal edit (GET)"), ("GET", f"/api/proposals/{ids['proposal_id']}", "api proposal detail"), ]) if ids["blog_draft_id"]: routes.extend([ ("GET", f"/blog/{ids['blog_draft_id']}", "blog detail"), ("GET", f"/blog/{ids['blog_draft_id']}/edit", "blog edit (GET)"), ("GET", f"/blog/{ids['blog_draft_id']}/export", "blog export"), ]) # Run tests results = {"ok": [], "error_500": [], "error_other": [], "exception": []} for method, url, desc in routes: try: if method == "GET": resp = client.get(url) elif method == "POST": resp = client.post(url, json={}) else: continue status = resp.status_code if status == 500: # Try to extract error from response error_text = resp.data.decode("utf-8", errors="replace")[:500] results["error_500"].append((desc, url, status, error_text)) print(f" FAIL 500 {desc:30s} {url}") elif status >= 400: results["error_other"].append((desc, url, status)) print(f" WARN {status} {desc:30s} {url}") else: results["ok"].append((desc, url, status)) print(f" OK {status} {desc:30s} {url}") except Exception as e: tb = traceback.format_exc() results["exception"].append((desc, url, str(e), tb)) print(f" EXCP {desc:30s} {url} -> {e}") # Summary print("\n" + "=" * 70) print(f"SUMMARY: {len(results['ok'])} OK, {len(results['error_500'])} x 500, " f"{len(results['error_other'])} x 4xx, {len(results['exception'])} exceptions") print("=" * 70) if results["error_500"]: print("\n--- 500 ERRORS (BROKEN ROUTES) ---") for desc, url, status, error_text in results["error_500"]: print(f"\n Route: {desc}") print(f" URL: {url}") print(f" Error preview:") # Show just enough of the error for line in error_text.split("\n")[:10]: print(f" {line}") if results["exception"]: print("\n--- EXCEPTIONS (APP CRASHED) ---") for desc, url, err, tb in results["exception"]: print(f"\n Route: {desc}") print(f" URL: {url}") print(f" Exception: {err}") # Show last few lines of traceback tb_lines = tb.strip().split("\n") for line in tb_lines[-5:]: print(f" {line}") if results["error_other"]: print("\n--- 4xx RESPONSES (may be expected) ---") for desc, url, status in results["error_other"]: print(f" {status} {desc:30s} {url}") if __name__ == "__main__": main()