Files
odysseus/tests/test_live_thinking_chat_integration.py
T

171 lines
7.0 KiB
Python

"""Source-level wiring guards for live-thinking stream lifecycle.
The pure scheduler suite covers timing behavior. These assertions pin the
browser-only integration seams that are impractical to import without the full
application DOM.
"""
from pathlib import Path
_CHAT = (Path(__file__).resolve().parent.parent / "static" / "js" / "chat.js").read_text(
encoding="utf-8"
)
def _between(start: str, end: str) -> str:
return _CHAT.split(start, 1)[1].split(end, 1)[0]
def test_in_thinking_delta_short_circuits_before_cumulative_normalization():
delta_handler = _between(
"let _delta = json.delta;",
"} else if (json.type === 'research_progress')",
)
delta_path = _between(
"// Detect thinking-in-progress:",
"} else if (json.type === 'research_progress')",
)
guard = "if (!_thinkingAnalysisGate.shouldAnalyze(roundText, {"
normalize = "markdownModule.normalizeThinkingMarkup(roundText)"
assert guard in delta_path
assert delta_path.index(guard) < delta_path.index(normalize)
assert "_queueLiveThinking(roundText);" in delta_path
assert "createThinkingAnalysisGate" in _CHAT
projector_append = "_roundDisplayProjector.append(_delta, roundText);"
assert projector_append in delta_handler
assert delta_handler.index(projector_append) < delta_handler.index(guard)
assert "_renderStream({ knownNormal: true, displayText: _roundDisplayProjector.current() });" in delta_path
assert "_replyDisplayProjector.append(_delta, roundReplyText)" in delta_path
def test_short_close_grace_expires_without_another_delta():
assert "function _scheduleThinkingGrace()" in _CHAT
grace = _between(
"function _scheduleThinkingGrace()",
"function _replyAfterClosedThinking",
)
assert "setTimeout(() =>" in grace
assert "_finishLiveThinkingTransition();" in grace
cancel = _between("_cancelLiveThinkingWork = () =>", "function _finalizeLiveThinking")
assert "_cancelThinkingGrace();" in cancel
delta_path = _between(
"// Detect thinking-in-progress:",
"} else if (json.type === 'research_progress')",
)
false_close = _between(
"// Detect false close:",
"if (hasUnclosedThink && !isThinking)",
)
assert "Do NOT require a prior unclosed delta" in false_close
assert "_afterClose &&" in false_close
assert "&& isThinking" not in false_close.split("let _falseCloseDeadline", 1)[1].split("if (isThinking)", 1)[0]
assert "_thinkingRecheckAt = _falseCloseDeadline || 0;" in delta_path
def test_terminal_paths_use_one_authoritative_rich_round_render():
tool_path = _between(
"} else if (json.type === 'tool_start') {",
"} else if (json.type === 'tool_output') {",
)
assert "_endLiveThinkingSection({ rich: false });" in tool_path
assert tool_path.count("_finalizeRoundRender();") == 1
assert "_renderStream();" not in tool_path
agent_path = _between(
"} else if (json.type === 'agent_step') {",
"} else if (json.type === 'budget_exceeded') {",
)
assert "_endLiveThinkingSection({ rich: false });" in agent_path
assert agent_path.count("_finalizeRoundRender();") == 1
assert "if (!roundFinalized)" not in agent_path
catch_path = _between(
"// foreground session's text.\n const _isBgCatch",
"} finally {",
)
assert "if (_isBgCatch)" in catch_path
assert "_cancelLiveThinkingWork();" in catch_path
assert "_catchTerminalView = _finalizeInterruptedView();" in catch_path
assert "_finalizeRoundRender();" not in catch_path
assert "_endThinkingOnTerminalPath({ rich: false });" in catch_path
assert "const _catchViewHolder = _catchTerminalView?.holder || holder;" in catch_path
round_finalizer = _between(
"_finalizeRoundRender = () => {",
"_finalizeInterruptedView = () => {",
)
assert "if (roundFinalized) return roundFinalization;" in round_finalizer
assert round_finalizer.index("processWithThinking") < round_finalizer.rindex("roundFinalized = true;")
assert "lastContentRoundHolder = terminalHolder;" in round_finalizer
interrupted_finalizer = _between(
"_finalizeInterruptedView = () => {",
"function _replyAfterClosedThinking",
)
assert "finalization?.hasContent" in interrupted_finalizer
assert "lastContentRoundHolder || finalization?.holder" in interrupted_finalizer
stop_path = _between(
"// Render whatever was accumulated so far",
"// Reset button state",
)
assert "const _stoppedViewHolder = _terminalView?.holder || currentHolder;" in stop_path
assert "_stoppedViewHolder.querySelector('.body').appendChild(stoppedIndicator);" in stop_path
done_path = _between(
"if (data === '[DONE]') {",
"try {\n const json = JSON.parse(data);",
)
assert "_finalizeLiveThinking(_closedThinkingText(roundText), false);" in done_path
assert "_renderStream();" not in done_path
post_loop = _between(
"if (!_streamSawDone) {",
"// --- Final render (skip if stream was ever backgrounded or currently in background) ---",
)
assert "_cancelLiveThinkingWork();" in post_loop
assert "_renderStream();" not in post_loop
recovery_path = _between(
"function _tryAutoRecover(holder, accumulated, sessionId)",
"function _removeStallBanner()",
)
assert "processWithThinking" not in recovery_path
def test_final_response_skips_duplicate_end_of_stream_render():
final_response_path = _between(
"if (json.type === 'final_response') {",
"if (json.delta) {",
)
assert "terminalFinalResponseRendered = true;" in final_response_path
post_loop = _between(
"// --- Final render (skip if stream was ever backgrounded or currently in background) ---",
"} // end if (!_isBgFinal)",
)
assert "if (!terminalFinalResponseRendered) _renderStream();" in post_loop
assert "const finalDisplay = terminalFinalResponseRendered ? '' : _streamDisplayText" in post_loop
def test_only_rich_completed_turns_reload_saved_canonical_assistant_row():
assert "if (_canonicalTerminalSaved && _savedAssistantMessageId && !_pendingContinue)" not in _CHAT
assert "if (!_pendingContinue)" in _CHAT
assert "fall back to its latest" in _CHAT
assert ": [...history].reverse().find(msg => msg && msg.role === 'assistant');" in _CHAT
assert "const _needsCanonicalTurnRebuild" in _CHAT
assert "lastToolThread" in _CHAT
assert "_streamTurnMarker.remove();" in _CHAT
def test_detach_synchronously_cancels_delayed_view_work():
registration = _between("_activeStreams.set(streamSessionId", "_syncForegroundStreamGlobals();")
assert "cancelViewWork: () => _cancelLiveThinkingWork()" in registration
detach = _between("export function detachCurrentStream", "// _notifyStreamComplete")
cancel = "if (active.cancelViewWork) active.cancelViewWork();"
background = "_backgroundStreams.set(sessionId"
assert cancel in detach
assert detach.index(cancel) < detach.index(background)