Add 3 new Coolify plugin handlers (37 new tools, 67 total): - projects.py: 8 tools (CRUD projects + environments) - databases.py: 16 tools (CRUD, lifecycle, 6 DB types, backups) - services.py: 13 tools (CRUD, lifecycle, env vars) 734 tests passing. Total platform tools: 633. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
215 lines
7.6 KiB
Python
215 lines
7.6 KiB
Python
"""
|
|
Tests for Coolify Projects & Environments Handler
|
|
|
|
Unit tests with mocked HTTP responses.
|
|
"""
|
|
|
|
import json
|
|
from unittest.mock import AsyncMock
|
|
|
|
import pytest
|
|
|
|
from plugins.coolify.client import CoolifyClient
|
|
from plugins.coolify.handlers import projects
|
|
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
"""Create a CoolifyClient instance for testing."""
|
|
return CoolifyClient(site_url="https://coolify.test.com", token="test-token-123")
|
|
|
|
|
|
class TestProjectToolSpecs:
|
|
"""Tests for project tool specifications."""
|
|
|
|
def test_project_tool_count(self):
|
|
"""Test project tool specification count."""
|
|
specs = projects.get_tool_specifications()
|
|
assert len(specs) == 8
|
|
|
|
def test_tool_specs_have_required_fields(self):
|
|
"""Test all specs have required fields."""
|
|
for spec in projects.get_tool_specifications():
|
|
assert "name" in spec
|
|
assert "method_name" in spec
|
|
assert "description" in spec
|
|
assert "schema" in spec
|
|
assert "scope" in spec
|
|
assert spec["scope"] in ("read", "write", "admin")
|
|
|
|
def test_tool_names_unique(self):
|
|
"""Test that all tool names are unique."""
|
|
specs = projects.get_tool_specifications()
|
|
names = [s["name"] for s in specs]
|
|
assert len(names) == len(set(names))
|
|
|
|
def test_scope_distribution(self):
|
|
"""Test correct scope assignments."""
|
|
specs = projects.get_tool_specifications()
|
|
by_scope = {}
|
|
for s in specs:
|
|
by_scope.setdefault(s["scope"], []).append(s["name"])
|
|
assert "list_projects" in by_scope["read"]
|
|
assert "get_project" in by_scope["read"]
|
|
assert "create_project" in by_scope["write"]
|
|
assert "delete_project" in by_scope["admin"]
|
|
|
|
|
|
class TestProjectHandlers:
|
|
"""Tests for project handler functions."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_projects(self, client):
|
|
"""Test list_projects handler."""
|
|
mock_projects = [
|
|
{"uuid": "proj-1", "name": "mcphub", "description": "MCP Hub project"},
|
|
{"uuid": "proj-2", "name": "blog", "description": "Blog project"},
|
|
]
|
|
client.list_projects = AsyncMock(return_value=mock_projects)
|
|
|
|
result = await projects.list_projects(client)
|
|
data = json.loads(result)
|
|
|
|
assert data["success"] is True
|
|
assert data["count"] == 2
|
|
assert len(data["projects"]) == 2
|
|
client.list_projects.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_project(self, client):
|
|
"""Test get_project handler."""
|
|
mock_project = {
|
|
"uuid": "proj-1",
|
|
"name": "mcphub",
|
|
"description": "MCP Hub project",
|
|
"environments": [{"name": "production"}],
|
|
}
|
|
client.get_project = AsyncMock(return_value=mock_project)
|
|
|
|
result = await projects.get_project(client, uuid="proj-1")
|
|
data = json.loads(result)
|
|
|
|
assert data["success"] is True
|
|
assert data["project"]["name"] == "mcphub"
|
|
client.get_project.assert_called_once_with("proj-1")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_project(self, client):
|
|
"""Test create_project handler."""
|
|
mock_response = {"uuid": "proj-new", "name": "new-project"}
|
|
client.create_project = AsyncMock(return_value=mock_response)
|
|
|
|
result = await projects.create_project(
|
|
client, name="new-project", description="A test project"
|
|
)
|
|
data = json.loads(result)
|
|
|
|
assert data["success"] is True
|
|
assert "created" in data["message"].lower()
|
|
client.create_project.assert_called_once_with(
|
|
{"name": "new-project", "description": "A test project"}
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_project_no_description(self, client):
|
|
"""Test create_project without description."""
|
|
mock_response = {"uuid": "proj-new"}
|
|
client.create_project = AsyncMock(return_value=mock_response)
|
|
|
|
result = await projects.create_project(client, name="minimal")
|
|
data = json.loads(result)
|
|
|
|
assert data["success"] is True
|
|
client.create_project.assert_called_once_with({"name": "minimal"})
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_project(self, client):
|
|
"""Test update_project handler."""
|
|
mock_response = {"uuid": "proj-1"}
|
|
client.update_project = AsyncMock(return_value=mock_response)
|
|
|
|
result = await projects.update_project(client, uuid="proj-1", name="renamed")
|
|
data = json.loads(result)
|
|
|
|
assert data["success"] is True
|
|
assert "updated" in data["message"].lower()
|
|
client.update_project.assert_called_once_with("proj-1", {"name": "renamed"})
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_project(self, client):
|
|
"""Test delete_project handler."""
|
|
mock_response = {"message": "Project deleted."}
|
|
client.delete_project = AsyncMock(return_value=mock_response)
|
|
|
|
result = await projects.delete_project(client, uuid="proj-1")
|
|
data = json.loads(result)
|
|
|
|
assert data["success"] is True
|
|
assert "deleted" in data["message"].lower()
|
|
client.delete_project.assert_called_once_with("proj-1")
|
|
|
|
|
|
class TestEnvironmentHandlers:
|
|
"""Tests for environment handler functions."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_environments(self, client):
|
|
"""Test list_environments handler."""
|
|
mock_envs = [
|
|
{"name": "production", "id": 1},
|
|
{"name": "staging", "id": 2},
|
|
]
|
|
client.list_environments = AsyncMock(return_value=mock_envs)
|
|
|
|
result = await projects.list_environments(client, project_uuid="proj-1")
|
|
data = json.loads(result)
|
|
|
|
assert data["success"] is True
|
|
assert data["count"] == 2
|
|
assert len(data["environments"]) == 2
|
|
client.list_environments.assert_called_once_with("proj-1")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_environment(self, client):
|
|
"""Test get_environment handler."""
|
|
mock_env = {"name": "production", "id": 1, "project_id": 5}
|
|
client.get_environment = AsyncMock(return_value=mock_env)
|
|
|
|
result = await projects.get_environment(
|
|
client, project_uuid="proj-1", environment_name="production"
|
|
)
|
|
data = json.loads(result)
|
|
|
|
assert data["success"] is True
|
|
assert data["environment"]["name"] == "production"
|
|
client.get_environment.assert_called_once_with("proj-1", "production")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_environment(self, client):
|
|
"""Test create_environment handler."""
|
|
mock_response = {"name": "staging", "id": 3}
|
|
client.create_environment = AsyncMock(return_value=mock_response)
|
|
|
|
result = await projects.create_environment(
|
|
client, project_uuid="proj-1", name="staging", description="Staging env"
|
|
)
|
|
data = json.loads(result)
|
|
|
|
assert data["success"] is True
|
|
assert "staging" in data["message"]
|
|
client.create_environment.assert_called_once_with(
|
|
"proj-1", {"name": "staging", "description": "Staging env"}
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_environment_no_description(self, client):
|
|
"""Test create_environment without description."""
|
|
mock_response = {"name": "test"}
|
|
client.create_environment = AsyncMock(return_value=mock_response)
|
|
|
|
result = await projects.create_environment(client, project_uuid="proj-1", name="test")
|
|
data = json.loads(result)
|
|
|
|
assert data["success"] is True
|
|
client.create_environment.assert_called_once_with("proj-1", {"name": "test"})
|