"""Tests for matrix_hermes.cli module.""" import json from unittest.mock import MagicMock, patch import pytest from matrix_hermes.cli import ( _mask_token, build_parser, cmd_check, cmd_login, cmd_logout, cmd_show_config, cmd_whoami, main, ) class TestMaskToken: """Tests for _mask_token helper.""" def test_short_token(self): assert _mask_token("abc") == "***" def test_long_token(self): result = _mask_token("syt_abcdefghijklmnopqrstuvwxyz") assert result.startswith("syt_ab") assert result.endswith("uvwxyz") assert "..." in result def test_exact_length(self): """12-char token with visible=6: len<=12 means fully masked.""" result = _mask_token("123456789012") assert result == "************" def test_just_over_threshold(self): """13-char token with visible=6: len>12 means partial mask.""" result = _mask_token("1234567890123") assert result == "123456...890123" class TestParser: """Tests for CLI argument parser.""" def test_build_parser(self): parser = build_parser() assert "login" in parser._subparsers._group_actions[0].choices assert "whoami" in parser._subparsers._group_actions[0].choices assert "check" in parser._subparsers._group_actions[0].choices assert "show-config" in parser._subparsers._group_actions[0].choices def test_no_args_shows_help(self, capsys): """Running with no args shows help.""" result = main([]) # Should not crash captured = capsys.readouterr() assert "usage" in captured.out.lower() or result == 0 class TestCmdCheck: """Tests for 'check' command.""" def test_not_configured(self, clean_env, capsys): """When no Matrix vars are set, check should fail.""" args = build_parser().parse_args(["check"]) result = cmd_check(args) assert result == 1 captured = capsys.readouterr() assert "not configured" in captured.out.lower() def test_configured(self, mock_env, capsys): """When Matrix vars are set, check should succeed.""" args = build_parser().parse_args(["check"]) result = cmd_check(args) assert result == 0 captured = capsys.readouterr() assert "valid" in captured.out.lower() class TestCmdShowConfig: """Tests for 'show-config' command.""" def test_output_format(self, mock_env, capsys): """show-config outputs env var format.""" args = build_parser().parse_args(["show-config"]) result = cmd_show_config(args) assert result == 0 captured = capsys.readouterr() assert "MATRIX_HOMESERVER=" in captured.out assert "MATRIX_ACCESS_TOKEN=" in captured.out def test_redacted(self, mock_env, capsys): """show-config --redact masks sensitive values.""" args = build_parser().parse_args(["show-config", "--redact"]) result = cmd_show_config(args) assert result == 0 captured = capsys.readouterr() # Token should be masked assert "syt_test_token" not in captured.out assert "syt_test" in captured.out or "..." in captured.out class TestCmdLogin: """Tests for 'login' command.""" def test_no_homeserver(self, clean_env, capsys): """Login fails without homeserver.""" args = build_parser().parse_args(["login"]) result = cmd_login(args) assert result == 1 def test_with_valid_token(self, mock_env, capsys): """Login with valid token prints success.""" with patch("urllib.request.urlopen") as mock_urlopen: mock_resp = MagicMock() mock_resp.read.return_value = json.dumps( {"user_id": "@bot:example.org", "device_id": "DEV1"} ).encode() mock_resp.__enter__.return_value = mock_resp mock_urlopen.return_value = mock_resp args = build_parser().parse_args(["login"]) result = cmd_login(args) assert result == 0 def test_with_bad_credentials(self, mock_env, capsys): """Login with bad credentials prints error.""" with patch("urllib.request.urlopen") as mock_urlopen: mock_resp = MagicMock() mock_resp.read.return_value = json.dumps( {"errcode": "M_FORBIDDEN", "error": "Bad password"} ).encode() mock_resp.__enter__.return_value = mock_resp mock_urlopen.return_value = mock_resp # Override with password-based login (no valid token) import os os.environ["MATRIX_ACCESS_TOKEN"] = "" os.environ["MATRIX_PASSWORD"] = "wrong" args = build_parser().parse_args(["login"]) result = cmd_login(args) assert result == 1 class TestCmdWhoami: """Tests for 'whoami' command.""" def test_no_homeserver(self, clean_env, capsys): """whoami fails without homeserver.""" args = build_parser().parse_args(["whoami"]) result = cmd_whoami(args) assert result == 1 def test_valid(self, mock_env, capsys): """whoami with valid token.""" with patch("urllib.request.urlopen") as mock_urlopen: mock_resp = MagicMock() mock_resp.read.return_value = json.dumps( {"user_id": "@bot:example.org"} ).encode() mock_resp.__enter__.return_value = mock_resp mock_urlopen.return_value = mock_resp args = build_parser().parse_args(["whoami"]) result = cmd_whoami(args) assert result == 0 def test_invalid(self, mock_env, capsys): """whoami with invalid token.""" with patch("urllib.request.urlopen") as mock_urlopen: mock_resp = MagicMock() mock_resp.read.return_value = json.dumps( {"errcode": "M_UNKNOWN_TOKEN", "error": "Unrecognised"} ).encode() mock_resp.__enter__.return_value = mock_resp mock_urlopen.return_value = mock_resp args = build_parser().parse_args(["whoami"]) result = cmd_whoami(args) assert result == 1 class TestCmdLogout: """Tests for 'logout' command.""" def test_no_homeserver(self, clean_env, capsys): """logout fails without homeserver.""" args = build_parser().parse_args(["logout"]) result = cmd_logout(args) assert result == 1 def test_success(self, mock_env, capsys): """logout with valid token.""" with patch("urllib.request.urlopen") as mock_urlopen: mock_resp = MagicMock() mock_resp.read.return_value = json.dumps({}).encode() mock_resp.__enter__.return_value = mock_resp mock_urlopen.return_value = mock_resp args = build_parser().parse_args(["logout"]) result = cmd_logout(args) assert result == 0