mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 19:20:16 +00:00
9e45de463d
Two-part fix: - Backend: handle_get returns True (not None from j()) for compress/status route, preventing edge-case 404 fallback in do_GET - Frontend: resumeManualCompressionForSession silently returns on 404 instead of showing "Compression failed: not found" toast Includes 6 regression tests covering backend return value, idle/empty session responses, and frontend 404 guard presence.
140 lines
4.7 KiB
Python
140 lines
4.7 KiB
Python
"""
|
|
Regression test: /api/session/compress/status must not return 404.
|
|
|
|
Bug: switching sessions triggered resumeManualCompressionForSession(),
|
|
which called GET /api/session/compress/status. The route handler returned
|
|
None (from j()) instead of True, causing do_GET's fallback to emit
|
|
{"error":"not found"} 404 in edge cases. The frontend then showed
|
|
"Compression failed: not found" toast on every session switch.
|
|
|
|
Fix (two-part):
|
|
1. Backend: handle_get now returns True after _handle_session_compress_status
|
|
2. Frontend: resumeManualCompressionForSession catches 404 silently
|
|
"""
|
|
|
|
import io
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from api.routes import _handle_session_compress_status, handle_get
|
|
from tests._pytest_port import BASE
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Reuse the _FakeHandler pattern from test_sprint46
|
|
# ---------------------------------------------------------------------------
|
|
class _FakeHandler:
|
|
def __init__(self):
|
|
self.wfile = io.BytesIO()
|
|
self.status = None
|
|
self.sent_headers = {}
|
|
|
|
def send_response(self, status):
|
|
self.status = status
|
|
|
|
def send_header(self, key, value):
|
|
self.sent_headers[key] = value
|
|
|
|
def end_headers(self):
|
|
pass
|
|
|
|
def payload(self):
|
|
return json.loads(self.wfile.getvalue().decode("utf-8"))
|
|
|
|
|
|
# ======== Backend tests ========
|
|
|
|
|
|
def test_compress_status_returns_200_idle_for_unknown_session():
|
|
"""The idle case (no active compression) must return 200, not 404."""
|
|
handler = _FakeHandler()
|
|
result = _handle_session_compress_status(handler, "nonexistent_session_xyz")
|
|
body = handler.payload()
|
|
|
|
assert handler.status == 200
|
|
assert body["ok"] is True
|
|
assert body["status"] == "idle"
|
|
|
|
|
|
def test_compress_status_returns_200_idle_for_empty_session_id():
|
|
"""Empty session_id should return 400 (bad), not 404."""
|
|
handler = _FakeHandler()
|
|
_handle_session_compress_status(handler, "")
|
|
assert handler.status == 400
|
|
|
|
|
|
def test_handle_get_returns_true_for_compress_status():
|
|
"""handle_get must return True (not None/False) for compress/status.
|
|
|
|
This is the core fix: previously it returned None (from j()), which
|
|
only worked because 'None is False' is False. But in edge cases
|
|
(stale process state, exception during response write) the fallback
|
|
could produce a 404. Returning True is defensive.
|
|
"""
|
|
from urllib.parse import urlparse
|
|
|
|
handler = _FakeHandler()
|
|
parsed = urlparse("/api/session/compress/status?session_id=test_resume_sid")
|
|
result = handle_get(handler, parsed)
|
|
|
|
assert result is True, f"handle_get returned {result!r}, expected True"
|
|
assert handler.status == 200
|
|
|
|
|
|
def test_handle_get_returns_true_for_compress_status_no_sid():
|
|
"""Even with missing session_id, handle_get returns True (400 handled internally)."""
|
|
from urllib.parse import urlparse
|
|
|
|
handler = _FakeHandler()
|
|
parsed = urlparse("/api/session/compress/status")
|
|
result = handle_get(handler, parsed)
|
|
|
|
assert result is True
|
|
# _handle_session_compress_status should return 400 for empty sid
|
|
assert handler.status == 400
|
|
|
|
|
|
# ======== Frontend static tests ========
|
|
|
|
|
|
def _read_commands_js():
|
|
with open(
|
|
Path(__file__).resolve().parents[1] / "static" / "commands.js",
|
|
encoding="utf-8",
|
|
) as f:
|
|
return f.read()
|
|
|
|
|
|
def test_frontend_resume_404_silent():
|
|
"""resumeManualCompressionForSession must silently return on 404/5xx.
|
|
|
|
The catch block should check for 404 and 5xx and return early, so
|
|
switching sessions never shows 'Compression failed' on transient errors.
|
|
"""
|
|
src = _read_commands_js()
|
|
|
|
# Find the resumeManualCompressionForSession function
|
|
assert "async function resumeManualCompressionForSession" in src
|
|
|
|
# The guard must be present in the catch block
|
|
assert "e.status===404" in src
|
|
assert "e.status>=500" in src
|
|
# Verify it's inside the catch block of resumeManualCompressionForSession
|
|
fn_start = src.index("async function resumeManualCompressionForSession")
|
|
# Find the catch block after this function
|
|
catch_idx = src.index("}catch(e){", fn_start)
|
|
guard_404 = src.index("e.status===404", fn_start)
|
|
guard_500 = src.index("e.status>=500", fn_start)
|
|
assert catch_idx < guard_404 < guard_500, "guards must be inside catch block"
|
|
|
|
# The guard must return early (not just log)
|
|
line_with_guard = src[guard_404 : src.index("\n", guard_500) + 80]
|
|
assert "return" in line_with_guard, "guard must return early"
|
|
|
|
|
|
def test_frontend_compress_status_call_present():
|
|
"""Verify the compress/status API call is still in the frontend code."""
|
|
src = _read_commands_js()
|
|
assert "/api/session/compress/status" in src
|
|
assert "resumeManualCompressionForSession" in src
|