项目文件夹

文件
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

364 行
14 KiB
Python

"""
Test JavaScript URL configuration consistency
This tests that the JavaScript URL configuration matches the backend routes
"""
import re
from pathlib import Path
import pytest
from local_deep_research.web.routes.route_registry import (
get_all_routes,
)
class TestJavaScriptURLConfiguration:
"""Test that JavaScript URL configuration matches backend routes"""
@pytest.fixture
def js_urls_content(self):
"""Load the JavaScript URLs configuration file"""
js_file_path = (
Path(__file__).parent.parent.parent
/ "src"
/ "local_deep_research"
/ "web"
/ "static"
/ "js"
/ "config"
/ "urls.js"
)
with open(js_file_path, "r") as f:
return f.read()
def extract_js_urls(self, js_content):
"""Extract URL patterns from JavaScript content"""
urls = {}
# More robust extraction pattern that handles multiline and comments
# Extract the URLS object
urls_match = re.search(
r"const\s+URLS\s*=\s*\{(.*?)\n\};", js_content, re.DOTALL
)
if not urls_match:
return urls
urls_content = urls_match.group(1)
# Extract each section
sections = {
"API": r"API:\s*\{(.*?)\n\s*\}",
"PAGES": r"PAGES:\s*\{(.*?)\n\s*\}",
"HISTORY_API": r"HISTORY_API:\s*\{(.*?)\n\s*\}",
"SETTINGS_API": r"SETTINGS_API:\s*\{(.*?)\n\s*\}",
"METRICS_API": r"METRICS_API:\s*\{(.*?)\n\s*\}",
}
for section_name, section_pattern in sections.items():
section_match = re.search(section_pattern, urls_content, re.DOTALL)
if section_match:
section_content = section_match.group(1)
# Extract key-value pairs, handling comments and multiline
kv_pattern = r"(\w+):\s*'([^']+)'(?:\s*,|\s*(?://[^\n]*)?\s*$)"
for match in re.finditer(kv_pattern, section_content):
key, url = match.groups()
urls[f"{section_name}.{key}"] = url
return urls
def normalize_url_pattern(self, url):
"""Normalize URL patterns for comparison"""
# Replace {id} with Flask-style <int:id> or <id>
url = re.sub(r"\{id\}", "<string:research_id>", url)
url = re.sub(r"\{key\}", "<path:key>", url)
url = re.sub(r"\{(\w+)\}", r"<\1>", url)
return url
def test_js_urls_exist(self, js_urls_content):
"""Test that the JavaScript URLs file exists and has content"""
assert js_urls_content
assert "URLS" in js_urls_content
assert "URLBuilder" in js_urls_content
def test_critical_api_urls_match_backend(self, js_urls_content):
"""Test that critical API URLs in JS match backend routes"""
js_urls = self.extract_js_urls(js_urls_content)
backend_routes = get_all_routes()
# Create a map of backend routes for easy lookup
backend_map = {}
for route in backend_routes:
# Normalize the path for comparison
path = route["path"]
backend_map[path] = route
# Critical URLs that must match
critical_mappings = [
("API.START_RESEARCH", "/api/start_research"),
("API.RESEARCH_STATUS", "/api/research/<research_id>/status"),
("API.RESEARCH_DETAILS", "/api/research/<string:research_id>"),
("API.RESEARCH_LOGS", "/api/research/<string:research_id>/logs"),
("API.RESEARCH_REPORT", "/api/report/<string:research_id>"),
("API.TERMINATE_RESEARCH", "/api/terminate/<string:research_id>"),
("API.DELETE_RESEARCH", "/api/delete/<string:research_id>"),
("API.HISTORY", "/history/api"),
("PAGES.HOME", "/"),
("PAGES.HISTORY", "/history/"),
("PAGES.SETTINGS", "/settings/"),
("PAGES.METRICS", "/metrics/"),
]
for js_key, expected_backend_path in critical_mappings:
assert js_key in js_urls, f"Missing JS URL key: {js_key}"
js_url = js_urls[js_key]
normalized_js_url = self.normalize_url_pattern(js_url)
# Check if the URL exists in backend (with some flexibility for parameter names)
found = False
for backend_path in backend_map:
if self.urls_match(normalized_js_url, backend_path):
found = True
break
assert found, (
f"JS URL {js_key}='{js_url}' not found in backend routes"
)
def urls_match(self, js_url, backend_url):
"""Check if two URLs match, accounting for parameter differences"""
# Normalize both URLs
js_parts = js_url.strip("/").split("/")
backend_parts = backend_url.strip("/").split("/")
if len(js_parts) != len(backend_parts):
return False
for js_part, backend_part in zip(js_parts, backend_parts, strict=False):
# If either part is a parameter, consider it a match
if "<" in js_part or "<" in backend_part:
continue
# Otherwise, parts must match exactly
if js_part != backend_part:
return False
return True
def test_url_builder_methods_exist(self, js_urls_content):
"""Test that URLBuilder utility methods are defined"""
expected_methods = [
"build",
"buildWithReplacements",
"progressPage",
"resultsPage",
"detailsPage",
"researchStatus",
"researchDetails",
"researchLogs",
"researchReport",
"terminateResearch",
"deleteResearch",
"extractResearchId",
"getCurrentPageType",
]
for method in expected_methods:
assert (
f"{method}(" in js_urls_content
or f"{method}:" in js_urls_content
), f"URLBuilder method '{method}' not found"
def test_no_hardcoded_urls_in_pattern(self, js_urls_content):
"""Test that URLs follow consistent patterns"""
js_urls = self.extract_js_urls(js_urls_content)
# Check for consistent parameter naming
for key, url in js_urls.items():
# Research ID parameters should use {id}
if "research" in key.lower() and "{" in url:
assert "{id}" in url or "{research_id}" in url, (
f"Research URL {key} should use {{id}} parameter: {url}"
)
# No URLs should have trailing slashes except root paths
if (
url != "/"
and not url.endswith("/{id}")
and not url.endswith("/{key}")
):
# Allow trailing slash for directory-like paths
if url.count("/") > 2:
assert not url.endswith("//"), (
f"URL {key} has double slash: {url}"
)
def test_api_url_patterns(self, js_urls_content):
"""Test that API URLs follow RESTful patterns"""
js_urls = self.extract_js_urls(js_urls_content)
api_urls = {k: v for k, v in js_urls.items() if k.startswith("API.")}
for key, url in api_urls.items():
# API URLs should start with /api (with some exceptions)
if key not in ["API.HISTORY", "API.HEALTH"]: # Known exceptions
assert url.startswith("/api"), (
f"API URL {key} should start with /api: {url}"
)
# Check REST patterns
if "DELETE" in key:
# Delete operations should have an ID parameter
assert "{id}" in url, (
f"DELETE URL {key} should have ID parameter: {url}"
)
if "SAVE" in key or "CREATE" in key:
# Save/Create operations typically don't have ID in URL
if (
"RATING" not in key
): # Exception for ratings which need research ID
assert "{id}" not in url, (
f"CREATE/SAVE URL {key} shouldn't have ID in path: {url}"
)
def test_settings_api_consistency(self, js_urls_content):
"""Test that settings API URLs are consistent"""
js_urls = self.extract_js_urls(js_urls_content)
settings_urls = {
k: v for k, v in js_urls.items() if k.startswith("SETTINGS_API.")
}
# All settings API URLs should start with /settings/
for key, url in settings_urls.items():
assert url.startswith("/settings/"), (
f"Settings API URL {key} should start with /settings/: {url}"
)
# Check that CRUD operations exist
assert "SETTINGS_API.GET_SETTING" in js_urls
assert "SETTINGS_API.UPDATE_SETTING" in js_urls
assert "SETTINGS_API.DELETE_SETTING" in js_urls
def test_metrics_api_consistency(self, js_urls_content):
"""Test that metrics API URLs are consistent"""
js_urls = self.extract_js_urls(js_urls_content)
metrics_urls = {
k: v for k, v in js_urls.items() if k.startswith("METRICS_API.")
}
# All metrics API URLs should start with /metrics/
for key, url in metrics_urls.items():
assert url.startswith("/metrics/"), (
f"Metrics API URL {key} should start with /metrics/: {url}"
)
# Check research-specific metrics URLs have ID parameter
research_metrics = [
"RESEARCH",
"RESEARCH_TIMELINE",
"RESEARCH_SEARCH",
"RATINGS_GET",
"RATINGS_SAVE",
"RESEARCH_COSTS",
]
for metric in research_metrics:
key = f"METRICS_API.{metric}"
if key in js_urls:
assert "{id}" in js_urls[key], (
f"Metrics URL {key} should have {{id}} parameter: {js_urls[key]}"
)
def test_page_routes_consistency(self, js_urls_content):
"""Test that page routes are consistent"""
js_urls = self.extract_js_urls(js_urls_content)
# Pages with IDs should have {id} parameter
id_pages = ["PROGRESS", "RESULTS", "DETAILS"]
for page in id_pages:
key = f"PAGES.{page}"
if key in js_urls:
assert "{id}" in js_urls[key], (
f"Page URL {key} should have {{id}} parameter: {js_urls[key]}"
)
# Root pages should end with /
root_pages = ["HOME", "HISTORY", "SETTINGS", "METRICS"]
for page in root_pages:
key = f"PAGES.{page}"
if key in js_urls and page != "HOME": # HOME is just '/'
assert js_urls[key].endswith("/"), (
f"Root page URL {key} should end with /: {js_urls[key]}"
)
def test_url_builder_convenience_methods_match_urls(self, js_urls_content):
"""Test that URLBuilder convenience methods use correct URL constants"""
# Extract method implementations
method_checks = [
("progressPage", "URLS.PAGES.PROGRESS"),
("resultsPage", "URLS.PAGES.RESULTS"),
("detailsPage", "URLS.PAGES.DETAILS"),
("researchStatus", "URLS.API.RESEARCH_STATUS"),
("researchDetails", "URLS.API.RESEARCH_DETAILS"),
("researchLogs", "URLS.API.RESEARCH_LOGS"),
("researchReport", "URLS.API.RESEARCH_REPORT"),
("terminateResearch", "URLS.API.TERMINATE_RESEARCH"),
("deleteResearch", "URLS.API.DELETE_RESEARCH"),
]
for method_name, expected_url_const in method_checks:
# Find the method implementation
method_pattern = rf"{method_name}\([^)]*\)\s*\{{[^}}]*\}}"
method_match = re.search(method_pattern, js_urls_content, re.DOTALL)
assert method_match, f"Method {method_name} not found"
method_body = method_match.group(0)
assert expected_url_const in method_body, (
f"Method {method_name} should use {expected_url_const}"
)
def test_no_duplicate_urls(self, js_urls_content):
"""Test that there are no duplicate URL patterns"""
js_urls = self.extract_js_urls(js_urls_content)
# Normalize URLs for comparison
normalized_urls = {}
for key, url in js_urls.items():
normalized = self.normalize_url_pattern(url)
if normalized in normalized_urls:
# Allow some known duplicates
allowed_duplicates = [
# RESTful APIs use same URL with different methods
("SETTINGS_API.GET_SETTING", "SETTINGS_API.UPDATE_SETTING"),
(
"SETTINGS_API.UPDATE_SETTING",
"SETTINGS_API.DELETE_SETTING",
),
("SETTINGS_API.GET_SETTING", "SETTINGS_API.DELETE_SETTING"),
("METRICS_API.RATINGS_GET", "METRICS_API.RATINGS_SAVE"),
# History API might share patterns with main API
("HISTORY_API", "API"),
]
# Check if this is an allowed duplicate
is_allowed = False
for pattern1, pattern2 in allowed_duplicates:
if (
key.startswith(pattern1)
and normalized_urls[normalized].startswith(pattern2)
) or (
key.startswith(pattern2)
and normalized_urls[normalized].startswith(pattern1)
):
is_allowed = True
break
if not is_allowed:
pytest.fail(
f"Duplicate URL pattern found:\n"
f" {key} = {url}\n"
f" {normalized_urls[normalized]} = {url}\n"
f" (normalized: {normalized})"
)
normalized_urls[normalized] = key