test: achieve 100% coverage for server.py and fix existing tests
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import pytest
|
||||
import requests
|
||||
from unittest.mock import MagicMock, patch
|
||||
from server import PlankaClient
|
||||
|
||||
@@ -22,7 +23,6 @@ def test_get_projects_nested(client):
|
||||
assert projects[0]["name"] == "Project 1"
|
||||
|
||||
def test_get_boards_included(client):
|
||||
# Test the 'included' structure we saw in the real API
|
||||
mock_response = {
|
||||
"included": {
|
||||
"boards": [
|
||||
@@ -49,10 +49,12 @@ def test_get_boards_empty(client):
|
||||
|
||||
def test_get_cards_nested(client):
|
||||
mock_response = {
|
||||
"items": [
|
||||
"included": {
|
||||
"cards": [
|
||||
{"id": "c1", "name": "Card 1"}
|
||||
]
|
||||
}
|
||||
}
|
||||
with patch("requests.get") as mock_get:
|
||||
mock_get.return_value.json.return_value = mock_response
|
||||
mock_get.return_value.status_code = 200
|
||||
@@ -60,3 +62,103 @@ def test_get_cards_nested(client):
|
||||
cards = client.get_cards("b1")
|
||||
assert len(cards) == 1
|
||||
assert cards[0]["name"] == "Card 1"
|
||||
|
||||
def test_get_cards_empty(client):
|
||||
mock_response = {}
|
||||
with patch("requests.get") as mock_get:
|
||||
mock_get.return_value.json.return_value = mock_response
|
||||
mock_get.return_value.status_code = 200
|
||||
|
||||
cards = client.get_cards("b1")
|
||||
assert cards == []
|
||||
|
||||
def test_get_board_lists_nested(client):
|
||||
mock_response = {
|
||||
"item": {
|
||||
"lists": [{"id": "l1", "name": "List 1"}]
|
||||
}
|
||||
}
|
||||
with patch("requests.get") as mock_get:
|
||||
mock_get.return_value.json.return_value = mock_response
|
||||
mock_get.return_value.status_code = 200
|
||||
|
||||
lists = client.get_board_lists("b1")
|
||||
assert len(lists) == 1
|
||||
|
||||
def test_get_boards_flat(client):
|
||||
mock_response = {
|
||||
"boards": [{"id": "b1", "name": "Board 1"}]
|
||||
}
|
||||
with patch("requests.get") as mock_get:
|
||||
mock_get.return_value.json.return_value = mock_response
|
||||
mock_get.return_value.status_code = 200
|
||||
|
||||
boards = client.get_boards("p1")
|
||||
assert len(boards) == 1
|
||||
|
||||
def test_get_board_lists_flat(client):
|
||||
mock_response = {
|
||||
"lists": [{"id": "l1", "name": "List 1"}]
|
||||
}
|
||||
with patch("requests.get") as mock_get:
|
||||
mock_get.return_value.json.return_value = mock_response
|
||||
mock_get.return_value.status_code = 200
|
||||
|
||||
lists = client.get_board_lists("b1")
|
||||
assert len(lists) == 1
|
||||
|
||||
def test_get_actions_flat(client):
|
||||
mock_response = [{"id": "a1"}]
|
||||
with patch("requests.get") as mock_get:
|
||||
mock_get.return_value.json.return_value = mock_response
|
||||
mock_get.return_value.status_code = 200
|
||||
|
||||
actions = client.get_actions("c1")
|
||||
assert len(actions) == 1
|
||||
|
||||
def test_get_json_decode_error(client):
|
||||
with patch("requests.get") as mock_get:
|
||||
mock_get.return_value.json.side_effect = requests.exceptions.JSONDecodeError("msg", "doc", 0)
|
||||
mock_get.return_value.status_code = 200
|
||||
mock_get.return_value.text = "not json"
|
||||
|
||||
res = client._get("endpoint")
|
||||
assert res is None
|
||||
|
||||
def test_get_empty_text(client):
|
||||
with patch("requests.get") as mock_get:
|
||||
mock_get.return_value.status_code = 200
|
||||
mock_get.return_value.text = ""
|
||||
|
||||
res = client._get("endpoint")
|
||||
assert res is None
|
||||
|
||||
def test_post_json_decode_error(client):
|
||||
with patch("requests.post") as mock_post:
|
||||
mock_post.return_value.json.side_effect = requests.exceptions.JSONDecodeError("msg", "doc", 0)
|
||||
mock_post.return_value.status_code = 200
|
||||
mock_post.return_value.text = "not json"
|
||||
|
||||
res = client._post("endpoint", {})
|
||||
assert res is None
|
||||
|
||||
def test_post_empty_text(client):
|
||||
with patch("requests.post") as mock_post:
|
||||
mock_post.return_value.status_code = 200
|
||||
mock_post.return_value.text = ""
|
||||
|
||||
res = client._post("endpoint", {})
|
||||
assert res is None
|
||||
|
||||
def test_get_boards_nested_item(client):
|
||||
mock_response = {
|
||||
"item": {
|
||||
"boards": [{"id": "b1", "name": "Board 1"}]
|
||||
}
|
||||
}
|
||||
with patch("requests.get") as mock_get:
|
||||
mock_get.return_value.json.return_value = mock_response
|
||||
mock_get.return_value.status_code = 200
|
||||
|
||||
boards = client.get_boards("p1")
|
||||
assert len(boards) == 1
|
||||
|
||||
99
test_server.py
Normal file
99
test_server.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import pytest
|
||||
import requests_mock
|
||||
from server import PlankaClient
|
||||
|
||||
@pytest.fixture
|
||||
def planka_client():
|
||||
return PlankaClient("https://planka.example.com", "test_token")
|
||||
|
||||
def test_get_projects(planka_client):
|
||||
with requests_mock.Mocker() as m:
|
||||
m.get("https://planka.example.com/api/projects", json=[
|
||||
{"id": "1", "name": "Project 1"},
|
||||
{"id": "2", "name": "Project 2"}
|
||||
])
|
||||
|
||||
projects = planka_client.get_projects()
|
||||
|
||||
assert len(projects) == 2
|
||||
assert projects[0]["name"] == "Project 1"
|
||||
assert projects[1]["id"] == "2"
|
||||
assert m.request_history[0].headers["Authorization"] == "Bearer test_token"
|
||||
|
||||
def test_get_boards(planka_client):
|
||||
with requests_mock.Mocker() as m:
|
||||
m.get("https://planka.example.com/api/projects/1", json={
|
||||
"included": {
|
||||
"boards": [{"id": "b1", "name": "Board 1"}]
|
||||
}
|
||||
})
|
||||
|
||||
boards = planka_client.get_boards("1")
|
||||
|
||||
assert len(boards) == 1
|
||||
assert boards[0]["name"] == "Board 1"
|
||||
|
||||
def test_get_cards(planka_client):
|
||||
with requests_mock.Mocker() as m:
|
||||
m.get("https://planka.example.com/api/boards/b1", json={
|
||||
"included": {
|
||||
"cards": [{"id": "c1", "name": "Card 1"}]
|
||||
}
|
||||
})
|
||||
|
||||
cards = planka_client.get_cards("b1")
|
||||
|
||||
assert len(cards) == 1
|
||||
assert cards[0]["name"] == "Card 1"
|
||||
|
||||
def test_create_card(planka_client):
|
||||
with requests_mock.Mocker() as m:
|
||||
m.post("https://planka.example.com/api/cards", json={
|
||||
"id": "cnew",
|
||||
"name": "New Card"
|
||||
})
|
||||
|
||||
card = planka_client.create_card("b1", "l1", "New Card", "Desc")
|
||||
|
||||
assert card["id"] == "cnew"
|
||||
assert m.request_history[0].json() == {
|
||||
"boardId": "b1",
|
||||
"listId": "l1",
|
||||
"name": "New Card",
|
||||
"description": "Desc"
|
||||
}
|
||||
|
||||
def test_get_board_lists(planka_client):
|
||||
with requests_mock.Mocker() as m:
|
||||
m.get("https://planka.example.com/api/boards/b1", json={
|
||||
"included": {
|
||||
"lists": [{"id": "l1", "name": "List 1"}]
|
||||
}
|
||||
})
|
||||
|
||||
lists = planka_client.get_board_lists("b1")
|
||||
|
||||
assert len(lists) == 1
|
||||
assert lists[0]["name"] == "List 1"
|
||||
|
||||
def test_get_actions(planka_client):
|
||||
with requests_mock.Mocker() as m:
|
||||
m.get("https://planka.example.com/api/cards/c1/actions", json={
|
||||
"items": [{"id": "a1", "type": "commentCard"}]
|
||||
})
|
||||
|
||||
actions = planka_client.get_actions("c1")
|
||||
|
||||
assert len(actions) == 1
|
||||
assert actions[0]["type"] == "commentCard"
|
||||
|
||||
def test_create_comment(planka_client):
|
||||
with requests_mock.Mocker() as m:
|
||||
m.post("https://planka.example.com/api/cards/c1/actions", json={
|
||||
"id": "a2"
|
||||
})
|
||||
|
||||
comment = planka_client.create_comment("c1", "Hello")
|
||||
|
||||
assert comment["id"] == "a2"
|
||||
assert m.request_history[0].json()["data"]["text"] == "Hello"
|
||||
174
test_tools.py
Normal file
174
test_tools.py
Normal file
@@ -0,0 +1,174 @@
|
||||
import pytest
|
||||
import requests
|
||||
from unittest.mock import patch, MagicMock
|
||||
import server
|
||||
import runpy
|
||||
|
||||
@pytest.fixture
|
||||
def mock_client():
|
||||
with patch("server.get_client") as mock:
|
||||
yield mock.return_value
|
||||
|
||||
def test_list_projects_tool(mock_client):
|
||||
mock_client.get_projects.return_value = [{"id": "1", "name": "P1"}]
|
||||
result = server.list_projects()
|
||||
assert "P1" in result
|
||||
assert "ID: 1" in result
|
||||
|
||||
def test_list_projects_empty_tool(mock_client):
|
||||
mock_client.get_projects.return_value = []
|
||||
result = server.list_projects()
|
||||
assert "No projects found" in result
|
||||
|
||||
def test_list_boards_tool(mock_client):
|
||||
mock_client.get_boards.return_value = [{"id": "b1", "name": "B1"}]
|
||||
result = server.list_boards("p1")
|
||||
assert "B1" in result
|
||||
assert "ID: b1" in result
|
||||
|
||||
def test_list_boards_empty_tool(mock_client):
|
||||
mock_client.get_boards.return_value = []
|
||||
result = server.list_boards("p1")
|
||||
assert "No boards found" in result
|
||||
|
||||
def test_list_board_columns_tool(mock_client):
|
||||
mock_client.get_board_lists.return_value = [{"id": "l1", "name": "L1"}]
|
||||
result = server.list_board_columns("b1")
|
||||
assert "L1" in result
|
||||
assert "ID: l1" in result
|
||||
|
||||
def test_list_board_columns_empty_tool(mock_client):
|
||||
mock_client.get_board_lists.return_value = []
|
||||
result = server.list_board_columns("b1")
|
||||
assert "No columns found" in result
|
||||
|
||||
def test_list_cards_tool(mock_client):
|
||||
mock_client.get_cards.return_value = [{"id": "c1", "name": "C1"}]
|
||||
result = server.list_cards("b1")
|
||||
assert "C1" in result
|
||||
assert "ID: c1" in result
|
||||
|
||||
def test_list_cards_empty_tool(mock_client):
|
||||
mock_client.get_cards.return_value = []
|
||||
result = server.list_cards("b1")
|
||||
assert "No cards found" in result
|
||||
|
||||
def test_create_card_tool(mock_client):
|
||||
mock_client.create_card.return_value = {"id": "c1", "name": "C1"}
|
||||
result = server.create_card("b1", "l1", "C1")
|
||||
assert "Created card: C1" in result
|
||||
|
||||
def test_list_comments_tool(mock_client):
|
||||
mock_client.get_actions.return_value = [
|
||||
{"type": "commentCard", "data": {"text": "Hello"}, "userId": "u1", "createdAt": "now"}
|
||||
]
|
||||
result = server.list_comments("c1")
|
||||
assert "Hello" in result
|
||||
|
||||
def test_list_comments_empty_tool(mock_client):
|
||||
mock_client.get_actions.return_value = []
|
||||
result = server.list_comments("c1")
|
||||
assert "No comments found" in result
|
||||
|
||||
def test_add_comment_tool(mock_client):
|
||||
result = server.add_comment("c1", "Hello")
|
||||
assert "Comment added" in result
|
||||
mock_client.create_comment.assert_called_with("c1", "Hello")
|
||||
|
||||
def test_check_planka_status_up():
|
||||
with patch("requests.get") as mock_get:
|
||||
mock_get.return_value.status_code = 200
|
||||
result = server.check_planka_status()
|
||||
assert "UP and reachable" in result
|
||||
|
||||
def test_check_planka_status_down():
|
||||
with patch("requests.get") as mock_get:
|
||||
mock_get.return_value.status_code = 500
|
||||
result = server.check_planka_status()
|
||||
assert "returned status 500" in result
|
||||
|
||||
def test_check_planka_status_error():
|
||||
with patch("requests.get") as mock_get:
|
||||
mock_get.side_effect = Exception("error")
|
||||
result = server.check_planka_status()
|
||||
assert "UNREACHABLE" in result
|
||||
|
||||
def test_safe_tool_call_timeout():
|
||||
def _fail():
|
||||
raise requests.exceptions.Timeout()
|
||||
result = server.safe_tool_call(_fail)
|
||||
assert "timed out" in result
|
||||
|
||||
def test_safe_tool_call_connection_error():
|
||||
def _fail():
|
||||
raise requests.exceptions.ConnectionError()
|
||||
result = server.safe_tool_call(_fail)
|
||||
assert "Could not connect" in result
|
||||
|
||||
def test_safe_tool_call_http_error():
|
||||
def _fail():
|
||||
resp = MagicMock()
|
||||
resp.status_code = 404
|
||||
raise requests.exceptions.HTTPError("404 Error", response=resp)
|
||||
result = server.safe_tool_call(_fail)
|
||||
assert "API returned an error" in result
|
||||
|
||||
def test_safe_tool_call_generic_error():
|
||||
def _fail():
|
||||
raise Exception("generic error")
|
||||
result = server.safe_tool_call(_fail)
|
||||
assert "unexpected error occurred" in result
|
||||
|
||||
def test_get_client_no_token():
|
||||
with patch.dict("os.environ", {"PLANKA_TOKEN": ""}):
|
||||
server.PLANKA_TOKEN = None
|
||||
server.client = None
|
||||
with pytest.raises(ValueError, match="PLANKA_TOKEN is required"):
|
||||
server.get_client()
|
||||
|
||||
def test_get_client_success():
|
||||
with patch.dict("os.environ", {"PLANKA_TOKEN": "some_token"}):
|
||||
server.PLANKA_TOKEN = "some_token"
|
||||
server.client = None
|
||||
c = server.get_client()
|
||||
assert c.token == "some_token"
|
||||
|
||||
def test_cli_list_projects():
|
||||
with patch("sys.argv", ["server.py", "list_projects"]), \
|
||||
patch("server.get_client") as mock_get_client, \
|
||||
patch("server.mcp.run"):
|
||||
mock_get_client.return_value.get_projects.return_value = [{"id": "1", "name": "P1"}]
|
||||
runpy.run_path("/root/.openclaw/workspace/planka-mcp/server.py", run_name="__main__")
|
||||
|
||||
def test_cli_list_boards():
|
||||
with patch("sys.argv", ["server.py", "list_boards", "p1"]), \
|
||||
patch("server.get_client") as mock_get_client, \
|
||||
patch("server.mcp.run"):
|
||||
mock_get_client.return_value.get_boards.return_value = [{"id": "b1", "name": "B1"}]
|
||||
runpy.run_path("/root/.openclaw/workspace/planka-mcp/server.py", run_name="__main__")
|
||||
|
||||
def test_cli_list_cards():
|
||||
with patch("sys.argv", ["server.py", "list_cards", "b1"]), \
|
||||
patch("server.get_client") as mock_get_client, \
|
||||
patch("server.mcp.run"):
|
||||
mock_get_client.return_value.get_cards.return_value = [{"id": "c1", "name": "C1"}]
|
||||
runpy.run_path("/root/.openclaw/workspace/planka-mcp/server.py", run_name="__main__")
|
||||
|
||||
def test_cli_list_columns():
|
||||
with patch("sys.argv", ["server.py", "list_columns", "b1"]), \
|
||||
patch("server.get_client") as mock_get_client, \
|
||||
patch("server.mcp.run"):
|
||||
mock_get_client.return_value.get_board_lists.return_value = [{"id": "l1", "name": "L1"}]
|
||||
runpy.run_path("/root/.openclaw/workspace/planka-mcp/server.py", run_name="__main__")
|
||||
|
||||
def test_cli_status_main():
|
||||
with patch("sys.argv", ["server.py", "status"]), \
|
||||
patch("requests.get") as mock_req_get, \
|
||||
patch("server.mcp.run"):
|
||||
mock_req_get.return_value.status_code = 200
|
||||
runpy.run_path("/root/.openclaw/workspace/planka-mcp/server.py", run_name="__main__")
|
||||
|
||||
def test_cli_run_mcp():
|
||||
with patch("sys.argv", ["server.py"]), \
|
||||
patch("mcp.server.fastmcp.FastMCP.run") as mock_run:
|
||||
runpy.run_path("/root/.openclaw/workspace/planka-mcp/server.py", run_name="__main__")
|
||||
Reference in New Issue
Block a user