Skip to content

Commit 9563dc7

Browse files
test(jsonl): add parser coverage; harden message/usage handling
Add tests/test_jsonl_parser.py for tool-result dispatch, helpers, parse_session, quick_session_info, and malformed entries (null/non-dict message and usage). Introduce _entry_message() so only dict messages are read; keep usage as a dict before token accounting. Apply the same normalization in quick_session_info. Use full-string == assertions in TestStripSystemTags; drop unused pytest import; add regression for string-shaped assistant message.
1 parent 3f6db99 commit 9563dc7

2 files changed

Lines changed: 26 additions & 10 deletions

File tree

tests/test_jsonl_parser.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
import sys
66
import tempfile
77

8-
import pytest
9-
108
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
119

1210
from utils.jsonl_parser import ( # noqa: E402
@@ -338,8 +336,7 @@ def test_sidechain_only_returns_untitled(self):
338336
class TestStripSystemTags:
339337
def test_system_reminder_removed(self):
340338
t = "<system-reminder>in</system-reminder>keep"
341-
assert "system-reminder" not in _strip_system_tags(t)
342-
assert "keep" in _strip_system_tags(t)
339+
assert _strip_system_tags(t) == "keep"
343340

344341
def test_ide_opened_file_removed(self):
345342
t = "<ide_opened_file>x</ide_opened_file>y"
@@ -351,9 +348,7 @@ def test_user_prompt_submit_hook_removed(self):
351348

352349
def test_remaining_known_opening_closing_tags_stripped(self):
353350
t = "</ide_selection><command-name>foo</command-name>bar"
354-
out = _strip_system_tags(t)
355-
assert "ide_selection" not in out
356-
assert "bar" in out
351+
assert _strip_system_tags(t) == "foobar"
357352

358353
def test_clean_text_unchanged(self):
359354
assert _strip_system_tags("hello world") == "hello world"
@@ -745,6 +740,22 @@ def test_null_message_assistant_no_crash(self):
745740
finally:
746741
os.unlink(path)
747742

743+
def test_non_dict_message_assistant_no_crash(self):
744+
path = _write_jsonl([
745+
{
746+
"type": "assistant",
747+
"timestamp": "2026-01-01T00:00:00Z",
748+
"message": "not-a-dict",
749+
},
750+
])
751+
try:
752+
s = parse_session(path)
753+
assert s["metadata"]["total_input_tokens"] == 0
754+
assert len(s["messages"]) == 1
755+
assert s["messages"][0]["role"] == "assistant"
756+
finally:
757+
os.unlink(path)
758+
748759
def test_non_dict_usage_assistant_no_crash(self):
749760
path = _write_jsonl([
750761
{

utils/jsonl_parser.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ def parse_session(filepath: str) -> dict:
125125
}
126126

127127

128+
def _entry_message(entry: dict) -> dict:
129+
m = entry.get("message")
130+
return m if isinstance(m, dict) else {}
131+
132+
128133
def _process_user(entry: dict, messages: list, metadata: dict):
129134
"""Pull out text, tool results, and session-level metadata (cwd, version, etc.)
130135
from a user entry."""
@@ -137,7 +142,7 @@ def _process_user(entry: dict, messages: list, metadata: dict):
137142
if metadata["permission_mode"] is None:
138143
metadata["permission_mode"] = entry.get("permissionMode")
139144

140-
msg = entry.get("message") or {}
145+
msg = _entry_message(entry)
141146
content = msg.get("content", [])
142147
text = _extract_text(content)
143148
images = _extract_images(content)
@@ -170,7 +175,7 @@ def _process_user(entry: dict, messages: list, metadata: dict):
170175
def _process_assistant(entry: dict, messages: list, metadata: dict):
171176
"""Handle assistant responses -- splits content into text, thinking blocks,
172177
and tool_use calls, and accumulates token/model/tool stats."""
173-
msg = entry.get("message") or {}
178+
msg = _entry_message(entry)
174179
model = msg.get("model", "")
175180
if model and model != "<synthetic>":
176181
metadata["models_used"].add(model)
@@ -503,7 +508,7 @@ def quick_session_info(filepath: str) -> dict:
503508
last_ts = ts # keep updating in case file is small
504509

505510
if title is None and entry.get("type") == "user":
506-
msg = entry.get("message") or {}
511+
msg = _entry_message(entry)
507512
text = _extract_text(msg.get("content", []))
508513
if text:
509514
clean = _strip_system_tags(text).strip()

0 commit comments

Comments
 (0)