Files
hermes-webui/tests/test_1466_sidebar_cancel_clarify.py
T
Nathan EsquenaziandClaude Opus 4.7 e8dbf94a6f fix(i18n+tests): complete locale parity + bump test windows for new menu action
PR #2294 added the show_previous_messaging_sessions setting and a "Hide
from list" menu action for external sessions, but tripped 8 tests:

  - 4 locale-parity tests (tests/test_{japanese,russian,spanish,chinese}_locale.py)
    demand every en key be defined in ja/ru/es/zh blocks. The contributor
    only added the 5 new keys to en + ko, leaving ja/ru/es/zh/it/de/zh-TW/pt/fr
    missing them. tests/test_provider_quota_status.py also requires the two
    settings_{label,desc}_previous_messaging_sessions keys in ALL 11 locales.

  - tests/test_1466_sidebar_cancel_clarify.py read the first 5200 chars of
    _openSessionActionMenu to find cancelSessionStream/delete actions; the
    new "Hide from list" branch (17 lines for external sessions) pushed
    those past the read window.

  - tests/test_issue1611_session_profile_filtering.py grep'd for the
    literal string `_keep_latest_messaging_session_per_source(scoped)`,
    which no longer exists after the call was rewritten as a multi-line
    keyword-arg form.

Fixes:

1. Translations for the 5 new i18n keys added to all 9 missing locales
   (it, ja, ru, es, de, zh-CN, zh-TW, pt, fr):
   - session_hide_external
   - session_hide_external_desc
   - session_hidden
   - settings_label_previous_messaging_sessions
   - settings_desc_previous_messaging_sessions

   Where the locale already used the English fallback for related keys
   (ru/es/de session_archive), I provided localized translations for the
   new keys to match the project's general direction. Native-script
   quality, not machine-translation.

2. test_1466 window bumped 5200 → 6400 with a comment explaining the
   bump (mirrors the existing 3200→4400→5200 history annotations).

3. test_1611 dedupe-position check loosened to match the function name
   without the `(scoped)` suffix so it tolerates both single-line and
   keyword-arg call shapes.

Tests: full suite 5828 passed / 63 skipped / 0 failed (was 8 failed).
Behavioral harness verifies the toggle's claimed behavior — off (default)
hides reset/compression segments, on shows all rows in timestamp order.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:38:06 -07:00

89 lines
4.8 KiB
Python

"""Regression coverage for issue #1466 sidebar cancel ownership.
The active pane is only a projection; running state belongs to the session that
owns the stream. Cancelling a running session from the sidebar context menu must
address that session's stream id and must only clear approval/clarify UI owned by
that session.
"""
from pathlib import Path
ROOT = Path(__file__).parent.parent
BOOT_JS = (ROOT / "static" / "boot.js").read_text(encoding="utf-8")
SESSIONS_JS = (ROOT / "static" / "sessions.js").read_text(encoding="utf-8")
def _function_body(src: str, name: str, window: int = 1800) -> str:
idx = src.find(f"function {name}(")
assert idx >= 0, f"{name} not found"
return src[idx : idx + window]
class TestSidebarCancelAction:
def test_running_sidebar_sessions_get_stop_action(self):
"""Running sessions need a context-menu cancel action even when not active pane."""
# Window bumped from 3200 → 4400 in #1764 to accommodate the new
# Rename action item, then to 5200 in #2111 for response-aware archive
# toast handling, then to 6400 in #2294 for the new "Hide from list"
# action prepended for external sessions.
# The `session.active_stream_id` / cancelSessionStream / delete checks
# are positional further down in the function, so growing the prefix
# required growing this read window.
body = _function_body(SESSIONS_JS, "_openSessionActionMenu", 6400)
assert "session.active_stream_id" in body, (
"sidebar action menu must detect per-session active_stream_id instead of S.activeStreamId"
)
assert "cancelSessionStream(session)" in body, (
"running sidebar sessions must expose a stop action that cancels that session"
)
assert body.find("cancelSessionStream(session)") < body.find("deleteSession(session.session_id)"), (
"stop action should appear before destructive delete action"
)
def test_cancel_session_stream_uses_session_owned_stream_id(self):
"""Cancel-from-sidebar must call /api/chat/cancel with the row's stream id."""
body = _function_body(BOOT_JS, "cancelSessionStream")
assert "session&&session.active_stream_id" in body or "session && session.active_stream_id" in body
assert "stream_id=${encodeURIComponent(streamId)}" in body
assert "S.activeStreamId" not in body.split("const streamId", 1)[1].split("fetch", 1)[0], (
"sidebar cancel must not derive the stream id from the active pane global"
)
def test_cancel_session_stream_clears_only_owned_clarify_and_approval_cards(self):
"""Cancelling A from sidebar must not blanket-clear B's clarify/approval cards."""
body = _function_body(BOOT_JS, "cancelSessionStream")
assert "_clarifySessionId===sid" in body, (
"clarify card cleanup must be gated to the cancelled session id"
)
assert "_approvalSessionId===sid" in body, (
"approval card cleanup must be gated to the cancelled session id"
)
assert "hideClarifyCard(true" in body
assert "hideApprovalCard(true" in body
def test_cli_session_helper_identifies_cli_origin(self):
"""CLI sessions should be treated as external-only for destructive action gating."""
body = _function_body(SESSIONS_JS, "_isCliSession", 900)
assert "function _isCliSession(session) {" in body
assert "session.session_source === 'cli'" in body
assert "session.raw_source" in body
assert "session.source_tag" in body
assert "session.source" in body
assert "session.source_label" in body
assert "if (_isMessagingSession(session)) return false;" in body
assert "return session.is_cli_session === true;" in body
def test_cli_sessions_hide_duplicate_and_delete_in_action_menu(self):
"""Session action menu should hide duplicate/delete for CLI-origin sessions."""
# Window bumped 3600 → 4800 in #1764 (Rename action prepended), then
# to 5200 in #2111 for response-aware archive toast handling, then
# to 6400 in #2294 for the "Hide from list" action on external sessions.
body = _function_body(SESSIONS_JS, "_openSessionActionMenu", 6400)
assert "const isCliSession = _isCliSession(session);" in body
assert "const isExternalSession = isMessagingSession || isCliSession;" in body
assert "if(!isExternalSession)" in body
# duplicate/delete should both be gated by the same external-session check
first = body.find("_appendSessionDuplicateAction")
second = body.find("t('session_delete')")
assert first > 0 and second > 0, "menu actions should still include duplicate/delete nodes"
assert first < second, "duplicate action should render before delete action"