feat: implement Trello card management
Add 12 card management tools: - trello_create_card — create a card on a list - trello_card_details — view card with members, checklists, comments - trello_update_card — update title, description, due date - trello_move_card — move card to a different list - trello_archive_card — archive a card - trello_assign_member / trello_remove_member — member management - trello_add_comment / trello_delete_comment — comments - trello_add_checklist_item / trello_toggle_checklist_item / trello_delete_checklist_item Also adds _delete() helper on TrelloClient for DELETE verbs. 73 total tests — all passing. Issue: #4
This commit is contained in:
+1
-1
@@ -325,5 +325,5 @@ class TestPluginMetadata:
|
||||
assert PLUGIN_NAME == "trello-plugin"
|
||||
assert isinstance(PLUGIN_DESCRIPTION, str)
|
||||
assert isinstance(PLUGIN_VERSION, str)
|
||||
assert len(PLUGIN_TOOLS) == 12
|
||||
assert len(PLUGIN_TOOLS) == 24
|
||||
assert all(callable(t) for t in PLUGIN_TOOLS)
|
||||
@@ -0,0 +1,313 @@
|
||||
"""Tests for the Trello plugin — card management feature."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("clear_env")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clear_env(monkeypatch: pytest.MonkeyPatch) -> Any:
|
||||
monkeypatch.delenv("TRELLO_API_KEY", raising=False)
|
||||
monkeypatch.delenv("TRELLO_TOKEN", raising=False)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TrelloClient — create_card
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestCreateCard:
|
||||
def test_success(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
requests_mock.post("https://api.trello.com/1/cards", json={"id": "c1", "name": "My Card", "url": "https://trello.com/c/c1", "shortUrl": "https://trello.com/c/abc", "idList": "l1"})
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.create_card(name="My Card", list_id="l1")
|
||||
assert result["success"]
|
||||
assert result["card"]["name"] == "My Card"
|
||||
|
||||
def test_with_description_and_due(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
requests_mock.post("https://api.trello.com/1/cards", json={"id": "c1", "name": "Card", "url": "", "shortUrl": "", "idList": "l1"})
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.create_card(name="Card", list_id="l1", desc="Desc", due="2026-06-01T00:00:00Z")
|
||||
assert result["success"]
|
||||
|
||||
def test_missing_credentials(self) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
client = TrelloClient(api_key="", token="")
|
||||
result = client.create_card(name="Card", list_id="l1")
|
||||
assert not result["success"]
|
||||
assert "TRELLO_API_KEY" in result["message"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TrelloClient — card_details
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestCardDetails:
|
||||
def test_success(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
requests_mock.get(
|
||||
"https://api.trello.com/1/cards/c1",
|
||||
json={
|
||||
"id": "c1", "name": "My Card", "desc": "Description", "due": None,
|
||||
"dueComplete": False, "url": "https://trello.com/c/c1", "shortUrl": "https://trello.com/c/abc",
|
||||
"idList": "l1", "closed": False, "list": {"id": "l1", "name": "To Do"},
|
||||
"members": [{"id": "m1", "username": "user1", "fullName": "User One"}],
|
||||
"checklists": [{"id": "cl1", "name": "Checklist", "checkItems": [{"id": "ci1", "name": "Item", "state": "incomplete"}]}],
|
||||
"actions": [{"id": "a1", "type": "commentCard", "data": {"text": "Nice card"}, "memberCreator": {"username": "user1"}, "date": "2026-05-01T00:00:00Z"}],
|
||||
},
|
||||
)
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.card_details(card_id="c1")
|
||||
assert result["success"]
|
||||
assert result["card"]["name"] == "My Card"
|
||||
assert len(result["card"]["members"]) == 1
|
||||
assert len(result["card"]["checklists"]) == 1
|
||||
assert len(result["card"]["comments"]) == 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TrelloClient — update_card
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestUpdateCard:
|
||||
def test_update_name(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
requests_mock.put("https://api.trello.com/1/cards/c1", json={"id": "c1", "name": "Updated", "desc": "", "due": None})
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.update_card(card_id="c1", name="Updated")
|
||||
assert result["success"]
|
||||
assert result["card"]["name"] == "Updated"
|
||||
|
||||
def test_no_changes(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.update_card(card_id="c1")
|
||||
assert result["success"]
|
||||
assert "No changes" in result["message"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TrelloClient — move_card, archive_card
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestMoveCard:
|
||||
def test_success(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
requests_mock.put("https://api.trello.com/1/cards/c1", json={"id": "c1", "name": "Card", "idList": "l2"})
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.move_card(card_id="c1", list_id="l2")
|
||||
assert result["success"]
|
||||
|
||||
|
||||
class TestArchiveCard:
|
||||
def test_success(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
requests_mock.put("https://api.trello.com/1/cards/c1", json={"id": "c1", "name": "Card", "closed": True})
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.archive_card(card_id="c1")
|
||||
assert result["success"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TrelloClient — member operations
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestMemberOps:
|
||||
def test_assign_member(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
requests_mock.post("https://api.trello.com/1/cards/c1/idMembers", json={"id": "c1"}, status_code=200)
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.assign_member(card_id="c1", member_id="m1")
|
||||
assert result["success"]
|
||||
|
||||
def test_remove_member(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
requests_mock.delete("https://api.trello.com/1/cards/c1/idMembers/m1", status_code=200, json={})
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.remove_member(card_id="c1", member_id="m1")
|
||||
assert result["success"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TrelloClient — comments
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestComments:
|
||||
def test_add_comment(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
requests_mock.post("https://api.trello.com/1/cards/c1/actions/comments", json={"id": "a1", "data": {"text": "Great!"}})
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.add_comment(card_id="c1", text="Great!")
|
||||
assert result["success"]
|
||||
assert result["comment"]["id"] == "a1"
|
||||
|
||||
def test_delete_comment(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
requests_mock.delete("https://api.trello.com/1/cards/c1/actions/a1/comments", status_code=200, json={})
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.delete_comment(card_id="c1", comment_id="a1")
|
||||
assert result["success"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TrelloClient — checklists
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestChecklistItems:
|
||||
def test_add_item_with_checklist_id(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
requests_mock.post("https://api.trello.com/1/cards/c1/checklistItems", json={"id": "ci1", "name": "Task", "state": "incomplete", "idChecklist": "cl1"})
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.add_checklist_item(card_id="c1", name="Task", checklist_id="cl1")
|
||||
assert result["success"]
|
||||
assert result["item"]["name"] == "Task"
|
||||
|
||||
def test_add_item_auto_discover(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
# card_details call for auto-discover
|
||||
requests_mock.get(
|
||||
"https://api.trello.com/1/cards/c1",
|
||||
json={
|
||||
"id": "c1", "name": "Card", "desc": "", "due": None,
|
||||
"dueComplete": False, "url": "", "shortUrl": "", "idList": "l1", "closed": False,
|
||||
"list": {"id": "l1", "name": "List"},
|
||||
"members": [], "checklists": [{"id": "cl1", "name": "Checklist", "checkItems": []}],
|
||||
"actions": [],
|
||||
},
|
||||
)
|
||||
requests_mock.post("https://api.trello.com/1/cards/c1/checklistItems", json={"id": "ci1", "name": "Auto", "state": "incomplete", "idChecklist": "cl1"})
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.add_checklist_item(card_id="c1", name="Auto")
|
||||
assert result["success"]
|
||||
|
||||
def test_toggle_complete(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
requests_mock.put("https://api.trello.com/1/cards/c1/checklistItem/ci1", json={"id": "ci1", "name": "Task", "state": "complete"})
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.toggle_checklist_item(card_id="c1", item_id="ci1", checked=True)
|
||||
assert result["success"]
|
||||
assert result["item"]["state"] == "complete"
|
||||
|
||||
def test_toggle_incomplete(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
requests_mock.put("https://api.trello.com/1/cards/c1/checklistItem/ci1", json={"id": "ci1", "name": "Task", "state": "incomplete"})
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.toggle_checklist_item(card_id="c1", item_id="ci1", checked=False)
|
||||
assert result["success"]
|
||||
assert result["item"]["state"] == "incomplete"
|
||||
|
||||
def test_delete_item(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.client import TrelloClient
|
||||
requests_mock.delete("https://api.trello.com/1/cards/c1/checklistItems/ci1", status_code=200, json={})
|
||||
client = TrelloClient(api_key="k", token="t")
|
||||
result = client.delete_checklist_item(card_id="c1", item_id="ci1")
|
||||
assert result["success"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool functions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestCardToolFunctions:
|
||||
def test_trello_create_card_tool(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.tools import trello_create_card
|
||||
os.environ["TRELLO_API_KEY"] = "k"
|
||||
os.environ["TRELLO_TOKEN"] = "t"
|
||||
requests_mock.post("https://api.trello.com/1/cards", json={"id": "c1", "name": "Card", "url": "", "shortUrl": "", "idList": "l1"})
|
||||
result = json.loads(trello_create_card(name="Card", list_id="l1"))
|
||||
assert result["success"]
|
||||
|
||||
def test_trello_card_details_tool(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.tools import trello_card_details
|
||||
os.environ["TRELLO_API_KEY"] = "k"
|
||||
os.environ["TRELLO_TOKEN"] = "t"
|
||||
requests_mock.get("https://api.trello.com/1/cards/c1", json={"id": "c1", "name": "Card", "desc": "", "due": None, "dueComplete": False, "url": "", "shortUrl": "", "idList": "l1", "closed": False, "list": {"id": "l1", "name": "List"}, "members": [], "checklists": [], "actions": []})
|
||||
result = json.loads(trello_card_details(card_id="c1"))
|
||||
assert result["success"]
|
||||
|
||||
def test_trello_update_card_tool(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.tools import trello_update_card
|
||||
os.environ["TRELLO_API_KEY"] = "k"
|
||||
os.environ["TRELLO_TOKEN"] = "t"
|
||||
requests_mock.put("https://api.trello.com/1/cards/c1", json={"id": "c1", "name": "New", "desc": "", "due": None})
|
||||
result = json.loads(trello_update_card(card_id="c1", name="New"))
|
||||
assert result["success"]
|
||||
|
||||
def test_trello_move_card_tool(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.tools import trello_move_card
|
||||
os.environ["TRELLO_API_KEY"] = "k"
|
||||
os.environ["TRELLO_TOKEN"] = "t"
|
||||
requests_mock.put("https://api.trello.com/1/cards/c1", json={"id": "c1", "name": "Card", "idList": "l2"})
|
||||
result = json.loads(trello_move_card(card_id="c1", list_id="l2"))
|
||||
assert result["success"]
|
||||
|
||||
def test_trello_archive_card_tool(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.tools import trello_archive_card
|
||||
os.environ["TRELLO_API_KEY"] = "k"
|
||||
os.environ["TRELLO_TOKEN"] = "t"
|
||||
requests_mock.put("https://api.trello.com/1/cards/c1", json={"id": "c1", "name": "Card", "closed": True})
|
||||
result = json.loads(trello_archive_card(card_id="c1"))
|
||||
assert result["success"]
|
||||
|
||||
def test_trello_assign_member_tool(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.tools import trello_assign_member
|
||||
os.environ["TRELLO_API_KEY"] = "k"
|
||||
os.environ["TRELLO_TOKEN"] = "t"
|
||||
requests_mock.post("https://api.trello.com/1/cards/c1/idMembers", json={}, status_code=200)
|
||||
result = json.loads(trello_assign_member(card_id="c1", member_id="m1"))
|
||||
assert result["success"]
|
||||
|
||||
def test_trello_remove_member_tool(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.tools import trello_remove_member
|
||||
os.environ["TRELLO_API_KEY"] = "k"
|
||||
os.environ["TRELLO_TOKEN"] = "t"
|
||||
requests_mock.delete("https://api.trello.com/1/cards/c1/idMembers/m1", status_code=200, json={})
|
||||
result = json.loads(trello_remove_member(card_id="c1", member_id="m1"))
|
||||
assert result["success"]
|
||||
|
||||
def test_trello_add_comment_tool(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.tools import trello_add_comment
|
||||
os.environ["TRELLO_API_KEY"] = "k"
|
||||
os.environ["TRELLO_TOKEN"] = "t"
|
||||
requests_mock.post("https://api.trello.com/1/cards/c1/actions/comments", json={"id": "a1", "data": {"text": "Nice"}})
|
||||
result = json.loads(trello_add_comment(card_id="c1", text="Nice"))
|
||||
assert result["success"]
|
||||
|
||||
def test_trello_delete_comment_tool(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.tools import trello_delete_comment
|
||||
os.environ["TRELLO_API_KEY"] = "k"
|
||||
os.environ["TRELLO_TOKEN"] = "t"
|
||||
requests_mock.delete("https://api.trello.com/1/cards/c1/actions/a1/comments", status_code=200, json={})
|
||||
result = json.loads(trello_delete_comment(card_id="c1", comment_id="a1"))
|
||||
assert result["success"]
|
||||
|
||||
def test_trello_add_checklist_item_tool(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.tools import trello_add_checklist_item
|
||||
os.environ["TRELLO_API_KEY"] = "k"
|
||||
os.environ["TRELLO_TOKEN"] = "t"
|
||||
requests_mock.post("https://api.trello.com/1/cards/c1/checklistItems", json={"id": "ci1", "name": "Task", "state": "incomplete", "idChecklist": "cl1"})
|
||||
result = json.loads(trello_add_checklist_item(card_id="c1", name="Task", checklist_id="cl1"))
|
||||
assert result["success"]
|
||||
|
||||
def test_trello_toggle_checklist_item_tool(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.tools import trello_toggle_checklist_item
|
||||
os.environ["TRELLO_API_KEY"] = "k"
|
||||
os.environ["TRELLO_TOKEN"] = "t"
|
||||
requests_mock.put("https://api.trello.com/1/cards/c1/checklistItem/ci1", json={"id": "ci1", "name": "Task", "state": "complete"})
|
||||
result = json.loads(trello_toggle_checklist_item(card_id="c1", item_id="ci1", checked=True))
|
||||
assert result["success"]
|
||||
|
||||
def test_trello_delete_checklist_item_tool(self, requests_mock: Any) -> None:
|
||||
from trello_plugin.tools import trello_delete_checklist_item
|
||||
os.environ["TRELLO_API_KEY"] = "k"
|
||||
os.environ["TRELLO_TOKEN"] = "t"
|
||||
requests_mock.delete("https://api.trello.com/1/cards/c1/checklistItems/ci1", status_code=200, json={})
|
||||
result = json.loads(trello_delete_checklist_item(card_id="c1", item_id="ci1"))
|
||||
assert result["success"]
|
||||
Reference in New Issue
Block a user