Files
Homer 3d51ba061e Tests that found things reading did not
The testing pass: 2140 tests to 2283, and four bugs that no amount of
reading had turned up. Three came from driving the JavaScript under a
Node DOM stub, which is the practice CLAUDE.md sets out and this is the
reason it does.

The terminal dropped every keystroke after a reconnect. `onclose` closed
over the module-level socket rather than its own, and close() queues its
event -- so the old socket's close arrived after a new one was assigned
and nulled the live one. Output kept coming, because onmessage is bound
to the object, while every send gates on the variable. It also announced
"Disconnected" about a shell that had just reconnected.

Two scripts were loaded twice on /messages, once by base.html and again
by the page. Each is an IIFE with its own state, so four keyboard
shortcuts toggled their panel twice and therefore did nothing, /help
opened two dialogs, and an @ mention attached its file twice. A sweep
refuses any template re-loading what base.html has.

The microphone had no guard while the permission prompt was up, so each
click opened another stream and only the last was ever stopped. And a
skill shared with you took its name out of your own library: create
checked uniqueness against what is *visible* rather than what is owned,
against a (owner_id, name) constraint, and told you to edit a row you
cannot edit.

--ink-faint failed the contrast minimum in both themes -- 3.85 and 3.19
against 4.5 -- so the smallest text on every screen was the hardest to
read. Measured in a headless browser rather than judged by eye.

And the suite runs on 3.11 and 3.12 now as well as 3.14. It had only ever
run on 3.14 while the image ships 3.12 and the packaging claimed 3.11:
the interpreter most people would run was the one nothing had tested.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 14:41:45 +02:00

106 lines
3.8 KiB
Python

"""The wire format every reply travels over, which had no test of its own.
`services/sse.py` is twenty lines and carries all of it. That is exactly the
kind of module that never gets one: too small to look risky, and load-bearing
enough that a subtle mistake is a stream which works until a model emits a
newline -- which is to say, until the first code block.
The events are parsed back the way the browser's `EventSource` does rather than
compared against an expected string. A test asserting the literal bytes passes
on a format that is wrong in the same way it was written.
"""
from __future__ import annotations
import pytest
from lembas.services.sse import KEEPALIVE, event
def parse(frame: str) -> tuple[str, str]:
"""Read one frame the way an `EventSource` does.
Field lines are `name: value`, the space after the colon is stripped, and
several `data:` lines in one event are joined with a newline. That last rule
is the whole reason `event()` exists.
"""
name = ""
data: list[str] = []
for line in frame.split("\n"):
if line.startswith("event:"):
name = line[len("event:") :].lstrip(" ")
elif line.startswith("data:"):
data.append(line[len("data:") :].lstrip(" "))
return name, "\n".join(data)
def test_a_plain_payload_survives_the_round_trip():
assert parse(event("render", "hello")) == ("render", "hello")
def test_a_frame_ends_with_a_blank_line():
"""The blank line is what tells the browser the event is over. Without it
nothing is dispatched at all and the stream simply appears to hang."""
assert event("render", "hello").endswith("\n\n")
@pytest.mark.parametrize(
"payload",
[
"line one\nline two",
"```python\nprint('hi')\n```",
"\nleading",
"trailing\n",
"two\n\nblank",
"<p>markup</p>\n<p>and more</p>",
],
)
def test_newlines_survive_because_they_are_split_across_data_lines(payload):
"""The failure this module exists to prevent. A raw newline inside one
`data:` line ends that line, so everything after it is dropped -- the event
arrives truncated, with no error anywhere, the first time a model writes a
code block."""
name, back = parse(event("render", payload))
assert name == "render"
assert back == payload
def test_every_line_of_a_multiline_payload_is_its_own_field():
frame = event("steps", "a\nb\nc")
assert frame.count("data: ") == 3
assert "data: a\ndata: b\ndata: c\n" in frame
def test_an_empty_payload_is_still_a_frame():
"""Several frames must be able to blank themselves -- `metrics`, `status`,
`ask`, `reasoning`, `think` and `render` are sent on every version bump
*including* empty, because each has to be able to clear. An approval card
that survived being answered would be a button you could press twice."""
name, back = parse(event("ask", ""))
assert name == "ask"
assert back == ""
assert event("ask", "").endswith("\n\n")
def test_a_payload_that_looks_like_a_field_is_not_read_as_one():
"""Model output is untrusted, and `event: done` inside a reply must stay
text. It does because every line is prefixed -- but the reason is worth
pinning, since the day it is not, a model can end its own stream."""
hostile = "event: done\ndata: {}"
name, back = parse(event("render", hostile))
assert name == "render", "the payload took over the event name"
assert back == hostile
def test_the_keepalive_is_a_comment_and_not_an_event():
"""It exists to stop a proxy killing an idle stream while a model thinks.
A comment line does that without dispatching anything; an event would reach
the page and be swapped into it."""
assert KEEPALIVE.startswith(":")
assert KEEPALIVE.endswith("\n\n")
assert parse(KEEPALIVE) == ("", "")