Files
hermes-webui/tests/test_issue2977_use_command.py
T

130 lines
5.8 KiB
Python

from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def read(path):
return (ROOT / path).read_text(encoding="utf-8")
def test_use_entry_in_commands_array():
src = read("static/commands.js")
assert "{name:'use'," in src, "COMMANDS must contain a {name:'use', ...} entry"
def test_use_entry_precedes_stop_entry():
src = read("static/commands.js")
use_pos = src.index("{name:'use',")
stop_pos = src.index("{name:'stop',")
assert use_pos < stop_pos, "/use must be registered before /stop in COMMANDS"
def test_cmdUse_function_defined():
src = read("static/commands.js")
assert "async function cmdUse(args)" in src, "cmdUse function must be defined"
def test_forced_skill_directive_declared():
src = read("static/commands.js")
assert "let _forcedSkillDirectivePending=null;" in src, "_forcedSkillDirectivePending must be declared at module scope"
def test_forced_skill_directive_set_in_cmdUse():
src = read("static/commands.js")
assert "pending.promise = new Promise" in src, "cmdUse must create a pending Promise"
assert "_forcedSkillDirectivePending = pending;" in src, "cmdUse must publish the pending directive before awaiting"
assert "resolve({name:match.name,directive,content:skillContent});" in src, \
"cmdUse must resolve the pending payload with skill name, directive, and fetched content"
def test_use_entry_has_noEcho():
src = read("static/commands.js")
# Extract the /use entry line and check noEcho:true is present
idx = src.index("{name:'use',")
line_end = src.index("}", idx)
entry = src[idx:line_end + 1]
assert "noEcho:true" in entry, "/use entry must have noEcho:true"
def test_use_entry_has_subArgs_skills():
src = read("static/commands.js")
idx = src.index("{name:'use',")
line_end = src.index("}", idx)
entry = src[idx:line_end + 1]
assert "subArgs:'skills'" in entry, "/use entry must have subArgs:'skills' for autocomplete"
def test_directive_consumed_at_injection_site():
"""_forcedSkillDirectivePending is cleared at the consume site, not in finally."""
src = read("static/messages.js")
finally_part = src.split("finally")[1] if "finally" in src else ""
assert "_forcedSkillDirectivePending = null;" not in finally_part, \
"_forcedSkillDirectivePending must NOT be cleared in the finally block"
assert "const _directivePayload = await _pending.promise;" in src, \
"consume site must await the pending promise"
assert "_forcedSkillDirectivePending = null;" in src, \
"_forcedSkillDirectivePending must be cleared somewhere in messages.js"
def test_directive_injection_before_empty_guard():
src = read("static/messages.js")
inject_pos = src.index("_forcedSkillDirectivePending")
guard_pos = src.index("if(!msgText){setComposerStatus('Nothing to send');return;}")
assert inject_pos < guard_pos, "directive injection must appear before the if(!msgText) guard"
def test_directive_text_uses_match_name():
src = read("static/commands.js")
assert "match.name" in src, "directive must use match.name (canonical casing), not raw user input"
assert "[USER OVERRIDE] You MUST follow the skill '" in src, "directive text must match the specified format"
assert "content provided below" in src, "directive must reference the injected skill content"
def test_use_fetches_canonical_skill_content():
src = read("static/commands.js")
assert "api(`/api/skills/content?name=${encodeURIComponent(match.name)}`)" in src, \
"cmdUse must fetch the canonical skill content after resolving the canonical skill name"
assert "typeof detail.content==='string' ? detail.content.trim() : ''" in src, \
"cmdUse must reject missing or non-string skill content"
def test_pending_promise_set_synchronously():
"""_forcedSkillDirectivePending must be set before the first await in cmdUse."""
src = read("static/commands.js")
fn_start = src.index("async function cmdUse(args)")
fn_body = src[fn_start:]
pending_pos = fn_body.index("_forcedSkillDirectivePending = pending;")
first_await = fn_body.index("await ")
assert pending_pos < first_await, \
"_forcedSkillDirectivePending must be set before the first await to close the race window"
def test_directive_survives_local_slash_commands():
"""The consume block must appear after the slash-command early-return, not before."""
src = read("static/messages.js")
early_return = src.index("autoResize();hideCmdDropdown();return;")
consume = src.index("_forcedSkillDirectivePending")
assert early_return < consume, \
"slash-command early-return must precede the directive consume block"
def test_directive_pending_captures_session_id():
src = read("static/commands.js")
assert "const pending = {sessionId:S.session&&S.session.session_id||null,promise:null};" in src, \
"cmdUse must capture the session where /use was issued"
assert "const isCurrentSession = () => !pending.sessionId || (S.session&&S.session.session_id)===pending.sessionId;" in src, \
"async /use completion must avoid writing status messages into a different session"
def test_directive_only_consumed_by_matching_session():
src = read("static/messages.js")
assert "const _pending=_forcedSkillDirectivePending;" in src, \
"send() must snapshot the pending directive before awaiting it"
assert "if(!_pending.sessionId||_pending.sessionId===activeSid){" in src, \
"send() must only consume /use directives issued for the active session"
assert "if(_forcedSkillDirectivePending===_pending)_forcedSkillDirectivePending = null;" in src, \
"send() must not clear a newer pending directive created while awaiting"
assert "[FORCED SKILL CONTEXT: ${_forcedSkillName}]" in src, \
"send() must prepend deterministic forced-skill content before the user message"