Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions frontend_multi_user/tests/test_plan_telemetry_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,14 @@ def test_read_activity_overview_prefers_task_column(self) -> None:
def test_sanitize_legacy_run_zip_for_download_removes_track_activity(self) -> None:
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, "w", compression=zipfile.ZIP_DEFLATED) as archive:
archive.writestr("030-report.html", "<html>ok</html>")
archive.writestr("report.html", "<html>ok</html>")
archive.writestr("nested/track_activity.jsonl", "{\"event\":\"secret\"}\n")
sanitized = self.app_obj._sanitize_legacy_run_zip_for_download(buffer.getvalue())
self.assertIsNotNone(sanitized)
assert sanitized is not None
with zipfile.ZipFile(sanitized, "r") as archive:
files = sorted(archive.namelist())
self.assertIn("030-report.html", files)
self.assertIn("report.html", files)
self.assertNotIn("nested/track_activity.jsonl", files)

def test_build_plan_telemetry_uses_activity_overview_fallback_without_metrics(self) -> None:
Expand Down
2 changes: 1 addition & 1 deletion mcp_cloud/db_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def ensure_last_progress_at_column() -> None:

WORKER_PLAN_URL = os.environ.get("PLANEXE_WORKER_PLAN_URL", "http://worker_plan:8000")

REPORT_FILENAME = "030-report.html"
REPORT_FILENAME = "report.html"
REPORT_CONTENT_TYPE = "text/html; charset=utf-8"
ZIP_FILENAME = "run.zip"
ZIP_CONTENT_TYPE = "application/zip"
Expand Down
8 changes: 4 additions & 4 deletions mcp_cloud/tests/test_download_rate_limit.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def test_non_download_path_is_not_rate_limited(self):
self.assertIsNone(result)

def test_download_path_is_rate_limited(self):
request = _fake_request("/download/abc-123/030-report.html")
request = _fake_request("/download/abc-123/report.html")
for _ in range(http_server.DOWNLOAD_RATE_LIMIT_REQUESTS):
result = asyncio.run(http_server._enforce_download_rate_limit(request))
self.assertIsNone(result)
Expand All @@ -35,8 +35,8 @@ def test_download_path_is_rate_limited(self):
self.assertEqual(result.status_code, 429)

def test_different_clients_have_separate_buckets(self):
req_a = _fake_request("/download/abc/030-report.html", client_host="10.0.0.1")
req_b = _fake_request("/download/abc/030-report.html", client_host="10.0.0.2")
req_a = _fake_request("/download/abc/report.html", client_host="10.0.0.1")
req_b = _fake_request("/download/abc/report.html", client_host="10.0.0.2")
for _ in range(http_server.DOWNLOAD_RATE_LIMIT_REQUESTS):
asyncio.run(http_server._enforce_download_rate_limit(req_a))
# Client A is exhausted
Expand All @@ -47,7 +47,7 @@ def test_different_clients_have_separate_buckets(self):
self.assertIsNone(result_b)

def test_disabled_when_limit_is_zero(self):
request = _fake_request("/download/abc/030-report.html")
request = _fake_request("/download/abc/report.html")
original = server_boot.DOWNLOAD_RATE_LIMIT_REQUESTS
try:
server_boot.DOWNLOAD_RATE_LIMIT_REQUESTS = 0
Expand Down
32 changes: 16 additions & 16 deletions mcp_cloud/tests/test_download_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,61 +20,61 @@ def tearDown(self):
self._secret_patch.stop()

def test_valid_token_accepted(self):
token = cloud_app.generate_download_token("task-abc", "030-report.html")
self.assertTrue(cloud_app.validate_download_token(token, "task-abc", "030-report.html"))
token = cloud_app.generate_download_token("task-abc", "report.html")
self.assertTrue(cloud_app.validate_download_token(token, "task-abc", "report.html"))

def test_wrong_plan_id_rejected(self):
token = cloud_app.generate_download_token("task-abc", "030-report.html")
self.assertFalse(cloud_app.validate_download_token(token, "task-xyz", "030-report.html"))
token = cloud_app.generate_download_token("task-abc", "report.html")
self.assertFalse(cloud_app.validate_download_token(token, "task-xyz", "report.html"))

