import json import shlex from tamq.cli import main from tamq.store import Store async def service_is_down(): return False def test_manual_send_inbox_and_ack_use_window_repository_identity(tmp_path, monkeypatch, capsys): monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state")) monkeypatch.setenv("TAMQ_REPO", "flex-auth") monkeypatch.setattr("tamq.cli.ping", service_is_down) monkeypatch.setattr("tamq.cli.validate_targets", lambda repos: None) assert main(["send", "@audit-core:", "review", "this"]) == 0 message_id = capsys.readouterr().out.strip() assert main(["inbox", "--repo", "audit-core", "--json"]) == 0 row = json.loads(capsys.readouterr().out) assert row["message_id"] == message_id assert row["sender_repo"] == "flex-auth" assert row["target_repo"] == "audit-core" assert row["body"] == "review this" assert row["state"] == "pending" assert main(["ack", message_id]) == 0 capsys.readouterr() assert main(["inbox", "--repo", "audit-core"]) == 0 assert capsys.readouterr().out == "" def test_bare_reply_targets_latest_inbound_sender(tmp_path, monkeypatch, capsys): monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state")) monkeypatch.setenv("TAMQ_REPO", "audit-core") monkeypatch.setattr("tamq.cli.ping", service_is_down) monkeypatch.setattr("tamq.cli.validate_targets", lambda repos: None) store = Store(tmp_path / "state" / "tamq.sqlite3") store.add("flex-auth", "audit-core", "older") latest = store.add("railiance-platform", "audit-core", "latest") store.acknowledge(latest) store.add("audit-core", "audit-core", "self note") store.close() assert main(["reply", "--", "--looks-like-an-option", "thanks"]) == 0 message_id = capsys.readouterr().out.strip() store = Store(tmp_path / "state" / "tamq.sqlite3") row = next(row for row in store.list() if row["message_id"] == message_id) assert row["sender_repo"] == "audit-core" assert row["target_repo"] == "railiance-platform" assert row["body"] == "--looks-like-an-option thanks" store.close() def test_interactive_reply_shows_latest_and_cycles_session_recipients(tmp_path, monkeypatch, capsys): monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state")) monkeypatch.setenv("TAMQ_REPO", "audit-core") monkeypatch.setenv( "TAMQ_RECIPIENTS", '["audit-core","railiance-platform","flex-auth"]' ) monkeypatch.setattr("tamq.cli.ping", service_is_down) monkeypatch.setattr("tamq.cli.validate_targets", lambda repos: None) store = Store(tmp_path / "state" / "tamq.sqlite3") store.add("flex-auth", "audit-core", "latest inbound") store.close() seen = [] def compose(recipients): seen.append(recipients) return "railiance-platform", "What's up?" monkeypatch.setattr("tamq.cli.compose_message", compose) assert main(["reply"]) == 0 message_id = capsys.readouterr().out.strip() assert seen == [("flex-auth", "railiance-platform")] store = Store(tmp_path / "state" / "tamq.sqlite3") row = next(row for row in store.list() if row["message_id"] == message_id) assert (row["sender_repo"], row["target_repo"], row["body"]) == ( "audit-core", "railiance-platform", "What's up?" ) store.close() def test_interactive_reply_uses_first_peer_without_history(tmp_path, monkeypatch, capsys): monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state")) monkeypatch.setenv("TAMQ_REPO", "audit-core") monkeypatch.setenv("TAMQ_RECIPIENTS", '["audit-core","flex-auth"]') monkeypatch.setattr("tamq.cli.ping", service_is_down) monkeypatch.setattr("tamq.cli.validate_targets", lambda repos: None) monkeypatch.setattr( "tamq.cli.compose_message", lambda recipients: (recipients[0], "hello") ) assert main(["reply"]) == 0 message_id = capsys.readouterr().out.strip() store = Store(tmp_path / "state" / "tamq.sqlite3") row = next(row for row in store.list() if row["message_id"] == message_id) assert row["target_repo"] == "flex-auth" store.close() def test_interactive_reply_cancellation_queues_nothing(tmp_path, monkeypatch): monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state")) monkeypatch.setenv("TAMQ_REPO", "audit-core") monkeypatch.setenv("TAMQ_RECIPIENTS", '["audit-core","flex-auth"]') monkeypatch.setattr("tamq.cli.compose_message", lambda recipients: None) assert main(["reply"]) == 130 store = Store(tmp_path / "state" / "tamq.sqlite3") assert store.list() == [] store.close() def test_bare_reply_requires_managed_identity_and_prior_sender(tmp_path, monkeypatch, capsys): monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state")) monkeypatch.setattr("tamq.cli.ping", service_is_down) monkeypatch.setattr("tamq.cli.validate_targets", lambda repos: None) monkeypatch.delenv("TAMQ_REPO", raising=False) assert main(["reply", "hello"]) == 2 assert "managed window" in capsys.readouterr().err monkeypatch.setenv("TAMQ_REPO", "audit-core") assert main(["reply", "hello"]) == 2 assert "no counterparty" in capsys.readouterr().err def test_inbox_requires_repository_outside_managed_window(tmp_path, monkeypatch, capsys): monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state")) monkeypatch.delenv("TAMQ_REPO", raising=False) assert main(["inbox"]) == 2 assert "requires --repo" in capsys.readouterr().err def test_human_inbox_is_comment_safe_on_every_line(tmp_path, monkeypatch, capsys): monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state")) monkeypatch.setattr("tamq.cli.validate_targets", lambda repos: None) store = Store(tmp_path / "state" / "tamq.sqlite3") message_id = store.add("flex-auth", "audit-core", "first\nsecond\x1b[31m") store.close() assert main(["inbox", "--repo", "audit-core"]) == 0 assert capsys.readouterr().out == ( f"#flex-auth: first\n# second\\x1b[31m [{message_id}]\n" ) def test_inbox_filter_logs_and_acknowledges_successful_messages(tmp_path, monkeypatch, capsys): monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state")) monkeypatch.setattr("tamq.cli.validate_targets", lambda repos: None) store = Store(tmp_path / "state" / "tamq.sqlite3") first_id = store.add("flex-auth", "audit-core", "review 'quoted' $value") second_id = store.add("railiance-platform", "audit-core", "second") store.close() log = tmp_path / "messages.log" assert main([ "inbox", "--repo", "audit-core", "--filter", f"cat >> {shlex.quote(str(log))}", ]) == 0 assert log.read_text(encoding="utf-8") == ( f"#flex-auth: review 'quoted' $value [{first_id}]\n" f"#railiance-platform: second [{second_id}]\n" ) assert main(["inbox", "--repo", "audit-core"]) == 0 assert capsys.readouterr().out == "" def test_failed_inbox_filter_leaves_current_and_later_messages_pending(tmp_path, monkeypatch, capsys): monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state")) monkeypatch.setattr("tamq.cli.validate_targets", lambda repos: None) store = Store(tmp_path / "state" / "tamq.sqlite3") first_id = store.add("a", "audit-core", "first") second_id = store.add("b", "audit-core", "second") store.close() assert main(["inbox", "--repo", "audit-core", "--filter", "false"]) == 1 assert first_id in capsys.readouterr().err assert main(["inbox", "--repo", "audit-core", "--json"]) == 0 rows = [json.loads(line) for line in capsys.readouterr().out.splitlines()] assert [row["message_id"] for row in rows] == [first_id, second_id] assert all(row["state"] == "pending" for row in rows) def test_inbox_filter_rejects_non_pending_and_json_modes(tmp_path, monkeypatch, capsys): monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state")) monkeypatch.setattr("tamq.cli.validate_targets", lambda repos: None) assert main(["inbox", "--repo", "audit-core", "--all", "--filter", "cat"]) == 2 assert "cannot be combined" in capsys.readouterr().err assert main(["inbox", "--repo", "audit-core", "--json", "--filter", "cat"]) == 2 assert "cannot be combined" in capsys.readouterr().err assert main(["inbox", "--repo", "audit-core", "--filter", " "]) == 2 assert "must not be empty" in capsys.readouterr().err