import json from tamq.cli import build_parser, main, normalize_argv import pytest def test_help_and_version(capsys): with pytest.raises(SystemExit) as exc: main(["--version"]) assert exc.value.code == 0 assert "0.1.0" in capsys.readouterr().out assert main([]) == 0 assert "usage:" in capsys.readouterr().out def test_root_help_exposes_repository_shorthand_and_command(capsys): with pytest.raises(SystemExit) as exc: main(["--help"]) assert exc.value.code == 0 output = capsys.readouterr().out assert "tamq [--detach] [--command COMMAND] [--mode MODE]" in output assert "With no --command" in output assert "To:TARGET: MESSAGE" in output assert "Cmd: mode=trigger" in output assert "[--maxmsg N] [--maxin N] [--maxout N]" in output def test_attach_delegates_to_tmux(monkeypatch): calls = [] monkeypatch.delenv("TMUX", raising=False) monkeypatch.setattr("tamq.cli.subprocess.run", lambda args, check=False: calls.append((args, check)) or type("R", (), {"returncode": 0})()) assert main(["attach", "--session", "demo"]) == 0 assert calls == [(["tmux", "attach-session", "-t", "demo"], False)] def test_attach_switches_client_when_already_in_tmux(monkeypatch): calls = [] monkeypatch.setenv("TMUX", "/tmp/tmux,1,0") monkeypatch.setattr("tamq.cli.subprocess.run", lambda args, check=False: calls.append(args) or type("R", (), {"returncode": 0})()) assert main(["attach"]) == 0 assert calls == [["tmux", "switch-client", "-t", "tamq"]] def test_start_parser_supports_command_and_cmd_alias(): parser = build_parser() canonical = parser.parse_args(["start", "--command", "codex --quiet", "a", "b"]) compatibility = parser.parse_args(["start", "--cmd", "claude", "a"]) neutral = parser.parse_args(["start", "a", "b"]) inbox_only = parser.parse_args(["start", "--no-display", "a"]) pushy = parser.parse_args(["start", "--mode", "pushy", "a"]) trigger = parser.parse_args(["start", "--mode", "trigger", "--maxmsg", "3", "a"]) assert canonical.initial_command == "codex --quiet" assert canonical.repos == ["a", "b"] assert compatibility.initial_command == "claude" assert neutral.initial_command is None assert inbox_only.no_display is True assert pushy.mode == "pushy" assert trigger.mode == "trigger" assert trigger.maxmsg == 3 def test_repository_first_and_command_first_start_shorthand(): assert normalize_argv(["flex-auth", "audit-core"]) == [ "start", "flex-auth", "audit-core", ] assert normalize_argv(["--command", "codex", "flex-auth"]) == [ "start", "--command", "codex", "flex-auth", ] assert normalize_argv(["--no-display", "flex-auth"]) == [ "start", "--no-display", "flex-auth", ] assert normalize_argv(["--mode", "pushy", "flex-auth"]) == [ "start", "--mode", "pushy", "flex-auth", ] assert normalize_argv(["--mode=pushy", "flex-auth"]) == [ "start", "--mode=pushy", "flex-auth", ] assert normalize_argv(["--maxmsg=3", "flex-auth"]) == [ "start", "--maxmsg=3", "flex-auth" ] assert normalize_argv(["status"]) == ["status"] assert normalize_argv(["--verbose", "flex-auth", "audit-core"]) == [ "--verbose", "start", "flex-auth", "audit-core", ] def test_detached_no_service_start_skips_socket_registration(monkeypatch, capsys): endpoint = type( "Endpoint", (), { "endpoint_id": "tmux-amq-42", "instance_key": "tmux-amq-42-boot", "pid": 42, "session": "tamq", "repos": ["a", "b"], }, )() class Manager: def preflight(self, repos, command): assert repos == ["a", "b"] assert command is None return "plan" def ensure_plan(self, plan, *, tap=True): assert plan == "plan" assert tap is False return endpoint monkeypatch.setattr("tamq.cli.TmuxManager", Manager) monkeypatch.setattr("tamq.cli.preflight_runtime_paths", lambda: None) monkeypatch.setattr("tamq.cli.request", lambda payload: pytest.fail("socket request must not run")) monkeypatch.setattr("tamq.cli.attach_session", lambda session: pytest.fail("detached start must not attach")) assert main(["start", "--detach", "--no-service", "a", "b"]) == 0 summary = json.loads(capsys.readouterr().out) assert summary["service"] is False assert summary["messaging"] is False assert summary["repos"] == ["a", "b"] def test_interactive_no_service_start_attaches(monkeypatch, capsys): endpoint = type( "Endpoint", (), { "endpoint_id": "tmux-amq-42", "instance_key": "tmux-amq-42-boot", "session": "tamq", "repos": ["a"], }, )() class Manager: def preflight(self, repos, command): return "plan" def ensure_plan(self, plan, *, tap=True): assert tap is False return endpoint attached = [] monkeypatch.setattr("tamq.cli.TmuxManager", Manager) monkeypatch.setattr("tamq.cli.preflight_runtime_paths", lambda: None) monkeypatch.setattr("tamq.cli.attach_session", lambda session: attached.append(session) or 0) assert main(["start", "--no-service", "a"]) == 0 assert attached == ["tamq"] assert json.loads(capsys.readouterr().out)["repos"] == ["a"] def test_registration_failure_rolls_back_created_windows(monkeypatch, capsys): endpoint = type( "Endpoint", (), { "endpoint_id": "tmux-amq-42", "instance_key": "tmux-amq-42-boot", "pid": 42, "session": "tamq", "repos": ["a"], }, )() rolled_back = [] class Manager: def preflight(self, repos, command): return "plan" def ensure_plan(self, plan, *, tap=True, tap_limits=None): assert tap is True assert tap_limits == (8, 1024, 32768) return endpoint def rollback(self, value): rolled_back.append(value) async def failed_registration(payload): return {"ok": False, "error": "denied"} monkeypatch.setattr("tamq.cli.TmuxManager", Manager) monkeypatch.setattr("tamq.cli.preflight_runtime_paths", lambda: None) monkeypatch.setattr("tamq.cli.ensure_output_service", lambda: True) monkeypatch.setattr("tamq.cli.request", failed_registration) assert main(["start", "--detach", "a"]) == 1 assert rolled_back == [endpoint] assert "endpoint registration failed: denied" in capsys.readouterr().err def test_pushy_mode_registers_explicit_delivery_mode(monkeypatch, capsys): endpoint = type( "Endpoint", (), { "endpoint_id": "tmux-amq-42", "instance_key": "tmux-amq-42-boot", "pid": 42, "session": "tamq", "repos": ["a", "b"], }, )() requests = [] class Manager: def preflight(self, repos, command): assert repos == ["a", "b"] assert command == "codex" return "plan" def ensure_plan(self, plan, *, tap=True, tap_limits=None): assert tap is True assert tap_limits == (8, 1024, 32768) return endpoint def rollback(self, value): pytest.fail("successful registration must not roll back") async def register(payload): requests.append(payload) return {"ok": True} monkeypatch.setattr("tamq.cli.TmuxManager", Manager) monkeypatch.setattr("tamq.cli.preflight_runtime_paths", lambda: None) monkeypatch.setattr("tamq.cli.ensure_pushy_service", lambda: True) monkeypatch.setattr( "tamq.cli.ensure_output_service", lambda: pytest.fail("output capability must not select pushy mode"), ) monkeypatch.setattr("tamq.cli.request", register) assert main([ "start", "--detach", "--mode", "pushy", "--command", "codex", "a", "b" ]) == 0 summary = json.loads(capsys.readouterr().out) assert summary["mode"] == "pushy" assert summary["delivery_mode"] == "pushy" assert summary["input_observation_requested"] is True assert requests[0]["delivery_mode"] == "pushy" assert requests[0]["maxmsg"] == 8 def test_tap_requires_explicit_command_and_service(capsys): assert main(["start", "--tap", "a"]) == 2 assert "--tap requires an explicit --command" in capsys.readouterr().err assert main(["start", "--tap", "--no-service", "--command", "sh", "a"]) == 2 assert "--tap cannot be combined with --no-service" in capsys.readouterr().err assert main(["start", "--tap", "--no-display", "--command", "sh", "a"]) == 2 assert "--tap cannot be combined with --no-display" in capsys.readouterr().err assert main(["start", "--no-display", "--mode", "pushy", "a"]) == 2 assert "conflicts with the selected --mode" in capsys.readouterr().err assert main(["start", "--tap", "--mode", "pushy", "--command", "sh", "a"]) == 2 assert "--tap cannot be combined with --mode" in capsys.readouterr().err assert main(["start", "--no-service", "--mode", "pushy", "a"]) == 2 assert "--mode cannot be combined with --no-service" in capsys.readouterr().err def test_start_rejects_nonpositive_line_limits(capsys): assert main(["start", "--maxmsg", "0", "a"]) == 2 assert "maxmsg must be a positive integer" in capsys.readouterr().err