Six things that looked like they worked
None of these fails loudly and two of them correct themselves if you reload, which is why five were found by reading rather than by anybody reporting them. The reply that lost its author is the one worth knowing about: the frame that replaces a bubble when a reply lands was looking the models up as nobody, and "no user" answers "no models" rather than "all models" -- so every finished reply swapped the model's avatar for the plain mark and put the instance's name where the model's should be, until the next page load put it back. Beside it: a concurrency quota enforced on two of the six paths that start a reply, including neither of the two most used; a custom theme whose success and warning colours moved the text and left the background behind; a phone shell sized to one viewport inside a document sized to another, which is the reported scroll past the bottom of Settings; a whole conversation's Markdown rendered on every page load and read by nothing; a skip guard inert since it was written; and an endpoint nothing has ever called. The scroll fix folded five near-identical scroller rules into one, which is also where the containment they were all missing now lives. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,14 +22,19 @@ from lembas.services.agent.base import ExecRequest, ExecResult, clean_output
|
||||
from lembas.services.agent.session import AgentContext
|
||||
from lembas.services.agent.tools import _run_shell
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
shutil.which("setsid") is None or shutil.which("base64") is None,
|
||||
reason="needs setsid and base64 (Linux)",
|
||||
)
|
||||
|
||||
|
||||
# Stands up something real -- see the `slow` marker in pyproject.toml.
|
||||
pytestmark = pytest.mark.slow
|
||||
# A list, because there are two of them and `pytestmark = ...` twice is not two
|
||||
# marks -- the second binding replaces the first, silently. It did, for the
|
||||
# whole life of this file: the guard below was written, read as present, and
|
||||
# never once applied, so a host without `setsid` got a module that errored
|
||||
# instead of the skip somebody had taken the trouble to write.
|
||||
pytestmark = [
|
||||
pytest.mark.skipif(
|
||||
shutil.which("setsid") is None or shutil.which("base64") is None,
|
||||
reason="needs setsid and base64 (Linux)",
|
||||
),
|
||||
# Stands up something real -- see the `slow` marker in pyproject.toml.
|
||||
pytest.mark.slow,
|
||||
]
|
||||
|
||||
class LocalExecutor:
|
||||
"""`SshExecutor.run`'s contract, run against the local shell.
|
||||
|
||||
@@ -1167,3 +1167,68 @@ def test_the_think_frame_lands_beside_the_reasoning_body_not_around_it():
|
||||
# Neither element may open a tag that the other closes: siblings, not nested.
|
||||
assert "</span>" in between or "</div>" in between
|
||||
assert between.count("<div") <= 1
|
||||
|
||||
|
||||
# --- Who a finished reply says it came from ----------------------------------
|
||||
def test_a_finished_bubble_names_the_model_that_wrote_it(db, client, registered, make_chat):
|
||||
"""The `done` frame and the tail route render the bubble from scratch, and
|
||||
both looked the models up as *nobody* -- which `models_visible_to` answers
|
||||
with an empty list, not with everything. So a reply was attributed correctly
|
||||
for as long as it was streaming and lost its avatar and its author line at
|
||||
the instant it finished, then corrected itself on the next page load.
|
||||
|
||||
Asserted on the rendered HTML rather than on the argument: passing `owner`
|
||||
is what the old code looked like it was doing, and an assertion on the call
|
||||
would have been green throughout.
|
||||
"""
|
||||
from lembas.api import chats as chats_api
|
||||
from lembas.db.models import Chat, Connection, Message, Model, User
|
||||
|
||||
connection = Connection(name="c", base_url="http://127.0.0.1:1", api_key_encrypted="")
|
||||
db.add(connection)
|
||||
db.commit()
|
||||
db.add(Model(connection_id=connection.id, model_id="mithril-7b", display_name="Mithril 7B"))
|
||||
db.commit()
|
||||
|
||||
chat_id = make_chat(model_id="mithril-7b")
|
||||
chat = db.get(Chat, chat_id)
|
||||
owner = db.get(User, chat.user_id)
|
||||
message = Message(
|
||||
chat_id=chat.id, role="assistant", content="Spoken.",
|
||||
complete=True, model_id="mithril-7b",
|
||||
)
|
||||
db.add(message)
|
||||
db.commit()
|
||||
|
||||
html = chats_api._render_bubble(db, chat, owner, message)
|
||||
assert "Mithril 7B" in html
|
||||
|
||||
|
||||
def test_the_reply_limit_covers_every_way_of_starting_one(db):
|
||||
"""It was enforced in `_send` alone, so an account at its ceiling reached it
|
||||
by sending into an existing chat and walked past it by pressing New chat --
|
||||
and by editing, by sending a queued message, and by regenerating.
|
||||
|
||||
Reading the source is the honest test here: driving four routes to the point
|
||||
of refusal needs four live generations, which is a fixture that would tell
|
||||
you more about the fixture than about the guard.
|
||||
"""
|
||||
import inspect
|
||||
|
||||
from lembas.api import chats as chats_api
|
||||
|
||||
source = inspect.getsource(chats_api)
|
||||
for route in ("start_chat", "edit_message", "send_queued_now", "regenerate", "_send"):
|
||||
body = source.split(f"def {route}(", 1)[1].split("\n@router", 1)[0]
|
||||
assert "_refuse_extra_reply" in body, route
|
||||
|
||||
|
||||
def test_a_new_chat_is_not_written_before_the_limit_is_checked(db):
|
||||
"""A refusal that has already created the row leaves an empty chat in the
|
||||
sidebar as the visible result of being told no."""
|
||||
import inspect
|
||||
|
||||
from lembas.api import chats as chats_api
|
||||
|
||||
body = inspect.getsource(chats_api.start_chat)
|
||||
assert body.index("_refuse_extra_reply") < body.index("_new_chat(")
|
||||
|
||||
+7
-2
@@ -163,10 +163,15 @@ def test_the_tab_reset_finds_the_container_that_actually_scrolls():
|
||||
either alone leaves the bug.
|
||||
"""
|
||||
admin = (ROOT / "web/static/css/admin.css").read_text(encoding="utf-8")
|
||||
app = (ROOT / "web/static/css/app.css").read_text(encoding="utf-8")
|
||||
|
||||
# The scroller rule names where the tabs must be, not just the class.
|
||||
assert ".main > .tabs > .tabs__body" in admin
|
||||
# The scroller rule names where the tabs must be, not just the class. Which
|
||||
# stylesheet it is written in is not the point and is not asserted -- the
|
||||
# four declarations that make something a scroller now live once, in
|
||||
# app.css, and this selector is listed there with the rest.
|
||||
assert ".main > .tabs > .tabs__body" in admin + app
|
||||
assert "\n.tabs__body {" not in admin
|
||||
assert "\n.tabs__body {" not in app
|
||||
|
||||
assert "overflowY" in SOURCE
|
||||
assert "scrollHeight > " in SOURCE
|
||||
|
||||
Reference in New Issue
Block a user