Add 5 board management tools extending the Trello client: - trello_create_board — create a new board - trello_rename_board — rename an existing board - trello_archive_board — close/archive a board - trello_open_board — re-open a closed board - trello_board_details — view board with lists and members Includes board name-to-ID resolution, 16 new tests, and spec doc. Issue: #2
406 lines
14 KiB
Python
406 lines
14 KiB
Python
"""Tests for the Trello plugin — board management feature."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from typing import Any
|
|
|
|
import pytest
|
|
import requests
|
|
|
|
pytestmark = pytest.mark.usefixtures("clear_env")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fixtures
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clear_env(monkeypatch: pytest.MonkeyPatch) -> Any:
|
|
"""Remove Trello env vars before each test so state is predictable."""
|
|
monkeypatch.delenv("TRELLO_API_KEY", raising=False)
|
|
monkeypatch.delenv("TRELLO_TOKEN", raising=False)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TrelloClient — create_board
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestCreateBoard:
|
|
"""Tests for TrelloClient.create_board()."""
|
|
|
|
def test_success(self, requests_mock: Any) -> None:
|
|
"""Happy path: creates a board and returns details."""
|
|
from trello_plugin.client import TrelloClient
|
|
|
|
requests_mock.post(
|
|
"https://api.trello.com/1/boards",
|
|
json={"id": "b1", "name": "New Board", "url": "https://trello.com/b/b1"},
|
|
status_code=200,
|
|
)
|
|
client = TrelloClient(api_key="key", token="tok")
|
|
result = client.create_board(name="New Board")
|
|
|
|
assert result["success"] is True
|
|
assert result["board"]["name"] == "New Board"
|
|
assert result["board"]["id"] == "b1"
|
|
|
|
def test_missing_credentials(self) -> None:
|
|
"""Missing creds returns error before any network call."""
|
|
from trello_plugin.client import TrelloClient
|
|
|
|
client = TrelloClient(api_key="", token="")
|
|
result = client.create_board(name="Board")
|
|
|
|
assert result["success"] is False
|
|
assert "TRELLO_API_KEY" in result["message"]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TrelloClient — rename_board
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestRenameBoard:
|
|
"""Tests for TrelloClient.rename_board()."""
|
|
|
|
def test_success_by_id(self, requests_mock: Any) -> None:
|
|
"""Renaming a board by ID works."""
|
|
from trello_plugin.client import TrelloClient
|
|
|
|
# Resolve board
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "Old Name", "url": "https://trello.com/b/b1"},
|
|
)
|
|
# Rename
|
|
requests_mock.put(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "New Name", "url": "https://trello.com/b/b1"},
|
|
)
|
|
|
|
client = TrelloClient(api_key="key", token="tok")
|
|
result = client.rename_board(board_id="b1", name="New Name")
|
|
|
|
assert result["success"] is True
|
|
assert result["board"]["name"] == "New Name"
|
|
|
|
def test_board_not_found(self, requests_mock: Any) -> None:
|
|
"""Non-existent board returns a clear error."""
|
|
from trello_plugin.client import TrelloClient
|
|
|
|
# Board lookup fails
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/nonexistent",
|
|
status_code=404,
|
|
)
|
|
# List boards returns empty
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/members/me/boards",
|
|
json=[],
|
|
)
|
|
|
|
client = TrelloClient(api_key="key", token="tok")
|
|
result = client.rename_board(board_id="nonexistent", name="New")
|
|
|
|
assert result["success"] is False
|
|
assert "not found" in result["message"].lower()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TrelloClient — archive_board
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestArchiveBoard:
|
|
"""Tests for TrelloClient.archive_board()."""
|
|
|
|
def test_success(self, requests_mock: Any) -> None:
|
|
"""Archiving a board returns success message."""
|
|
from trello_plugin.client import TrelloClient
|
|
|
|
# Resolve
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "My Board", "url": "https://trello.com/b/b1"},
|
|
)
|
|
# Archive
|
|
requests_mock.put(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "My Board", "closed": True},
|
|
)
|
|
|
|
client = TrelloClient(api_key="key", token="tok")
|
|
result = client.archive_board(board_id="b1")
|
|
|
|
assert result["success"] is True
|
|
assert "archived" in result["message"].lower()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TrelloClient — open_board
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestOpenBoard:
|
|
"""Tests for TrelloClient.open_board()."""
|
|
|
|
def test_success(self, requests_mock: Any) -> None:
|
|
"""Opening a closed board returns success message."""
|
|
from trello_plugin.client import TrelloClient
|
|
|
|
# Resolve
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "My Board", "url": "https://trello.com/b/b1"},
|
|
)
|
|
# Open
|
|
requests_mock.put(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "My Board", "closed": False},
|
|
)
|
|
|
|
client = TrelloClient(api_key="key", token="tok")
|
|
result = client.open_board(board_id="b1")
|
|
|
|
assert result["success"] is True
|
|
assert "opened" in result["message"].lower()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TrelloClient — board_details
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestBoardDetails:
|
|
"""Tests for TrelloClient.board_details()."""
|
|
|
|
def test_success(self, requests_mock: Any) -> None:
|
|
"""Board details return lists and members."""
|
|
from trello_plugin.client import TrelloClient
|
|
|
|
# Resolve
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "My Board", "url": "https://trello.com/b/b1"},
|
|
)
|
|
# Details fetch
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={
|
|
"id": "b1",
|
|
"name": "My Board",
|
|
"url": "https://trello.com/b/b1",
|
|
"desc": "A test board",
|
|
"closed": False,
|
|
"starred": False,
|
|
"lists": [
|
|
{"id": "l1", "name": "To Do"},
|
|
{"id": "l2", "name": "Done"},
|
|
],
|
|
"members": [
|
|
{"id": "m1", "username": "user1", "fullName": "User One"},
|
|
],
|
|
},
|
|
)
|
|
|
|
client = TrelloClient(api_key="key", token="tok")
|
|
result = client.board_details(board_id="b1")
|
|
|
|
assert result["success"] is True
|
|
assert len(result["board"]["lists"]) == 2
|
|
assert len(result["board"]["members"]) == 1
|
|
assert result["board"]["lists"][0]["name"] == "To Do"
|
|
|
|
def test_not_found(self, requests_mock: Any) -> None:
|
|
"""Non-existent board returns error."""
|
|
from trello_plugin.client import TrelloClient
|
|
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/nonexistent",
|
|
status_code=404,
|
|
)
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/members/me/boards",
|
|
json=[],
|
|
)
|
|
|
|
client = TrelloClient(api_key="key", token="tok")
|
|
result = client.board_details(board_id="nonexistent")
|
|
|
|
assert result["success"] is False
|
|
assert "not found" in result["message"].lower()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TrelloClient — _resolve_board_id
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestResolveBoardId:
|
|
"""Tests for TrelloClient._resolve_board_id()."""
|
|
|
|
def test_resolves_by_id(self, requests_mock: Any) -> None:
|
|
"""Resolves a valid ID directly."""
|
|
from trello_plugin.client import TrelloClient
|
|
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "My Board", "url": "https://trello.com/b/b1"},
|
|
)
|
|
|
|
client = TrelloClient(api_key="key", token="tok")
|
|
result = client._resolve_board_id("b1")
|
|
assert result["id"] == "b1"
|
|
|
|
def test_resolves_by_name(self, requests_mock: Any) -> None:
|
|
"""Resolves a board name to its ID."""
|
|
from trello_plugin.client import TrelloClient
|
|
|
|
# ID lookup fails
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/My%20Board",
|
|
status_code=404,
|
|
)
|
|
# List boards for name matching
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/members/me/boards",
|
|
json=[
|
|
{"id": "b1", "name": "My Board", "url": "https://trello.com/b/b1", "closed": False, "starred": False},
|
|
{"id": "b2", "name": "Other Board", "url": "https://trello.com/b/b2", "closed": False, "starred": False},
|
|
],
|
|
)
|
|
|
|
client = TrelloClient(api_key="key", token="tok")
|
|
result = client._resolve_board_id("My Board")
|
|
assert result["id"] == "b1"
|
|
|
|
def test_duplicate_name_error(self, requests_mock: Any) -> None:
|
|
"""Multiple boards with same name returns error."""
|
|
from trello_plugin.client import TrelloClient
|
|
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/Duplicate",
|
|
status_code=404,
|
|
)
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/members/me/boards",
|
|
json=[
|
|
{"id": "b1", "name": "Duplicate", "url": "", "closed": False, "starred": False},
|
|
{"id": "b2", "name": "Duplicate", "url": "", "closed": False, "starred": False},
|
|
],
|
|
)
|
|
|
|
client = TrelloClient(api_key="key", token="tok")
|
|
result = client._resolve_board_id("Duplicate")
|
|
assert "success" in result and result["success"] is False
|
|
assert "multiple" in result["message"].lower()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tool functions
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestBoardToolFunctions:
|
|
"""Tests for the Hermes board management tool wrappers."""
|
|
|
|
def test_trello_create_board_tool(self, requests_mock: Any) -> None:
|
|
"""Create board tool returns valid JSON."""
|
|
from trello_plugin.tools import trello_create_board
|
|
|
|
os.environ["TRELLO_API_KEY"] = "key"
|
|
os.environ["TRELLO_TOKEN"] = "tok"
|
|
|
|
requests_mock.post(
|
|
"https://api.trello.com/1/boards",
|
|
json={"id": "b1", "name": "New Board", "url": "https://trello.com/b/b1"},
|
|
)
|
|
|
|
result = json.loads(trello_create_board(name="New Board"))
|
|
assert result["success"] is True
|
|
assert result["board"]["name"] == "New Board"
|
|
|
|
def test_trello_rename_board_tool(self, requests_mock: Any) -> None:
|
|
"""Rename board tool returns valid JSON."""
|
|
from trello_plugin.tools import trello_rename_board
|
|
|
|
os.environ["TRELLO_API_KEY"] = "key"
|
|
os.environ["TRELLO_TOKEN"] = "tok"
|
|
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "Old", "url": ""},
|
|
)
|
|
requests_mock.put(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "Renamed", "url": ""},
|
|
)
|
|
|
|
result = json.loads(trello_rename_board(board_id="b1", name="Renamed"))
|
|
assert result["success"] is True
|
|
assert result["board"]["name"] == "Renamed"
|
|
|
|
def test_trello_archive_board_tool(self, requests_mock: Any) -> None:
|
|
"""Archive board tool returns valid JSON."""
|
|
from trello_plugin.tools import trello_archive_board
|
|
|
|
os.environ["TRELLO_API_KEY"] = "key"
|
|
os.environ["TRELLO_TOKEN"] = "tok"
|
|
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "Test Board", "url": ""},
|
|
)
|
|
requests_mock.put(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "Test Board", "closed": True},
|
|
)
|
|
|
|
result = json.loads(trello_archive_board(board_id="b1"))
|
|
assert result["success"] is True
|
|
assert "archived" in result["message"].lower()
|
|
|
|
def test_trello_open_board_tool(self, requests_mock: Any) -> None:
|
|
"""Open board tool returns valid JSON."""
|
|
from trello_plugin.tools import trello_open_board
|
|
|
|
os.environ["TRELLO_API_KEY"] = "key"
|
|
os.environ["TRELLO_TOKEN"] = "tok"
|
|
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "Test Board", "url": ""},
|
|
)
|
|
requests_mock.put(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "Test Board", "closed": False},
|
|
)
|
|
|
|
result = json.loads(trello_open_board(board_id="b1"))
|
|
assert result["success"] is True
|
|
assert "opened" in result["message"].lower()
|
|
|
|
def test_trello_board_details_tool(self, requests_mock: Any) -> None:
|
|
"""Board details tool returns valid JSON."""
|
|
from trello_plugin.tools import trello_board_details
|
|
|
|
os.environ["TRELLO_API_KEY"] = "key"
|
|
os.environ["TRELLO_TOKEN"] = "tok"
|
|
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={"id": "b1", "name": "Test", "url": ""},
|
|
)
|
|
requests_mock.get(
|
|
"https://api.trello.com/1/boards/b1",
|
|
json={
|
|
"id": "b1",
|
|
"name": "Test",
|
|
"url": "",
|
|
"desc": "",
|
|
"closed": False,
|
|
"starred": False,
|
|
"lists": [{"id": "l1", "name": "To Do"}],
|
|
"members": [],
|
|
},
|
|
)
|
|
|
|
result = json.loads(trello_board_details(board_id="b1"))
|
|
assert result["success"] is True
|
|
assert len(result["board"]["lists"]) == 1 |