Files
cordra-mcp/tests/test_client.py
daniel cc06e1eace feat: add schema listing functionality
- Add find method to CordraClient for querying objects
- Implement cordra://schemas resource for listing available type schemas
- Add comprehensive unit tests for both client find method and server schema listing
- Handle CordraPy response format with results array extraction
- Support schema name extraction from returned objects
2025-06-29 14:28:08 +02:00

232 lines
7.5 KiB
Python

"""Unit tests for the Cordra client."""
from unittest.mock import patch
import pytest
from mcp_cordra.client import (
CordraClient,
CordraClientError,
CordraNotFoundError,
DigitalObject,
)
from mcp_cordra.config import CordraConfig
@pytest.fixture
def config():
"""Create a test configuration."""
return CordraConfig(
cordra_url="https://test.example.com",
username="testuser",
password="testpass",
verify_ssl=False,
)
@pytest.fixture
def client(config):
"""Create a test client."""
return CordraClient(config)
@pytest.fixture
def mock_cordra_object():
"""Create a mock CordraObject response (dictionary)."""
return {
"type": "TestType",
"content": {"title": "Test Object", "description": "A test object"},
"metadata": {"created": "2023-01-01", "modified": "2023-01-02"},
"acl": {"read": ["public"], "write": ["admin"]},
"payloads": [
{
"name": "file1.txt",
"filename": "file1.txt",
"size": 1024,
"mediaType": "text/plain"
},
{
"name": "file2.pdf",
"filename": "file2.pdf",
"size": 2048,
"mediaType": "application/pdf"
}
]
}
class TestDigitalObject:
"""Test the DigitalObject model."""
def test_digital_object_creation(self):
"""Test creating a DigitalObject."""
obj = DigitalObject(
id="test/123",
type="TestType",
content={"title": "Test"},
metadata={"created": "2023-01-01"},
acl={"read": ["public"]},
payloads=[
{
"name": "file1.txt",
"mediaType": "text/plain",
"size": 1024,
"filename": "file1.txt"
}
],
)
assert obj.id == "test/123"
assert obj.type == "TestType"
assert obj.content == {"title": "Test"}
assert obj.metadata == {"created": "2023-01-01"}
assert obj.acl == {"read": ["public"]}
assert obj.payloads and len(obj.payloads) == 1
payload = obj.payloads[0]
assert payload["name"] == "file1.txt"
assert payload["mediaType"] == "text/plain"
assert payload["size"] == 1024
assert payload["filename"] == "file1.txt"
def test_digital_object_optional_fields(self):
"""Test DigitalObject with only required fields."""
obj = DigitalObject(
id="test/123",
type="TestType",
content={"title": "Test"}
)
assert obj.id == "test/123"
assert obj.type == "TestType"
assert obj.content == {"title": "Test"}
assert obj.metadata is None
assert obj.acl is None
assert obj.payloads is None
class TestCordraClient:
"""Test the CordraClient class."""
def test_client_initialization(self, config):
"""Test client initialization."""
client = CordraClient(config)
assert client.config == config
@patch('mcp_cordra.client.cordra.CordraObject.read')
async def test_get_object_success(self, mock_read, client, mock_cordra_object):
"""Test successful object retrieval."""
mock_read.return_value = mock_cordra_object
result = await client.get_object("test/123")
assert isinstance(result, DigitalObject)
assert result.id == "test/123"
assert result.type == "TestType"
assert result.content == {"title": "Test Object", "description": "A test object"}
assert result.metadata == {"created": "2023-01-01", "modified": "2023-01-02"}
assert result.acl == {"read": ["public"], "write": ["admin"]}
assert result.payloads and len(result.payloads) == 2
mock_read.assert_called_once_with(
host="https://test.example.com",
obj_id="test/123",
username="testuser",
password="testpass",
verify=False,
full=True
)
@patch('mcp_cordra.client.cordra.CordraObject.read')
async def test_get_object_not_found(self, mock_read, client):
"""Test object not found exception."""
mock_read.side_effect = Exception("Object not found")
with pytest.raises(CordraNotFoundError) as exc_info:
await client.get_object("test/nonexistent")
assert "Object not found: test/nonexistent" in str(exc_info.value)
@patch('mcp_cordra.client.cordra.CordraObject.read')
async def test_get_object_general_error(self, mock_read, client):
"""Test general error handling."""
mock_read.side_effect = Exception("Connection failed")
with pytest.raises(CordraClientError) as exc_info:
await client.get_object("test/123")
assert "Failed to retrieve object test/123" in str(exc_info.value)
@patch('mcp_cordra.client.cordra.CordraObject.find')
async def test_find_success(self, mock_find, client):
"""Test successful find operation."""
mock_response = {
"results": [
{"name": "User", "identifier": "test/user-schema"},
{"name": "Project", "identifier": "test/project-schema"},
{"name": "Document", "identifier": "test/doc-schema"}
],
"size": 3
}
mock_find.return_value = mock_response
result = await client.find("type:Schema")
assert len(result) == 3
assert result[0]["name"] == "User"
assert result[1]["name"] == "Project"
assert result[2]["name"] == "Document"
mock_find.assert_called_once_with(
client.config.cordra_url,
"type:Schema"
)
@patch('mcp_cordra.client.cordra.CordraObject.find')
async def test_find_empty_results(self, mock_find, client):
"""Test find with empty results."""
mock_response = {"results": [], "size": 0}
mock_find.return_value = mock_response
result = await client.find("type:NonExistent")
assert result == []
mock_find.assert_called_once_with(
client.config.cordra_url,
"type:NonExistent"
)
@patch('mcp_cordra.client.cordra.CordraObject.find')
async def test_find_no_results_key(self, mock_find, client):
"""Test find with response missing results key."""
mock_response = {"size": 0} # No results key
mock_find.return_value = mock_response
result = await client.find("type:Schema")
assert result == []
@patch('mcp_cordra.client.cordra.CordraObject.find')
async def test_find_error(self, mock_find, client):
"""Test find error handling."""
mock_find.side_effect = Exception("Search failed")
with pytest.raises(CordraClientError) as exc_info:
await client.find("invalid:query")
assert "Failed to search with query 'invalid:query'" in str(exc_info.value)
assert "Search failed" in str(exc_info.value)
class TestCordraConfig:
"""Test the CordraConfig class."""
def test_default_config(self):
"""Test default configuration values."""
config = CordraConfig()
assert config.cordra_url == "https://localhost:8443"
assert config.username is None
assert config.password is None
assert config.max_search_results == 1000
assert config.verify_ssl is True
assert config.timeout == 30