def test_wrong_filename_rejected(self):
token = cloud_app.generate_download_token("task-abc", "030-report.html")
token = cloud_app.generate_download_token("task-abc", "report.html")
self.assertFalse(cloud_app.validate_download_token(token, "task-abc", "run.zip"))

def test_tampered_mac_rejected(self):
token = cloud_app.generate_download_token("task-abc", "030-report.html")
token = cloud_app.generate_download_token("task-abc", "report.html")
expiry, _ = token.split(".", 1)
tampered = f"{expiry}.{'0' * 64}"
self.assertFalse(cloud_app.validate_download_token(tampered, "task-abc", "030-report.html"))
self.assertFalse(cloud_app.validate_download_token(tampered, "task-abc", "report.html"))

def test_expired_token_rejected(self):
# Generate a token that expired 1 second ago.
past_expiry = int(time.time()) - 1
import hashlib, hmac
message = f"task-abc:030-report.html:{past_expiry}".encode()
message = f"task-abc:report.html:{past_expiry}".encode()
mac = hmac.new(b"test-secret-for-unit-tests", message, hashlib.sha256).hexdigest()
expired_token = f"{past_expiry}.{mac}"
self.assertFalse(cloud_app.validate_download_token(expired_token, "task-abc", "030-report.html"))
self.assertFalse(cloud_app.validate_download_token(expired_token, "task-abc", "report.html"))

def test_malformed_token_rejected(self):
for bad in ("", "nodot", "abc.def.extra", "notanint.abc"):
with self.subTest(token=bad):
self.assertFalse(cloud_app.validate_download_token(bad, "task-abc", "030-report.html"))
self.assertFalse(cloud_app.validate_download_token(bad, "task-abc", "report.html"))

def test_token_contains_expiry_and_mac(self):
token = cloud_app.generate_download_token("task-abc", "030-report.html")
token = cloud_app.generate_download_token("task-abc", "report.html")
parts = token.split(".")
self.assertEqual(len(parts), 2)
expiry_str, mac = parts
self.assertTrue(expiry_str.isdigit())
self.assertEqual(len(mac), 64) # SHA-256 hex = 64 chars

def test_expiry_is_in_the_future(self):
token = cloud_app.generate_download_token("task-abc", "030-report.html")
token = cloud_app.generate_download_token("task-abc", "report.html")
expiry = int(token.split(".")[0])
self.assertGreater(expiry, int(time.time()))

def test_different_tasks_get_different_tokens(self):
t1 = cloud_app.generate_download_token("task-aaa", "030-report.html")
t2 = cloud_app.generate_download_token("task-bbb", "030-report.html")
t1 = cloud_app.generate_download_token("task-aaa", "report.html")
t2 = cloud_app.generate_download_token("task-bbb", "report.html")
self.assertNotEqual(t1, t2)

def test_report_url_contains_token(self):
with patch.object(_dt_mod, "_get_download_base_url", return_value="https://example.com"):
url = cloud_app.build_report_download_url("task-abc")
self.assertIsNotNone(url)
self.assertIn("?token=", url)
self.assertIn("/download/task-abc/030-report.html", url)
self.assertIn("/download/task-abc/report.html", url)

def test_zip_url_contains_token(self):
with patch.object(_dt_mod, "_get_download_base_url", return_value="https://example.com"):
Expand All @@ -87,7 +87,7 @@ def test_token_embedded_in_report_url_is_valid(self):
with patch.object(_dt_mod, "_get_download_base_url", return_value="https://example.com"):
url = cloud_app.build_report_download_url("task-abc")
token = url.split("?token=")[1]
self.assertTrue(cloud_app.validate_download_token(token, "task-abc", "030-report.html"))
self.assertTrue(cloud_app.validate_download_token(token, "task-abc", "report.html"))

def test_token_embedded_in_zip_url_is_valid(self):
with patch.object(_dt_mod, "_get_download_base_url", return_value="https://example.com"):
Expand Down
2 changes: 1 addition & 1 deletion mcp_local/planexe_mcp_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
logger = logging.getLogger(__name__)

DEFAULT_MCP_URL = "https://your-railway-app.up.railway.app/mcp"
REPORT_FILENAME = "030-report.html"
REPORT_FILENAME = "report.html"
ZIP_FILENAME = "run.zip"
ModelProfileInput = Literal[
"baseline",
Expand Down