mirror of
https://github.com/dnlbauer/cordra-mcp.git
synced 2026-09-10 21:55:30 +00:00
fix: update tests for module rename from mcp_cordra to cordra_mcp
- Updated all import statements in test files - Added types-requests dependency for mypy type checking - Fixed code formatting with ruff
This commit is contained in:
@@ -60,4 +60,5 @@ dev = [
|
|||||||
"mypy>=1.16.1",
|
"mypy>=1.16.1",
|
||||||
"pytest-asyncio>=1.0.0",
|
"pytest-asyncio>=1.0.0",
|
||||||
"ruff>=0.12.1",
|
"ruff>=0.12.1",
|
||||||
|
"types-requests>=2.32.4.20250611",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Cordra client wrapper using HTTP requests."""
|
"""Cordra client wrapper using HTTP requests."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any, cast
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
@@ -19,21 +19,26 @@ class DigitalObject(BaseModel):
|
|||||||
content: dict[str, Any] = Field(description="Object content as JSON")
|
content: dict[str, Any] = Field(description="Object content as JSON")
|
||||||
metadata: dict[str, Any] | None = Field(default=None, description="Object metadata")
|
metadata: dict[str, Any] | None = Field(default=None, description="Object metadata")
|
||||||
acl: dict[str, Any] | None = Field(default=None, description="Access control list")
|
acl: dict[str, Any] | None = Field(default=None, description="Access control list")
|
||||||
payloads: list[dict[str, Any]] | None = Field(default=None, description="List of payloads")
|
payloads: list[dict[str, Any]] | None = Field(
|
||||||
|
default=None, description="List of payloads"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CordraClientError(Exception):
|
class CordraClientError(Exception):
|
||||||
"""Base exception for Cordra client errors."""
|
"""Base exception for Cordra client errors."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CordraNotFoundError(CordraClientError):
|
class CordraNotFoundError(CordraClientError):
|
||||||
"""Exception raised when an object is not found."""
|
"""Exception raised when an object is not found."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CordraAuthenticationError(CordraClientError):
|
class CordraAuthenticationError(CordraClientError):
|
||||||
"""Exception raised for authentication/authorization failures."""
|
"""Exception raised for authentication/authorization failures."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -54,7 +59,9 @@ class CordraClient:
|
|||||||
if config.username and config.password:
|
if config.username and config.password:
|
||||||
self.session.auth = (config.username, config.password)
|
self.session.auth = (config.username, config.password)
|
||||||
elif config.username or config.password:
|
elif config.username or config.password:
|
||||||
logger.warning("Only username or password provided, not both. Authentication may fail.")
|
logger.warning(
|
||||||
|
"Only username or password provided, not both. Authentication may fail."
|
||||||
|
)
|
||||||
|
|
||||||
def _handle_http_error(self, response: requests.Response, context: str) -> None:
|
def _handle_http_error(self, response: requests.Response, context: str) -> None:
|
||||||
"""Handle HTTP errors and raise appropriate exceptions.
|
"""Handle HTTP errors and raise appropriate exceptions.
|
||||||
@@ -72,7 +79,9 @@ class CordraClient:
|
|||||||
if status_code == 404:
|
if status_code == 404:
|
||||||
raise CordraNotFoundError(f"{context}: Resource not found")
|
raise CordraNotFoundError(f"{context}: Resource not found")
|
||||||
elif status_code in (401, 403):
|
elif status_code in (401, 403):
|
||||||
raise CordraAuthenticationError(f"{context}: Authentication failed (HTTP {status_code})")
|
raise CordraAuthenticationError(
|
||||||
|
f"{context}: Authentication failed (HTTP {status_code})"
|
||||||
|
)
|
||||||
elif status_code >= 500:
|
elif status_code >= 500:
|
||||||
raise CordraClientError(f"{context}: Server error (HTTP {status_code})")
|
raise CordraClientError(f"{context}: Server error (HTTP {status_code})")
|
||||||
else:
|
else:
|
||||||
@@ -100,21 +109,25 @@ class CordraClient:
|
|||||||
response = self.session.get(url, params=params, timeout=self.config.timeout)
|
response = self.session.get(url, params=params, timeout=self.config.timeout)
|
||||||
|
|
||||||
if not response.ok:
|
if not response.ok:
|
||||||
self._handle_http_error(response, f"Failed to retrieve object {object_id}")
|
self._handle_http_error(
|
||||||
|
response, f"Failed to retrieve object {object_id}"
|
||||||
|
)
|
||||||
|
|
||||||
cordra_obj = response.json()
|
cordra_obj = response.json()
|
||||||
|
|
||||||
return DigitalObject(
|
return DigitalObject(
|
||||||
id=object_id,
|
id=object_id,
|
||||||
type=cordra_obj.get('type', ''),
|
type=cordra_obj.get("type", ""),
|
||||||
content=cordra_obj.get('content', cordra_obj),
|
content=cordra_obj.get("content", cordra_obj),
|
||||||
metadata=cordra_obj.get('metadata'),
|
metadata=cordra_obj.get("metadata"),
|
||||||
acl=cordra_obj.get('acl'),
|
acl=cordra_obj.get("acl"),
|
||||||
payloads=cordra_obj.get('payloads'),
|
payloads=cordra_obj.get("payloads"),
|
||||||
)
|
)
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
raise CordraClientError(f"Failed to retrieve object {object_id}: {e}") from e
|
raise CordraClientError(
|
||||||
|
f"Failed to retrieve object {object_id}: {e}"
|
||||||
|
) from e
|
||||||
|
|
||||||
async def find(self, query: str) -> list[dict[str, Any]]:
|
async def find(self, query: str) -> list[dict[str, Any]]:
|
||||||
"""Find objects using a Cordra query.
|
"""Find objects using a Cordra query.
|
||||||
@@ -137,18 +150,22 @@ class CordraClient:
|
|||||||
response = self.session.get(url, params=params, timeout=self.config.timeout)
|
response = self.session.get(url, params=params, timeout=self.config.timeout)
|
||||||
|
|
||||||
if not response.ok:
|
if not response.ok:
|
||||||
self._handle_http_error(response, f"Failed to search with query '{query}'")
|
self._handle_http_error(
|
||||||
|
response, f"Failed to search with query '{query}'"
|
||||||
|
)
|
||||||
|
|
||||||
search_result = response.json()
|
search_result = response.json()
|
||||||
|
|
||||||
# Extract the results array from the response
|
# Extract the results array from the response
|
||||||
if isinstance(search_result, dict) and 'results' in search_result:
|
if isinstance(search_result, dict) and "results" in search_result:
|
||||||
return search_result['results']
|
return search_result["results"] # type: ignore
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
raise CordraClientError(f"Failed to search with query '{query}': {e}") from e
|
raise CordraClientError(
|
||||||
|
f"Failed to search with query '{query}': {e}"
|
||||||
|
) from e
|
||||||
|
|
||||||
async def get_schema(self, schema_name: str) -> DigitalObject:
|
async def get_schema(self, schema_name: str) -> DigitalObject:
|
||||||
"""Retrieve a schema definition by its name.
|
"""Retrieve a schema definition by its name.
|
||||||
@@ -177,9 +194,11 @@ class CordraClient:
|
|||||||
schema_data = schemas[0]
|
schema_data = schemas[0]
|
||||||
|
|
||||||
# Get the full schema object using its ID
|
# Get the full schema object using its ID
|
||||||
return await self.get_object(schema_data['id'])
|
return await self.get_object(schema_data["id"])
|
||||||
|
|
||||||
except (CordraNotFoundError, CordraAuthenticationError):
|
except (CordraNotFoundError, CordraAuthenticationError):
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise CordraClientError(f"Failed to retrieve schema '{schema_name}': {e}") from e
|
raise CordraClientError(
|
||||||
|
f"Failed to retrieve schema '{schema_name}': {e}"
|
||||||
|
) from e
|
||||||
|
|||||||
@@ -3,13 +3,14 @@
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from mcp_cordra.client import (
|
|
||||||
|
from cordra_mcp.client import (
|
||||||
CordraClient,
|
CordraClient,
|
||||||
CordraClientError,
|
CordraClientError,
|
||||||
CordraNotFoundError,
|
CordraNotFoundError,
|
||||||
DigitalObject,
|
DigitalObject,
|
||||||
)
|
)
|
||||||
from mcp_cordra.config import CordraConfig
|
from cordra_mcp.config import CordraConfig
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -42,15 +43,15 @@ def mock_cordra_object():
|
|||||||
"name": "file1.txt",
|
"name": "file1.txt",
|
||||||
"filename": "file1.txt",
|
"filename": "file1.txt",
|
||||||
"size": 1024,
|
"size": 1024,
|
||||||
"mediaType": "text/plain"
|
"mediaType": "text/plain",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "file2.pdf",
|
"name": "file2.pdf",
|
||||||
"filename": "file2.pdf",
|
"filename": "file2.pdf",
|
||||||
"size": 2048,
|
"size": 2048,
|
||||||
"mediaType": "application/pdf"
|
"mediaType": "application/pdf",
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -70,7 +71,7 @@ class TestDigitalObject:
|
|||||||
"name": "file1.txt",
|
"name": "file1.txt",
|
||||||
"mediaType": "text/plain",
|
"mediaType": "text/plain",
|
||||||
"size": 1024,
|
"size": 1024,
|
||||||
"filename": "file1.txt"
|
"filename": "file1.txt",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -89,11 +90,7 @@ class TestDigitalObject:
|
|||||||
|
|
||||||
def test_digital_object_optional_fields(self):
|
def test_digital_object_optional_fields(self):
|
||||||
"""Test DigitalObject with only required fields."""
|
"""Test DigitalObject with only required fields."""
|
||||||
obj = DigitalObject(
|
obj = DigitalObject(id="test/123", type="TestType", content={"title": "Test"})
|
||||||
id="test/123",
|
|
||||||
type="TestType",
|
|
||||||
content={"title": "Test"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert obj.id == "test/123"
|
assert obj.id == "test/123"
|
||||||
assert obj.type == "TestType"
|
assert obj.type == "TestType"
|
||||||
@@ -111,7 +108,7 @@ class TestCordraClient:
|
|||||||
client = CordraClient(config)
|
client = CordraClient(config)
|
||||||
assert client.config == config
|
assert client.config == config
|
||||||
|
|
||||||
@patch('mcp_cordra.client.requests.Session.get')
|
@patch("cordra_mcp.client.requests.Session.get")
|
||||||
async def test_get_object_success(self, mock_get, client, mock_cordra_object):
|
async def test_get_object_success(self, mock_get, client, mock_cordra_object):
|
||||||
"""Test successful object retrieval."""
|
"""Test successful object retrieval."""
|
||||||
mock_response = mock_get.return_value
|
mock_response = mock_get.return_value
|
||||||
@@ -124,7 +121,10 @@ class TestCordraClient:
|
|||||||
assert isinstance(result, DigitalObject)
|
assert isinstance(result, DigitalObject)
|
||||||
assert result.id == "test/123"
|
assert result.id == "test/123"
|
||||||
assert result.type == "TestType"
|
assert result.type == "TestType"
|
||||||
assert result.content == {"title": "Test Object", "description": "A test object"}
|
assert result.content == {
|
||||||
|
"title": "Test Object",
|
||||||
|
"description": "A test object",
|
||||||
|
}
|
||||||
assert result.metadata == {"created": "2023-01-01", "modified": "2023-01-02"}
|
assert result.metadata == {"created": "2023-01-01", "modified": "2023-01-02"}
|
||||||
assert result.acl == {"read": ["public"], "write": ["admin"]}
|
assert result.acl == {"read": ["public"], "write": ["admin"]}
|
||||||
assert result.payloads and len(result.payloads) == 2
|
assert result.payloads and len(result.payloads) == 2
|
||||||
@@ -132,10 +132,10 @@ class TestCordraClient:
|
|||||||
mock_get.assert_called_once_with(
|
mock_get.assert_called_once_with(
|
||||||
"https://test.example.com/objects/test/123",
|
"https://test.example.com/objects/test/123",
|
||||||
params={"full": "true"},
|
params={"full": "true"},
|
||||||
timeout=30
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
@patch('mcp_cordra.client.requests.Session.get')
|
@patch("cordra_mcp.client.requests.Session.get")
|
||||||
async def test_get_object_not_found(self, mock_get, client):
|
async def test_get_object_not_found(self, mock_get, client):
|
||||||
"""Test object not found exception."""
|
"""Test object not found exception."""
|
||||||
mock_response = mock_get.return_value
|
mock_response = mock_get.return_value
|
||||||
@@ -147,10 +147,11 @@ class TestCordraClient:
|
|||||||
|
|
||||||
assert "Resource not found" in str(exc_info.value)
|
assert "Resource not found" in str(exc_info.value)
|
||||||
|
|
||||||
@patch('mcp_cordra.client.requests.Session.get')
|
@patch("cordra_mcp.client.requests.Session.get")
|
||||||
async def test_get_object_general_error(self, mock_get, client):
|
async def test_get_object_general_error(self, mock_get, client):
|
||||||
"""Test general error handling."""
|
"""Test general error handling."""
|
||||||
from requests import RequestException
|
from requests import RequestException
|
||||||
|
|
||||||
mock_get.side_effect = RequestException("Connection failed")
|
mock_get.side_effect = RequestException("Connection failed")
|
||||||
|
|
||||||
with pytest.raises(CordraClientError) as exc_info:
|
with pytest.raises(CordraClientError) as exc_info:
|
||||||
@@ -158,16 +159,16 @@ class TestCordraClient:
|
|||||||
|
|
||||||
assert "Failed to retrieve object test/123" in str(exc_info.value)
|
assert "Failed to retrieve object test/123" in str(exc_info.value)
|
||||||
|
|
||||||
@patch('mcp_cordra.client.requests.Session.get')
|
@patch("cordra_mcp.client.requests.Session.get")
|
||||||
async def test_find_success(self, mock_get, client):
|
async def test_find_success(self, mock_get, client):
|
||||||
"""Test successful find operation."""
|
"""Test successful find operation."""
|
||||||
mock_response_data = {
|
mock_response_data = {
|
||||||
"results": [
|
"results": [
|
||||||
{"name": "User", "identifier": "test/user-schema"},
|
{"name": "User", "identifier": "test/user-schema"},
|
||||||
{"name": "Project", "identifier": "test/project-schema"},
|
{"name": "Project", "identifier": "test/project-schema"},
|
||||||
{"name": "Document", "identifier": "test/doc-schema"}
|
{"name": "Document", "identifier": "test/doc-schema"},
|
||||||
],
|
],
|
||||||
"size": 3
|
"size": 3,
|
||||||
}
|
}
|
||||||
mock_response = mock_get.return_value
|
mock_response = mock_get.return_value
|
||||||
mock_response.status_code = 200
|
mock_response.status_code = 200
|
||||||
@@ -184,10 +185,10 @@ class TestCordraClient:
|
|||||||
mock_get.assert_called_once_with(
|
mock_get.assert_called_once_with(
|
||||||
"https://test.example.com/search",
|
"https://test.example.com/search",
|
||||||
params={"query": "type:Schema"},
|
params={"query": "type:Schema"},
|
||||||
timeout=30
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
@patch('mcp_cordra.client.requests.Session.get')
|
@patch("cordra_mcp.client.requests.Session.get")
|
||||||
async def test_find_empty_results(self, mock_get, client):
|
async def test_find_empty_results(self, mock_get, client):
|
||||||
"""Test find with empty results."""
|
"""Test find with empty results."""
|
||||||
mock_response_data = {"results": [], "size": 0}
|
mock_response_data = {"results": [], "size": 0}
|
||||||
@@ -202,10 +203,10 @@ class TestCordraClient:
|
|||||||
mock_get.assert_called_once_with(
|
mock_get.assert_called_once_with(
|
||||||
"https://test.example.com/search",
|
"https://test.example.com/search",
|
||||||
params={"query": "type:NonExistent"},
|
params={"query": "type:NonExistent"},
|
||||||
timeout=30
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
@patch('mcp_cordra.client.requests.Session.get')
|
@patch("cordra_mcp.client.requests.Session.get")
|
||||||
async def test_find_no_results_key(self, mock_get, client):
|
async def test_find_no_results_key(self, mock_get, client):
|
||||||
"""Test find with response missing results key."""
|
"""Test find with response missing results key."""
|
||||||
mock_response_data = {"size": 0} # No results key
|
mock_response_data = {"size": 0} # No results key
|
||||||
@@ -218,10 +219,11 @@ class TestCordraClient:
|
|||||||
|
|
||||||
assert result == []
|
assert result == []
|
||||||
|
|
||||||
@patch('mcp_cordra.client.requests.Session.get')
|
@patch("cordra_mcp.client.requests.Session.get")
|
||||||
async def test_find_error(self, mock_get, client):
|
async def test_find_error(self, mock_get, client):
|
||||||
"""Test find error handling."""
|
"""Test find error handling."""
|
||||||
from requests import RequestException
|
from requests import RequestException
|
||||||
|
|
||||||
mock_get.side_effect = RequestException("Search failed")
|
mock_get.side_effect = RequestException("Search failed")
|
||||||
|
|
||||||
with pytest.raises(CordraClientError) as exc_info:
|
with pytest.raises(CordraClientError) as exc_info:
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import json
|
|||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from mcp_cordra.client import CordraClientError, CordraNotFoundError, DigitalObject
|
|
||||||
from mcp_cordra.server import get_cordra_object
|
from cordra_mcp.client import CordraClientError, CordraNotFoundError, DigitalObject
|
||||||
|
from cordra_mcp.server import get_cordra_object
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -35,7 +36,7 @@ def sample_digital_object():
|
|||||||
class TestGetCordraObject:
|
class TestGetCordraObject:
|
||||||
"""Test the get_cordra_object resource handler."""
|
"""Test the get_cordra_object resource handler."""
|
||||||
|
|
||||||
@patch('mcp_cordra.server.cordra_client')
|
@patch('cordra_mcp.server.cordra_client')
|
||||||
async def test_get_object_success(self, mock_client, sample_digital_object):
|
async def test_get_object_success(self, mock_client, sample_digital_object):
|
||||||
"""Test successful object retrieval."""
|
"""Test successful object retrieval."""
|
||||||
mock_client.get_object = AsyncMock(return_value=sample_digital_object)
|
mock_client.get_object = AsyncMock(return_value=sample_digital_object)
|
||||||
@@ -55,7 +56,7 @@ class TestGetCordraObject:
|
|||||||
# Verify the client was called with the correct object ID
|
# Verify the client was called with the correct object ID
|
||||||
mock_client.get_object.assert_called_once_with("people/john-doe-123")
|
mock_client.get_object.assert_called_once_with("people/john-doe-123")
|
||||||
|
|
||||||
@patch('mcp_cordra.server.cordra_client')
|
@patch('cordra_mcp.server.cordra_client')
|
||||||
async def test_get_object_not_found(self, mock_client):
|
async def test_get_object_not_found(self, mock_client):
|
||||||
"""Test object not found exception."""
|
"""Test object not found exception."""
|
||||||
mock_client.get_object = AsyncMock(
|
mock_client.get_object = AsyncMock(
|
||||||
@@ -68,7 +69,7 @@ class TestGetCordraObject:
|
|||||||
assert "Object not found: people/nonexistent" in str(exc_info.value)
|
assert "Object not found: people/nonexistent" in str(exc_info.value)
|
||||||
mock_client.get_object.assert_called_once_with("people/nonexistent")
|
mock_client.get_object.assert_called_once_with("people/nonexistent")
|
||||||
|
|
||||||
@patch('mcp_cordra.server.cordra_client')
|
@patch('cordra_mcp.server.cordra_client')
|
||||||
async def test_get_object_client_error(self, mock_client):
|
async def test_get_object_client_error(self, mock_client):
|
||||||
"""Test general client error handling."""
|
"""Test general client error handling."""
|
||||||
mock_client.get_object = AsyncMock(
|
mock_client.get_object = AsyncMock(
|
||||||
@@ -82,7 +83,7 @@ class TestGetCordraObject:
|
|||||||
assert "Connection failed" in str(exc_info.value)
|
assert "Connection failed" in str(exc_info.value)
|
||||||
mock_client.get_object.assert_called_once_with("people/john-doe-123")
|
mock_client.get_object.assert_called_once_with("people/john-doe-123")
|
||||||
|
|
||||||
@patch('mcp_cordra.server.cordra_client')
|
@patch('cordra_mcp.server.cordra_client')
|
||||||
async def test_object_id_construction(self, mock_client, sample_digital_object):
|
async def test_object_id_construction(self, mock_client, sample_digital_object):
|
||||||
"""Test that object ID is correctly constructed from prefix and suffix."""
|
"""Test that object ID is correctly constructed from prefix and suffix."""
|
||||||
mock_client.get_object = AsyncMock(return_value=sample_digital_object)
|
mock_client.get_object = AsyncMock(return_value=sample_digital_object)
|
||||||
@@ -98,7 +99,7 @@ class TestGetCordraObject:
|
|||||||
await get_cordra_object(prefix, suffix)
|
await get_cordra_object(prefix, suffix)
|
||||||
mock_client.get_object.assert_called_with(expected_id)
|
mock_client.get_object.assert_called_with(expected_id)
|
||||||
|
|
||||||
@patch('mcp_cordra.server.cordra_client')
|
@patch('cordra_mcp.server.cordra_client')
|
||||||
async def test_json_formatting(self, mock_client, sample_digital_object):
|
async def test_json_formatting(self, mock_client, sample_digital_object):
|
||||||
"""Test that the returned JSON is properly formatted."""
|
"""Test that the returned JSON is properly formatted."""
|
||||||
mock_client.get_object = AsyncMock(return_value=sample_digital_object)
|
mock_client.get_object = AsyncMock(return_value=sample_digital_object)
|
||||||
@@ -120,7 +121,7 @@ class TestGetCordraObject:
|
|||||||
assert "acl" in parsed_result
|
assert "acl" in parsed_result
|
||||||
assert "payloads" in parsed_result
|
assert "payloads" in parsed_result
|
||||||
|
|
||||||
@patch('mcp_cordra.server.cordra_client')
|
@patch('cordra_mcp.server.cordra_client')
|
||||||
async def test_minimal_object(self, mock_client):
|
async def test_minimal_object(self, mock_client):
|
||||||
"""Test handling of object with minimal data."""
|
"""Test handling of object with minimal data."""
|
||||||
minimal_object = DigitalObject(
|
minimal_object = DigitalObject(
|
||||||
@@ -147,7 +148,7 @@ class TestGetCordraObject:
|
|||||||
class TestSchemaResourceFunctions:
|
class TestSchemaResourceFunctions:
|
||||||
"""Test the schema resource functions."""
|
"""Test the schema resource functions."""
|
||||||
|
|
||||||
@patch('mcp_cordra.server.cordra_client')
|
@patch('cordra_mcp.server.cordra_client')
|
||||||
async def test_create_schema_resource_success(self, mock_client):
|
async def test_create_schema_resource_success(self, mock_client):
|
||||||
"""Test successful schema resource creation."""
|
"""Test successful schema resource creation."""
|
||||||
mock_schema = DigitalObject(
|
mock_schema = DigitalObject(
|
||||||
@@ -157,7 +158,7 @@ class TestSchemaResourceFunctions:
|
|||||||
)
|
)
|
||||||
mock_client.get_schema = AsyncMock(return_value=mock_schema)
|
mock_client.get_schema = AsyncMock(return_value=mock_schema)
|
||||||
|
|
||||||
from mcp_cordra.server import create_schema_resource
|
from cordra_mcp.server import create_schema_resource
|
||||||
result = await create_schema_resource("User")
|
result = await create_schema_resource("User")
|
||||||
|
|
||||||
# Verify the result is valid JSON
|
# Verify the result is valid JSON
|
||||||
@@ -169,19 +170,19 @@ class TestSchemaResourceFunctions:
|
|||||||
# Verify the client was called with correct schema name
|
# Verify the client was called with correct schema name
|
||||||
mock_client.get_schema.assert_called_once_with("User")
|
mock_client.get_schema.assert_called_once_with("User")
|
||||||
|
|
||||||
@patch('mcp_cordra.server.cordra_client')
|
@patch('cordra_mcp.server.cordra_client')
|
||||||
async def test_create_schema_resource_not_found(self, mock_client):
|
async def test_create_schema_resource_not_found(self, mock_client):
|
||||||
"""Test schema resource creation with schema not found."""
|
"""Test schema resource creation with schema not found."""
|
||||||
mock_client.get_schema = AsyncMock(side_effect=CordraNotFoundError("Schema not found"))
|
mock_client.get_schema = AsyncMock(side_effect=CordraNotFoundError("Schema not found"))
|
||||||
|
|
||||||
from mcp_cordra.server import create_schema_resource
|
from cordra_mcp.server import create_schema_resource
|
||||||
with pytest.raises(RuntimeError) as exc_info:
|
with pytest.raises(RuntimeError) as exc_info:
|
||||||
await create_schema_resource("NonExistent")
|
await create_schema_resource("NonExistent")
|
||||||
|
|
||||||
assert "Schema not found: NonExistent" in str(exc_info.value)
|
assert "Schema not found: NonExistent" in str(exc_info.value)
|
||||||
mock_client.get_schema.assert_called_once_with("NonExistent")
|
mock_client.get_schema.assert_called_once_with("NonExistent")
|
||||||
|
|
||||||
@patch('mcp_cordra.server.cordra_client')
|
@patch('cordra_mcp.server.cordra_client')
|
||||||
async def test_register_schema_resources_success(self, mock_client):
|
async def test_register_schema_resources_success(self, mock_client):
|
||||||
"""Test successful schema resource registration."""
|
"""Test successful schema resource registration."""
|
||||||
mock_schemas = [
|
mock_schemas = [
|
||||||
@@ -192,8 +193,8 @@ class TestSchemaResourceFunctions:
|
|||||||
mock_client.find = AsyncMock(return_value=mock_schemas)
|
mock_client.find = AsyncMock(return_value=mock_schemas)
|
||||||
|
|
||||||
# Mock the mcp.add_resource method
|
# Mock the mcp.add_resource method
|
||||||
with patch('mcp_cordra.server.mcp') as mock_mcp:
|
with patch('cordra_mcp.server.mcp') as mock_mcp:
|
||||||
from mcp_cordra.server import register_schema_resources
|
from cordra_mcp.server import register_schema_resources
|
||||||
await register_schema_resources()
|
await register_schema_resources()
|
||||||
|
|
||||||
# Verify the client was called with correct query
|
# Verify the client was called with correct query
|
||||||
@@ -202,7 +203,7 @@ class TestSchemaResourceFunctions:
|
|||||||
# Verify add_resource was called for each schema
|
# Verify add_resource was called for each schema
|
||||||
assert mock_mcp.add_resource.call_count == 3
|
assert mock_mcp.add_resource.call_count == 3
|
||||||
|
|
||||||
@patch('mcp_cordra.server.cordra_client')
|
@patch('cordra_mcp.server.cordra_client')
|
||||||
async def test_register_schema_resources_missing_name(self, mock_client):
|
async def test_register_schema_resources_missing_name(self, mock_client):
|
||||||
"""Test schema resource registration with objects missing name field."""
|
"""Test schema resource registration with objects missing name field."""
|
||||||
mock_schemas = [
|
mock_schemas = [
|
||||||
@@ -212,20 +213,20 @@ class TestSchemaResourceFunctions:
|
|||||||
]
|
]
|
||||||
mock_client.find = AsyncMock(return_value=mock_schemas)
|
mock_client.find = AsyncMock(return_value=mock_schemas)
|
||||||
|
|
||||||
with patch('mcp_cordra.server.mcp') as mock_mcp:
|
with patch('cordra_mcp.server.mcp') as mock_mcp:
|
||||||
from mcp_cordra.server import register_schema_resources
|
from cordra_mcp.server import register_schema_resources
|
||||||
await register_schema_resources()
|
await register_schema_resources()
|
||||||
|
|
||||||
# Only 2 schemas should be registered (those with name field)
|
# Only 2 schemas should be registered (those with name field)
|
||||||
assert mock_mcp.add_resource.call_count == 2
|
assert mock_mcp.add_resource.call_count == 2
|
||||||
|
|
||||||
@patch('mcp_cordra.server.cordra_client')
|
@patch('cordra_mcp.server.cordra_client')
|
||||||
async def test_register_schema_resources_client_error(self, mock_client):
|
async def test_register_schema_resources_client_error(self, mock_client):
|
||||||
"""Test schema resource registration with client error."""
|
"""Test schema resource registration with client error."""
|
||||||
mock_client.find = AsyncMock(side_effect=CordraClientError("Search failed"))
|
mock_client.find = AsyncMock(side_effect=CordraClientError("Search failed"))
|
||||||
|
|
||||||
# Should not raise an exception, just log a warning
|
# Should not raise an exception, just log a warning
|
||||||
from mcp_cordra.server import register_schema_resources
|
from cordra_mcp.server import register_schema_resources
|
||||||
await register_schema_resources() # Should complete without raising
|
await register_schema_resources() # Should complete without raising
|
||||||
|
|
||||||
mock_client.find.assert_called_once_with("type:Schema")
|
mock_client.find.assert_called_once_with("type:Schema")
|
||||||
|
|||||||
112
uv.lock
generated
112
uv.lock
generated
@@ -111,6 +111,57 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cordra-mcp"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "mcp", extra = ["cli"] },
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "pydantic-settings" },
|
||||||
|
{ name = "requests" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "loguru" },
|
||||||
|
{ name = "mypy" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
{ name = "pytest-asyncio" },
|
||||||
|
{ name = "pytest-cov" },
|
||||||
|
{ name = "ruff" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dev-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "mypy" },
|
||||||
|
{ name = "pytest-asyncio" },
|
||||||
|
{ name = "ruff" },
|
||||||
|
{ name = "types-requests" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "loguru", marker = "extra == 'dev'", specifier = ">=0.7.0" },
|
||||||
|
{ name = "mcp", extras = ["cli"], specifier = ">=1.2.0" },
|
||||||
|
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.6.0" },
|
||||||
|
{ name = "pydantic", specifier = ">=2.0.0" },
|
||||||
|
{ name = "pydantic-settings", specifier = ">=2.0.0" },
|
||||||
|
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4.0" },
|
||||||
|
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.21.0" },
|
||||||
|
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" },
|
||||||
|
{ name = "requests", specifier = ">=2.25.0" },
|
||||||
|
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata.requires-dev]
|
||||||
|
dev = [
|
||||||
|
{ name = "mypy", specifier = ">=1.16.1" },
|
||||||
|
{ name = "pytest-asyncio", specifier = ">=1.0.0" },
|
||||||
|
{ name = "ruff", specifier = ">=0.12.1" },
|
||||||
|
{ name = "types-requests", specifier = ">=2.32.4.20250611" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "coverage"
|
name = "coverage"
|
||||||
version = "7.9.1"
|
version = "7.9.1"
|
||||||
@@ -313,55 +364,6 @@ cli = [
|
|||||||
{ name = "typer" },
|
{ name = "typer" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cordra-mcp"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = { editable = "." }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "mcp", extra = ["cli"] },
|
|
||||||
{ name = "pydantic" },
|
|
||||||
{ name = "pydantic-settings" },
|
|
||||||
{ name = "requests" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.optional-dependencies]
|
|
||||||
dev = [
|
|
||||||
{ name = "loguru" },
|
|
||||||
{ name = "mypy" },
|
|
||||||
{ name = "pytest" },
|
|
||||||
{ name = "pytest-asyncio" },
|
|
||||||
{ name = "pytest-cov" },
|
|
||||||
{ name = "ruff" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dev-dependencies]
|
|
||||||
dev = [
|
|
||||||
{ name = "mypy" },
|
|
||||||
{ name = "pytest-asyncio" },
|
|
||||||
{ name = "ruff" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata]
|
|
||||||
requires-dist = [
|
|
||||||
{ name = "loguru", marker = "extra == 'dev'", specifier = ">=0.7.0" },
|
|
||||||
{ name = "mcp", extras = ["cli"], specifier = ">=1.2.0" },
|
|
||||||
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.6.0" },
|
|
||||||
{ name = "pydantic", specifier = ">=2.0.0" },
|
|
||||||
{ name = "pydantic-settings", specifier = ">=2.0.0" },
|
|
||||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4.0" },
|
|
||||||
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.21.0" },
|
|
||||||
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" },
|
|
||||||
{ name = "requests", specifier = ">=2.25.0" },
|
|
||||||
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
|
||||||
dev = [
|
|
||||||
{ name = "mypy", specifier = ">=1.16.1" },
|
|
||||||
{ name = "pytest-asyncio", specifier = ">=1.0.0" },
|
|
||||||
{ name = "ruff", specifier = ">=0.12.1" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mdurl"
|
name = "mdurl"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -840,6 +842,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317 },
|
{ url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-requests"
|
||||||
|
version = "2.32.4.20250611"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "urllib3" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/6d/7f/73b3a04a53b0fd2a911d4ec517940ecd6600630b559e4505cc7b68beb5a0/types_requests-2.32.4.20250611.tar.gz", hash = "sha256:741c8777ed6425830bf51e54d6abe245f79b4dcb9019f1622b773463946bf826", size = 23118 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/ea/0be9258c5a4fa1ba2300111aa5a0767ee6d18eb3fd20e91616c12082284d/types_requests-2.32.4.20250611-py3-none-any.whl", hash = "sha256:ad2fe5d3b0cb3c2c902c8815a70e7fb2302c4b8c1f77bdcd738192cdb3878072", size = 20643 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.14.0"
|
version = "4.14.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user