fix: route case-insensitive worker messages
Some checks failed
tamq-ci / test (push) Failing after 5s
Some checks failed
tamq-ci / test (push) Failing after 5s
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a03397-4d51-7fd1-8ff2-946eb22ea2bc
This commit is contained in:
parent
fbb56f5981
commit
cd73b954e9
14 changed files with 183 additions and 37 deletions
|
|
@ -21,6 +21,23 @@ def test_broker_accepts_worker_output_with_distinct_provenance(tmp_path, monkeyp
|
|||
assert store.list()[0]["provenance"] == "worker_output"
|
||||
|
||||
|
||||
def test_broker_accepts_case_insensitive_framed_worker_output(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr("tamq.broker.validate_targets", lambda targets: None)
|
||||
store = Store(tmp_path / "queue.sqlite3")
|
||||
broker = InputBroker(store, BrokerIdentity("ep", "net-kingdom"))
|
||||
assert broker.inspect_worker_line("• tO:railiance-platform: hello") is not None
|
||||
row = store.list()[0]
|
||||
assert row["body"] == "hello"
|
||||
assert row["provenance"] == "worker_output"
|
||||
|
||||
|
||||
def test_case_insensitive_delivery_envelopes_are_not_rescanned(tmp_path):
|
||||
store = Store(tmp_path / "queue.sqlite3")
|
||||
broker = InputBroker(store, BrokerIdentity("ep", "net-kingdom"))
|
||||
assert broker.inspect_worker_line("fRoM:railiance-platform: hello") is None
|
||||
assert store.line_state("ep", "net-kingdom")["output"] == 0
|
||||
|
||||
|
||||
def test_worker_cmd_is_inert_but_operator_cmd_changes_mode(tmp_path):
|
||||
store = Store(tmp_path / "queue.sqlite3")
|
||||
store.register_endpoint("ep", 9, "tamq", ["repo"], "output")
|
||||
|
|
@ -30,11 +47,24 @@ def test_worker_cmd_is_inert_but_operator_cmd_changes_mode(tmp_path):
|
|||
)
|
||||
broker.inspect_worker_line("Cmd: mode=trigger")
|
||||
assert store.endpoint("ep")["delivery_mode"] == "output"
|
||||
broker.inspect_operator_line("Cmd: mode=trigger")
|
||||
broker.inspect_operator_line("cMD: MODE=TRIGGER")
|
||||
assert store.endpoint("ep")["delivery_mode"] == "trigger"
|
||||
assert notices[-1] == "From:tamq: Mode set to trigger."
|
||||
|
||||
|
||||
def test_operator_command_names_are_case_insensitive(tmp_path):
|
||||
store = Store(tmp_path / "queue.sqlite3")
|
||||
store.register_endpoint("ep", 9, "tamq", ["repo"], "output")
|
||||
notices = []
|
||||
broker = InputBroker(
|
||||
store, BrokerIdentity("ep", "repo"), notify=notices.append
|
||||
)
|
||||
broker.inspect_operator_line("CMD: MAXMSG=12")
|
||||
assert store.line_state("ep", "repo")["maxmsg"] == 12
|
||||
broker.inspect_operator_line("cmd: RESET-LIMITS")
|
||||
assert notices[-1] == "From:tamq: Limits reset for this terminal."
|
||||
|
||||
|
||||
def test_limits_block_at_equality_and_reset_current_window(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr("tamq.broker.validate_targets", lambda targets: None)
|
||||
store = Store(tmp_path / "queue.sqlite3")
|
||||
|
|
|
|||
|
|
@ -93,6 +93,17 @@ def test_output_observer_fails_closed_on_recent_operator_echo():
|
|||
assert lines == ["To:target: worker"]
|
||||
|
||||
|
||||
def test_output_observer_preserves_full_screen_program_gutters():
|
||||
lines = []
|
||||
observer = TerminalOutputObserver(lines.append)
|
||||
observer.feed(
|
||||
b"\x1b[4;1H\xe2\x80\xba To:target: operator echo"
|
||||
b"\x1b[5;1H\xe2\x80\xa2 tO:target: worker reply"
|
||||
b"\x1b[6;1H"
|
||||
)
|
||||
assert lines == ["› To:target: operator echo", "• tO:target: worker reply"]
|
||||
|
||||
|
||||
def test_copy_winsize_preserves_rows_columns_and_pixels():
|
||||
source_master, source_slave = pty.openpty()
|
||||
target_master, target_slave = pty.openpty()
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ def test_real_pty_attributes_worker_output_and_suppresses_operator_echo(
|
|||
"print('READY', flush=True)\n"
|
||||
"for line in sys.stdin:\n"
|
||||
" print(line.rstrip('\\r\\n'), flush=True)\n"
|
||||
" print('To:target: worker reply', flush=True)\n",
|
||||
" print('\\x1b[5;1H• tO:target: worker reply\\x1b[6;1H', flush=True)\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
bin_dir = tmp_path / "bin"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
import pytest
|
||||
|
||||
from tamq.routing import parse_address_line, parse_command_line
|
||||
from tamq.routing import (
|
||||
is_delivery_line,
|
||||
parse_address_line,
|
||||
parse_command_line,
|
||||
parse_worker_address_line,
|
||||
)
|
||||
|
||||
|
||||
def test_direct_address_uses_readable_case_sensitive_grammar():
|
||||
routed = parse_address_line("To:railiance-platform: do something!")
|
||||
@pytest.mark.parametrize("keyword", ["to", "To", "TO", "tO"])
|
||||
def test_direct_address_keyword_is_case_insensitive(keyword):
|
||||
routed = parse_address_line(
|
||||
f"{keyword}:railiance-platform: do something!"
|
||||
)
|
||||
assert routed.body == "do something!"
|
||||
assert routed.target_repo == "railiance-platform"
|
||||
|
||||
|
|
@ -14,7 +22,6 @@ def test_direct_address_uses_readable_case_sensitive_grammar():
|
|||
[
|
||||
"@repo: removed",
|
||||
"#repo: removed",
|
||||
"to:repo: wrong case",
|
||||
"hello To:repo: not at start",
|
||||
"To:repo:",
|
||||
"From:repo: inbound envelope",
|
||||
|
|
@ -24,7 +31,23 @@ def test_non_protocol_and_legacy_input_is_not_routed(line):
|
|||
assert parse_address_line(line) is None
|
||||
|
||||
|
||||
def test_operator_command_grammar():
|
||||
assert parse_command_line("Cmd: mode=trigger") == "mode=trigger"
|
||||
assert parse_command_line("Cmd: reset-limits") == "reset-limits"
|
||||
assert parse_command_line("cmd: mode=trigger") is None
|
||||
@pytest.mark.parametrize("keyword", ["cmd", "Cmd", "CMD", "cMd"])
|
||||
def test_operator_command_keyword_is_case_insensitive(keyword):
|
||||
assert parse_command_line(f"{keyword}: mode=trigger") == "mode=trigger"
|
||||
assert parse_command_line(f"{keyword}: reset-limits") == "reset-limits"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("keyword", ["from", "From", "FROM", "fRoM"])
|
||||
def test_delivery_keyword_is_case_insensitive(keyword):
|
||||
assert is_delivery_line(f"{keyword}:repo: inbound envelope") is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize("gutter", ["• ", " ● ", "▪ ", "◆ "])
|
||||
def test_worker_address_accepts_terminal_output_gutter(gutter):
|
||||
routed = parse_worker_address_line(f"{gutter}tO:target: worker reply")
|
||||
assert routed == parse_address_line("To:target: worker reply")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("gutter", ["› ", "> ", "$ ", "# ", "* ", "- "])
|
||||
def test_worker_address_rejects_operator_and_markup_gutters(gutter):
|
||||
assert parse_worker_address_line(f"{gutter}To:target: operator echo") is None
|
||||
|
|
|
|||
|
|
@ -161,9 +161,11 @@ def test_neutral_plan_starts_shell_without_sending_keystrokes(tmp_path, monkeypa
|
|||
assert "TAMQ_REPO=b" in new_window
|
||||
assert f"PATH={command_dir}{os.pathsep}" in " ".join(new_session)
|
||||
assert f"PATH={command_dir}{os.pathsep}" in " ".join(new_window)
|
||||
assert (command_dir / "To:a:").is_file()
|
||||
assert (command_dir / "To:b:").is_file()
|
||||
assert (command_dir / "Cmd:").is_file()
|
||||
for keyword in ("To", "to", "TO", "tO"):
|
||||
assert (command_dir / f"{keyword}:a:").is_file()
|
||||
assert (command_dir / f"{keyword}:b:").is_file()
|
||||
for keyword in ("Cmd", "cmd", "CMD", "cMd"):
|
||||
assert (command_dir / f"{keyword}:").is_file()
|
||||
assert not any(path.name.startswith("@") for path in command_dir.iterdir())
|
||||
|
||||
|
||||
|
|
@ -174,7 +176,16 @@ def test_protocol_commands_absorb_forwarded_shell_lines(tmp_path):
|
|||
)
|
||||
manager._install_address_commands(["audit-core"])
|
||||
|
||||
for name in ("To:audit-core:", "Cmd:"):
|
||||
for name in (
|
||||
"To:audit-core:",
|
||||
"to:audit-core:",
|
||||
"TO:audit-core:",
|
||||
"tO:audit-core:",
|
||||
"Cmd:",
|
||||
"cmd:",
|
||||
"CMD:",
|
||||
"cMd:",
|
||||
):
|
||||
result = subprocess.run(
|
||||
[str(command_dir / name), "Some message!", "$value"],
|
||||
text=True,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue