All checks were successful
Build Docker Image / build (pull_request) Successful in 1m16s
Ensures all client.post() calls to collection endpoints include trailing slashes, matching the standardized database API routes. Covers BaseService.create(), TransactionService, InjuryService, and DraftListService POST calls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
248 lines
8.4 KiB
Python
248 lines
8.4 KiB
Python
"""
|
|
Tests for BaseService functionality
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import AsyncMock
|
|
|
|
from services.base_service import BaseService
|
|
from models.base import SBABaseModel
|
|
|
|
|
|
class MockModel(SBABaseModel):
|
|
"""Mock model for testing BaseService."""
|
|
|
|
id: int
|
|
name: str
|
|
value: int = 100
|
|
|
|
|
|
class TestBaseService:
|
|
"""Test BaseService functionality."""
|
|
|
|
@pytest.fixture
|
|
def mock_client(self):
|
|
"""Mock API client."""
|
|
client = AsyncMock()
|
|
return client
|
|
|
|
@pytest.fixture
|
|
def base_service(self, mock_client):
|
|
"""Create BaseService instance for testing."""
|
|
service = BaseService(MockModel, "mocks", client=mock_client)
|
|
return service
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_init(self):
|
|
"""Test service initialization."""
|
|
service = BaseService(MockModel, "test_endpoint")
|
|
assert service.model_class == MockModel
|
|
assert service.endpoint == "test_endpoint"
|
|
assert service._client is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_by_id_success(self, base_service, mock_client):
|
|
"""Test successful get_by_id."""
|
|
mock_data = {"id": 1, "name": "Test", "value": 200}
|
|
mock_client.get.return_value = mock_data
|
|
|
|
result = await base_service.get_by_id(1)
|
|
|
|
assert isinstance(result, MockModel)
|
|
assert result.id == 1
|
|
assert result.name == "Test"
|
|
assert result.value == 200
|
|
mock_client.get.assert_called_once_with("mocks", object_id=1)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_by_id_not_found(self, base_service, mock_client):
|
|
"""Test get_by_id when object not found."""
|
|
mock_client.get.return_value = None
|
|
|
|
result = await base_service.get_by_id(999)
|
|
|
|
assert result is None
|
|
mock_client.get.assert_called_once_with("mocks", object_id=999)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_all_with_count(self, base_service, mock_client):
|
|
"""Test get_all with count response format."""
|
|
mock_data = {
|
|
"count": 2,
|
|
"mocks": [
|
|
{"id": 1, "name": "Test1", "value": 100},
|
|
{"id": 2, "name": "Test2", "value": 200},
|
|
],
|
|
}
|
|
mock_client.get.return_value = mock_data
|
|
|
|
result, count = await base_service.get_all()
|
|
|
|
assert len(result) == 2
|
|
assert count == 2
|
|
assert all(isinstance(item, MockModel) for item in result)
|
|
mock_client.get.assert_called_once_with("mocks", params=None)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_all_items_convenience(self, base_service, mock_client):
|
|
"""Test get_all_items convenience method."""
|
|
mock_data = {"count": 1, "mocks": [{"id": 1, "name": "Test", "value": 100}]}
|
|
mock_client.get.return_value = mock_data
|
|
|
|
result = await base_service.get_all_items()
|
|
|
|
assert len(result) == 1
|
|
assert isinstance(result[0], MockModel)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_success(self, base_service, mock_client):
|
|
"""Test successful object creation."""
|
|
input_data = {"name": "New Item", "value": 300}
|
|
response_data = {"id": 3, "name": "New Item", "value": 300}
|
|
mock_client.post.return_value = response_data
|
|
|
|
result = await base_service.create(input_data)
|
|
|
|
assert isinstance(result, MockModel)
|
|
assert result.id == 3
|
|
assert result.name == "New Item"
|
|
mock_client.post.assert_called_once_with("mocks/", input_data)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_success(self, base_service, mock_client):
|
|
"""Test successful object update."""
|
|
update_data = {"name": "Updated"}
|
|
response_data = {"id": 1, "name": "Updated", "value": 100}
|
|
mock_client.put.return_value = response_data
|
|
|
|
result = await base_service.update(1, update_data)
|
|
|
|
assert isinstance(result, MockModel)
|
|
assert result.name == "Updated"
|
|
mock_client.put.assert_called_once_with("mocks", update_data, object_id=1)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_success(self, base_service, mock_client):
|
|
"""Test successful object deletion."""
|
|
mock_client.delete.return_value = True
|
|
|
|
result = await base_service.delete(1)
|
|
|
|
assert result is True
|
|
mock_client.delete.assert_called_once_with("mocks", object_id=1)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_by_field(self, base_service, mock_client):
|
|
"""Test get_by_field functionality."""
|
|
mock_data = {"count": 1, "mocks": [{"id": 1, "name": "Test", "value": 100}]}
|
|
mock_client.get.return_value = mock_data
|
|
|
|
result = await base_service.get_by_field("name", "Test")
|
|
|
|
assert len(result) == 1
|
|
mock_client.get.assert_called_once_with("mocks", params=[("name", "Test")])
|
|
|
|
def test_extract_items_and_count_standard_format(self, base_service):
|
|
"""Test response parsing for standard format."""
|
|
data = {
|
|
"count": 3,
|
|
"mocks": [
|
|
{"id": 1, "name": "Test1"},
|
|
{"id": 2, "name": "Test2"},
|
|
{"id": 3, "name": "Test3"},
|
|
],
|
|
}
|
|
|
|
items, count = base_service._extract_items_and_count_from_response(data)
|
|
|
|
assert len(items) == 3
|
|
assert count == 3
|
|
assert items[0]["name"] == "Test1"
|
|
|
|
def test_extract_items_and_count_single_object(self, base_service):
|
|
"""Test response parsing for single object."""
|
|
data = {"id": 1, "name": "Single"}
|
|
|
|
items, count = base_service._extract_items_and_count_from_response(data)
|
|
|
|
assert len(items) == 1
|
|
assert count == 1
|
|
assert items[0] == data
|
|
|
|
def test_extract_items_and_count_direct_list(self, base_service):
|
|
"""Test response parsing for direct list."""
|
|
data = [{"id": 1, "name": "Test1"}, {"id": 2, "name": "Test2"}]
|
|
|
|
items, count = base_service._extract_items_and_count_from_response(data)
|
|
|
|
assert len(items) == 2
|
|
assert count == 2
|
|
|
|
|
|
class TestBaseServiceExtras:
|
|
"""Additional coverage tests for BaseService edge cases."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_base_service_additional_methods(self):
|
|
"""Test additional BaseService methods for coverage."""
|
|
from services.base_service import BaseService
|
|
from models.base import SBABaseModel
|
|
|
|
class TestModel(SBABaseModel):
|
|
name: str
|
|
value: int = 100
|
|
|
|
mock_client = AsyncMock()
|
|
service = BaseService(TestModel, "test", client=mock_client)
|
|
|
|
# Test count method
|
|
mock_client.reset_mock()
|
|
mock_client.get.return_value = {"count": 42, "test": []}
|
|
count = await service.count(params=[("active", "true")])
|
|
assert count == 42
|
|
|
|
# Test update_from_model with ID
|
|
mock_client.reset_mock()
|
|
model = TestModel(id=1, name="Updated", value=300)
|
|
mock_client.put.return_value = {"id": 1, "name": "Updated", "value": 300}
|
|
result = await service.update_from_model(model)
|
|
assert result.name == "Updated"
|
|
|
|
# Test update_from_model without ID
|
|
model_no_id = TestModel(name="Test")
|
|
with pytest.raises(ValueError, match="Cannot update TestModel without ID"):
|
|
await service.update_from_model(model_no_id)
|
|
|
|
def test_base_service_response_parsing_edge_cases(self):
|
|
"""Test edge cases in response parsing."""
|
|
from services.base_service import BaseService
|
|
from models.base import SBABaseModel
|
|
|
|
class TestModel(SBABaseModel):
|
|
name: str
|
|
|
|
service = BaseService(TestModel, "test")
|
|
|
|
# Test with 'items' field
|
|
data = {"count": 2, "items": [{"name": "Item1"}, {"name": "Item2"}]}
|
|
items, count = service._extract_items_and_count_from_response(data)
|
|
assert len(items) == 2
|
|
assert count == 2
|
|
|
|
# Test with 'data' field
|
|
data = {"count": 1, "data": [{"name": "DataItem"}]}
|
|
items, count = service._extract_items_and_count_from_response(data)
|
|
assert len(items) == 1
|
|
assert count == 1
|
|
|
|
# Test with count but no recognizable list field
|
|
data = {"count": 5, "unknown_field": [{"name": "Item"}]}
|
|
items, count = service._extract_items_and_count_from_response(data)
|
|
assert len(items) == 0
|
|
assert count == 5
|
|
|
|
# Test with unexpected data type
|
|
items, count = service._extract_items_and_count_from_response("unexpected")
|
|
assert len(items) == 0
|
|
assert count == 0
|