major-domo-v2/tests/test_services_base_service.py
Cal Corum 7b41520054 CLAUDE: Major bot enhancements - Admin commands, player stats, standings, schedules
Major Features Added:
• Admin Management System: Complete admin command suite with user moderation, system control, and bot maintenance tools
• Enhanced Player Commands: Added batting/pitching statistics with concurrent API calls and improved embed design
• League Standings: Full standings system with division grouping, playoff picture, and wild card visualization
• Game Schedules: Comprehensive schedule system with team filtering, series organization, and proper home/away indicators

New Admin Commands (12 total):
• /admin-status, /admin-help, /admin-reload, /admin-sync, /admin-clear
• /admin-announce, /admin-maintenance
• /admin-timeout, /admin-untimeout, /admin-kick, /admin-ban, /admin-unban, /admin-userinfo

Enhanced Player Display:
• Team logo positioned beside player name using embed author
• Smart thumbnail priority: fancycard → headshot → team logo fallback
• Concurrent batting/pitching stats fetching for performance
• Rich statistics display with team colors and comprehensive metrics

New Models & Services:
• BattingStats, PitchingStats, TeamStandings, Division, Game models
• StatsService, StandingsService, ScheduleService for data management
• CustomCommand system with CRUD operations and cleanup tasks

Bot Architecture Improvements:
• Admin commands integrated into bot.py with proper loading
• Permission checks and safety guards for moderation commands
• Enhanced error handling and comprehensive audit logging
• All 227 tests passing with new functionality

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-28 15:32:38 -05:00

257 lines
8.9 KiB
Python

"""
Tests for BaseService functionality
"""
import pytest
from unittest.mock import AsyncMock
from services.base_service import BaseService
from models.base import SBABaseModel
from exceptions import APIException
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