Phase 1 low-risk backend batch (on top of Batch A). Both byte-verified vs PR head, Codex SAFE, 64 own + 749 regression tests pass, coexists with Batch A's #5833. - #5828 (ai-ag2026): frame CORS OPTIONS preflight with Content-Length: 0 (keep-alive hang fix) - #5834 (ai-ag2026): parse session _index.json from bytes not decoded str (all 10 sites) Co-authored-by: t <a@b> Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
69 lines
3.0 KiB
Python
69 lines
3.0 KiB
Python
"""Regression tests — the session index is parsed from bytes, not a decoded str.
|
|
|
|
`/api/sessions` and its rebuild path read the session index (a multi-MB JSON
|
|
list on a busy install) with `json.loads(SESSION_INDEX_FILE.read_text(
|
|
encoding='utf-8'))`. That decodes the whole file to a Python `str` first, then
|
|
re-scans it in the JSON parser. Handing the raw `bytes` to `json.loads` lets its
|
|
C parser decode UTF-8 in one pass — measured ~22% faster on a real 6.2 MB index
|
|
(46 ms -> 36 ms), on every cache-miss rebuild.
|
|
|
|
`json.loads` accepts `bytes` and auto-detects the (UTF-8) encoding, producing an
|
|
identical object for our BOM-less index. These pin the equivalence (including
|
|
non-ASCII content) and that no index reader silently reverts to the slower
|
|
decoded-string path.
|
|
"""
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import api.models as models
|
|
import api.routes as routes
|
|
|
|
|
|
def test_bytes_and_text_parse_identically(tmp_path: Path):
|
|
"""A UTF-8 index with non-ASCII titles parses identically from bytes."""
|
|
index = [
|
|
{"session_id": "s1", "title": "Grüße 🌍", "last_message_at": 1.0},
|
|
{"session_id": "s2", "title": "日本語のセッション", "last_message_at": 2.0},
|
|
]
|
|
idx = tmp_path / "_index.json"
|
|
idx.write_text(json.dumps(index, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
|
|
from_text = json.loads(idx.read_text(encoding="utf-8"))
|
|
from_bytes = json.loads(idx.read_bytes())
|
|
assert from_bytes == from_text == index
|
|
|
|
|
|
def test_all_sessions_reads_unicode_index_via_bytes(tmp_path, monkeypatch):
|
|
"""End-to-end: all_sessions reads a non-ASCII index correctly through the
|
|
bytes path (a str/bytes decode mismatch would corrupt or drop titles)."""
|
|
session_dir = tmp_path / "sessions"
|
|
session_dir.mkdir()
|
|
idx = session_dir / "_index.json"
|
|
entries = [
|
|
{"session_id": "unic-1", "title": "Café ☕", "last_message_at": 10.0},
|
|
]
|
|
idx.write_text(json.dumps(entries, ensure_ascii=False), encoding="utf-8")
|
|
|
|
monkeypatch.setattr(models, "SESSION_DIR", session_dir)
|
|
monkeypatch.setattr(models, "SESSION_INDEX_FILE", idx)
|
|
# Treat the indexed id as persisted so the prune step keeps it.
|
|
monkeypatch.setattr(models, "_persisted_session_ids_snapshot", lambda: frozenset({"unic-1"}))
|
|
monkeypatch.setattr(models, "_active_stream_ids", lambda: set())
|
|
|
|
result = models.all_sessions(include_lineage_metadata=False)
|
|
|
|
titles = {s.get("session_id"): s.get("title") for s in result}
|
|
assert titles.get("unic-1") == "Café ☕"
|
|
|
|
|
|
def test_no_index_reader_uses_decoded_str(tmp_path):
|
|
"""Guard against a refactor reintroducing the slower read_text path on the
|
|
session index."""
|
|
for module in (models, routes):
|
|
src = Path(module.__file__).read_text(encoding="utf-8")
|
|
assert "SESSION_INDEX_FILE.read_text" not in src, (
|
|
f"{module.__name__} reads the session index via read_text; "
|
|
"use read_bytes so json.loads parses UTF-8 in one pass"
|
|
)
|
|
assert "session_index_file.read_text" not in src
|