"""Tests for the Click CLI: serve startup errors, check success/failure.""" from __future__ import annotations import pytest from click.testing import CliRunner from mcp import types from mcp_sonarqube_proxy import cli def test_check_success_lists_tools(monkeypatch: pytest.MonkeyPatch) -> None: fake_tools = [ types.Tool( name="search", description="Search issues.", inputSchema={"type": "object"}, ), types.Tool( name="get_metric", title="Project Metric", description=None, inputSchema={"type": "object"}, ), ] async def fake_run_check() -> list[types.Tool]: return fake_tools monkeypatch.setattr(cli, "_run_check", fake_run_check) result = CliRunner().invoke(cli.main, ["check"]) assert result.exit_code == 0 assert "2 Tools" in result.output assert "search" in result.output assert "Project Metric" in result.output def test_check_failure_exits_one_with_stderr(monkeypatch: pytest.MonkeyPatch) -> None: async def fake_run_check() -> list[types.Tool]: raise ConnectionError("connection refused") monkeypatch.setattr(cli, "_run_check", fake_run_check) result = CliRunner().invoke(cli.main, ["check"]) assert result.exit_code == 1 assert "Upstream nicht erreichbar" in result.stderr assert "connection refused" in result.stderr def test_serve_startup_error_exits_one(monkeypatch: pytest.MonkeyPatch) -> None: async def fake_run_serve() -> None: raise RuntimeError("upstream initialize failed") monkeypatch.setattr(cli, "_run_serve", fake_run_serve) result = CliRunner().invoke(cli.main, ["serve"]) assert result.exit_code == 1 assert "fatal" in result.stderr assert "upstream initialize failed" in result.stderr def test_version_flag() -> None: from mcp_sonarqube_proxy import __version__ result = CliRunner().invoke(cli.main, ["--version"]) assert result.exit_code == 0 assert __version__ in result.output # --- SONARQUBE_TOKEN handling ------------------------------------------------ def test_check_missing_token_exits_one_with_clean_message( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.delenv("SONARQUBE_TOKEN", raising=False) result = CliRunner().invoke(cli.main, ["check"]) assert result.exit_code == 1 assert "SONARQUBE_TOKEN" in result.stderr assert "Security" in result.stderr def test_serve_missing_token_exits_one_with_clean_message( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.delenv("SONARQUBE_TOKEN", raising=False) result = CliRunner().invoke(cli.main, ["serve"]) assert result.exit_code == 1 assert "SONARQUBE_TOKEN" in result.stderr def test_check_redacts_token_when_error_includes_it( monkeypatch: pytest.MonkeyPatch, ) -> None: secret = "secret-token-do-not-leak" monkeypatch.setenv("SONARQUBE_TOKEN", secret) async def fake_run_check() -> list[types.Tool]: # Simulate a worst-case dependency that leaks the token in its message. raise RuntimeError(f"401 Unauthorized; sent header Bearer {secret}") monkeypatch.setattr(cli, "_run_check", fake_run_check) result = CliRunner().invoke(cli.main, ["check"]) assert result.exit_code == 1 assert secret not in result.stderr assert "[REDACTED]" in result.stderr def test_serve_redacts_token_when_error_includes_it( monkeypatch: pytest.MonkeyPatch, ) -> None: secret = "another-secret-token-leak" monkeypatch.setenv("SONARQUBE_TOKEN", secret) async def fake_run_serve() -> None: raise RuntimeError(f"upstream said Bearer {secret} is invalid") monkeypatch.setattr(cli, "_run_serve", fake_run_serve) result = CliRunner().invoke(cli.main, ["serve"]) assert result.exit_code == 1 assert secret not in result.stderr assert "[REDACTED]" in result.stderr