pbi-cli/tests/test_helpers.py
MinaSaad1 51a23668a7 feat: add REPL mode, test suite, CI/CD, and Claude Skills (Sprints 6-8)
Sprint 6 - REPL Mode + Polish:
- Error hierarchy (PbiCliError, McpToolError, etc.) for clean REPL error handling
- Interactive REPL with prompt-toolkit (persistent MCP connection, command completion, history)
- REPL-aware run_tool() and connection commands that reuse shared client
- README.md and CHANGELOG.md

Sprint 7 - Tests + CI/CD:
- 120 tests across unit, command, and e2e test files (79% coverage)
- MockPbiMcpClient with canned responses for test isolation
- GitHub Actions CI (lint + typecheck + test matrix: 3 OS x 3 Python)
- GitHub Actions release workflow for PyPI trusted publishing

Sprint 8 - Claude Skills + Installer:
- 5 bundled SKILL.md files (modeling, dax, deployment, security, docs)
- `pbi skills install/list/uninstall` command for Claude Code discovery
- Skills packaged with wheel via setuptools package-data
2026-03-26 13:54:24 +02:00

94 lines
3 KiB
Python

"""Tests for pbi_cli.commands._helpers."""
from __future__ import annotations
from pathlib import Path
import pytest
from pbi_cli.commands._helpers import build_definition, run_tool
from pbi_cli.core.errors import McpToolError
from pbi_cli.main import PbiContext
from tests.conftest import MockPbiMcpClient
def test_build_definition_required_only() -> None:
result = build_definition(
required={"name": "Sales"},
optional={},
)
assert result == {"name": "Sales"}
def test_build_definition_filters_none() -> None:
result = build_definition(
required={"name": "Sales"},
optional={"description": None, "folder": "Finance"},
)
assert result == {"name": "Sales", "folder": "Finance"}
assert "description" not in result
def test_build_definition_preserves_falsy_non_none() -> None:
result = build_definition(
required={"name": "Sales"},
optional={"hidden": False, "count": 0, "label": ""},
)
assert result["hidden"] is False
assert result["count"] == 0
assert result["label"] == ""
def test_run_tool_adds_connection(monkeypatch: pytest.MonkeyPatch) -> None:
mock = MockPbiMcpClient()
monkeypatch.setattr("pbi_cli.commands._helpers.get_client", lambda repl_mode=False: mock)
ctx = PbiContext(json_output=True, connection="my-conn")
run_tool(ctx, "measure_operations", {"operation": "List"})
assert mock.calls[0][1]["connectionName"] == "my-conn"
def test_run_tool_no_connection(monkeypatch: pytest.MonkeyPatch) -> None:
mock = MockPbiMcpClient()
monkeypatch.setattr("pbi_cli.commands._helpers.get_client", lambda repl_mode=False: mock)
ctx = PbiContext(json_output=True)
run_tool(ctx, "measure_operations", {"operation": "List"})
assert "connectionName" not in mock.calls[0][1]
def test_run_tool_stops_client_in_oneshot(monkeypatch: pytest.MonkeyPatch) -> None:
mock = MockPbiMcpClient()
monkeypatch.setattr("pbi_cli.commands._helpers.get_client", lambda repl_mode=False: mock)
ctx = PbiContext(json_output=True, repl_mode=False)
run_tool(ctx, "measure_operations", {"operation": "List"})
assert mock.stopped is True
def test_run_tool_keeps_client_in_repl(monkeypatch: pytest.MonkeyPatch) -> None:
mock = MockPbiMcpClient()
monkeypatch.setattr("pbi_cli.commands._helpers.get_client", lambda repl_mode=False: mock)
ctx = PbiContext(json_output=True, repl_mode=True)
run_tool(ctx, "measure_operations", {"operation": "List"})
assert mock.stopped is False
def test_run_tool_raises_mcp_tool_error_on_failure(
monkeypatch: pytest.MonkeyPatch,
) -> None:
class FailingClient(MockPbiMcpClient):
def call_tool(self, tool_name: str, request: dict) -> None:
raise RuntimeError("server crashed")
mock = FailingClient()
monkeypatch.setattr("pbi_cli.commands._helpers.get_client", lambda repl_mode=False: mock)
ctx = PbiContext(json_output=True)
with pytest.raises(McpToolError):
run_tool(ctx, "measure_operations", {"operation": "List"})