Files
wehub-resource-sync b4fbd6fe9f
Deploy Site / deploy-vercel (push) Has been skipped
Deploy Site / deploy-docs (push) Has been skipped
Build Skills Index / build-index (push) Has been skipped
CI / Deny unrelated histories (push) Has been skipped
CI / Detect affected areas (push) Successful in 27m35s
CI / OSV scan (push) Failing after 4s
CI / Build&Test Docker image (push) Successful in 9s
CI / Supply-chain scan (push) Has been skipped
CI / Lint Docker scripts (push) Failing after 5m13s
CI / Check contributors (push) Failing after 12m8s
CI / Docs Site (push) Failing after 12m8s
CI / TypeScript (push) Failing after 12m8s
CI / Python lints (push) Failing after 12m9s
CI / Python tests (push) Failing after 12m9s
CI / Check uv.lock (push) Failing after 23m22s
CI / CI timing report (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
CI / All required checks pass (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:56:03 +08:00

180 lines
4.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Unit tests for hermes_cli.session_recap."""
from __future__ import annotations
import json
from hermes_cli.session_recap import build_recap
def _user(text):
return {"role": "user", "content": text}
def _assistant(text=None, tool_calls=None):
msg = {"role": "assistant", "content": text}
if tool_calls:
msg["tool_calls"] = tool_calls
return msg
def _tool_call(name, args):
return {
"id": f"call_{name}",
"type": "function",
"function": {"name": name, "arguments": json.dumps(args)},
}
def _tool_result(content="ok"):
return {"role": "tool", "content": content}
def test_empty_history():
out = build_recap([])
assert "Session recap" in out
assert "nothing to recap" in out
def test_header_shows_title_when_provided():
out = build_recap([_user("hello")], session_title="Refactor the adapter")
assert "Refactor the adapter" in out.splitlines()[0]
def test_header_shows_short_id_when_no_title():
out = build_recap([_user("hello")], session_id="abcdef1234567890")
assert "abcdef12" in out.splitlines()[0]
def test_counts_recent_turns():
msgs = [
_user("one"),
_assistant("first reply"),
_user("two"),
_assistant("second reply"),
]
out = build_recap(msgs)
assert "2 user turn" in out
assert "assistant repl" in out
def test_last_ask_and_reply_are_surfaced():
msgs = [
_user("old question"),
_assistant("old answer"),
_user("summarise the docs"),
_assistant("here is the summary of the docs you asked for"),
]
out = build_recap(msgs)
assert "summarise the docs" in out
assert "summary of the docs" in out
def test_tool_counts_and_files():
msgs = [
_user("edit the readme and run tests"),
_assistant(
tool_calls=[
_tool_call("read_file", {"path": "README.md"}),
_tool_call("patch", {"path": "README.md"}),
]
),
_tool_result(),
_tool_result(),
_assistant(
tool_calls=[
_tool_call("terminal", {"command": "pytest"}),
]
),
_tool_result("tests ok"),
_assistant("All green."),
]
out = build_recap(msgs)
assert "patch×1" in out
assert "terminal×1" in out
assert "read_file×1" in out
# README.md should appear (may include cwd-relative prefix stripping).
assert "README.md" in out
def test_tool_preview_length_truncates_long_user_prompt():
long = "x " * 500
out = build_recap([_user(long)])
ask_line = [l for l in out.splitlines() if "Last ask" in l][0]
assert len(ask_line) < 300 # truncated with ellipsis
assert "…" in ask_line
def test_respects_recent_window():
# 30 turns of user+assistant; only the most recent 20 should be summarised.
msgs = []
for i in range(30):
msgs.append(_user(f"question {i}"))
msgs.append(_assistant(f"answer {i}"))
out = build_recap(msgs)
# We scoped to the 20-turn window but show "of 30/30 total".
assert "of 30/30 total" in out
def test_multimodal_content_blocks_flattened():
msgs = [
{
"role": "user",
"content": [
{"type": "text", "text": "check this file"},
{"type": "image_url", "image_url": {"url": "..."}},
],
},
_assistant("Looked at your image."),
]
out = build_recap(msgs)
assert "check this file" in out
assert "Looked at your image" in out
def test_handles_arguments_as_dict_not_string():
# Some providers return arguments already as a dict.
msgs = [
_user("go"),
{
"role": "assistant",
"content": None,
"tool_calls": [
{
"type": "function",
"function": {
"name": "patch",
"arguments": {"path": "foo.py"},
},
}
],
},
]
out = build_recap(msgs)
assert "patch×1" in out
assert "foo.py" in out
def test_no_assistant_activity_hint():
out = build_recap([_user("just sent my first message")])
assert "no assistant activity" in out or "Last ask" in out
def test_tool_message_count_reported():
msgs = [
_user("go"),
_assistant(tool_calls=[_tool_call("read_file", {"path": "a"})]),
_tool_result(),
_tool_result(),
_assistant("done"),
]
out = build_recap(msgs)
assert "2 tool result" in out
def test_ignores_non_mapping_entries_gracefully():
msgs = [None, "stray", _user("hi"), _assistant("hello")]
# Should not raise.
out = build_recap(msgs)
assert "Session recap" in out