From ba55ed3109bc0dd2f4c16b8fd5a13f43e0679fda Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Mon, 9 Mar 2026 19:34:36 -0500 Subject: [PATCH] fix: add trailing slashes to all collection POST calls 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 --- services/base_service.py | 2 +- services/draft_list_service.py | 222 ++++++++++++++-------------- services/injury_service.py | 83 ++++++----- services/transaction_service.py | 2 +- tests/test_services_base_service.py | 197 ++++++++++++------------ 5 files changed, 252 insertions(+), 254 deletions(-) diff --git a/services/base_service.py b/services/base_service.py index faf0dba..7046efb 100644 --- a/services/base_service.py +++ b/services/base_service.py @@ -245,7 +245,7 @@ class BaseService(Generic[T]): """ try: client = await self.get_client() - response = await client.post(self.endpoint, model_data) + response = await client.post(f"{self.endpoint}/", model_data) if not response: logger.warning(f"No response from {self.model_class.__name__} creation") diff --git a/services/draft_list_service.py b/services/draft_list_service.py index 2a7017d..934a44f 100644 --- a/services/draft_list_service.py +++ b/services/draft_list_service.py @@ -3,13 +3,14 @@ Draft list service for Discord Bot v2.0 Handles team draft list (auto-draft queue) operations. NO CACHING - lists change frequently. """ + import logging from typing import Optional, List from services.base_service import BaseService from models.draft_list import DraftList -logger = logging.getLogger(f'{__name__}.DraftListService') +logger = logging.getLogger(f"{__name__}.DraftListService") class DraftListService(BaseService[DraftList]): @@ -32,7 +33,7 @@ class DraftListService(BaseService[DraftList]): def __init__(self): """Initialize draft list service.""" - super().__init__(DraftList, 'draftlist') + super().__init__(DraftList, "draftlist") logger.debug("DraftListService initialized") def _extract_items_and_count_from_response(self, data): @@ -54,20 +55,16 @@ class DraftListService(BaseService[DraftList]): return [], 0 # Get count - count = data.get('count', 0) + count = data.get("count", 0) # API returns items under 'picks' key (not 'draftlist') - if 'picks' in data and isinstance(data['picks'], list): - return data['picks'], count or len(data['picks']) + if "picks" in data and isinstance(data["picks"], list): + return data["picks"], count or len(data["picks"]) # Fallback to standard extraction return super()._extract_items_and_count_from_response(data) - async def get_team_list( - self, - season: int, - team_id: int - ) -> List[DraftList]: + async def get_team_list(self, season: int, team_id: int) -> List[DraftList]: """ Get team's draft list ordered by rank. @@ -82,8 +79,8 @@ class DraftListService(BaseService[DraftList]): """ try: params = [ - ('season', str(season)), - ('team_id', str(team_id)) + ("season", str(season)), + ("team_id", str(team_id)), # NOTE: API does not support 'sort' param - results must be sorted client-side ] @@ -100,11 +97,7 @@ class DraftListService(BaseService[DraftList]): return [] async def add_to_list( - self, - season: int, - team_id: int, - player_id: int, - rank: Optional[int] = None + self, season: int, team_id: int, player_id: int, rank: Optional[int] = None ) -> Optional[List[DraftList]]: """ Add player to team's draft list. @@ -133,10 +126,10 @@ class DraftListService(BaseService[DraftList]): # Create new entry data new_entry_data = { - 'season': season, - 'team_id': team_id, - 'player_id': player_id, - 'rank': rank + "season": season, + "team_id": team_id, + "player_id": player_id, + "rank": rank, } # Build complete list for bulk replacement @@ -146,36 +139,42 @@ class DraftListService(BaseService[DraftList]): for entry in current_list: if entry.rank >= rank: # Shift down entries at or after insertion point - draft_list_entries.append({ - 'season': entry.season, - 'team_id': entry.team_id, - 'player_id': entry.player_id, - 'rank': entry.rank + 1 - }) + draft_list_entries.append( + { + "season": entry.season, + "team_id": entry.team_id, + "player_id": entry.player_id, + "rank": entry.rank + 1, + } + ) else: # Keep existing rank for entries before insertion point - draft_list_entries.append({ - 'season': entry.season, - 'team_id': entry.team_id, - 'player_id': entry.player_id, - 'rank': entry.rank - }) + draft_list_entries.append( + { + "season": entry.season, + "team_id": entry.team_id, + "player_id": entry.player_id, + "rank": entry.rank, + } + ) # Add new entry draft_list_entries.append(new_entry_data) # Sort by rank for consistency - draft_list_entries.sort(key=lambda x: x['rank']) + draft_list_entries.sort(key=lambda x: x["rank"]) # POST entire list (bulk replacement) client = await self.get_client() payload = { - 'count': len(draft_list_entries), - 'draft_list': draft_list_entries + "count": len(draft_list_entries), + "draft_list": draft_list_entries, } - logger.debug(f"Posting draft list for team {team_id}: {len(draft_list_entries)} entries") - response = await client.post(self.endpoint, payload) + logger.debug( + f"Posting draft list for team {team_id}: {len(draft_list_entries)} entries" + ) + response = await client.post(f"{self.endpoint}/", payload) logger.debug(f"POST response: {response}") # Verify by fetching the list back (API returns full objects) @@ -184,20 +183,21 @@ class DraftListService(BaseService[DraftList]): # Verify the player was added if not any(entry.player_id == player_id for entry in verification): - logger.error(f"Player {player_id} not found in list after POST - operation may have failed") + logger.error( + f"Player {player_id} not found in list after POST - operation may have failed" + ) return None - logger.info(f"Added player {player_id} to team {team_id} draft list at rank {rank}") + logger.info( + f"Added player {player_id} to team {team_id} draft list at rank {rank}" + ) return verification # Return full updated list except Exception as e: logger.error(f"Error adding player {player_id} to draft list: {e}") return None - async def remove_from_list( - self, - entry_id: int - ) -> bool: + async def remove_from_list(self, entry_id: int) -> bool: """ Remove entry from draft list by ID. @@ -209,14 +209,13 @@ class DraftListService(BaseService[DraftList]): Returns: True if deletion succeeded """ - logger.warning("remove_from_list() called with entry_id - use remove_player_from_list() instead") + logger.warning( + "remove_from_list() called with entry_id - use remove_player_from_list() instead" + ) return False async def remove_player_from_list( - self, - season: int, - team_id: int, - player_id: int + self, season: int, team_id: int, player_id: int ) -> bool: """ Remove specific player from team's draft list. @@ -238,7 +237,9 @@ class DraftListService(BaseService[DraftList]): # Check if player is in list player_found = any(entry.player_id == player_id for entry in current_list) if not player_found: - logger.warning(f"Player {player_id} not found in team {team_id} draft list") + logger.warning( + f"Player {player_id} not found in team {team_id} draft list" + ) return False # Build new list without the player, adjusting ranks @@ -246,22 +247,24 @@ class DraftListService(BaseService[DraftList]): new_rank = 1 for entry in current_list: if entry.player_id != player_id: - draft_list_entries.append({ - 'season': entry.season, - 'team_id': entry.team_id, - 'player_id': entry.player_id, - 'rank': new_rank - }) + draft_list_entries.append( + { + "season": entry.season, + "team_id": entry.team_id, + "player_id": entry.player_id, + "rank": new_rank, + } + ) new_rank += 1 # POST updated list (bulk replacement) client = await self.get_client() payload = { - 'count': len(draft_list_entries), - 'draft_list': draft_list_entries + "count": len(draft_list_entries), + "draft_list": draft_list_entries, } - await client.post(self.endpoint, payload) + await client.post(f"{self.endpoint}/", payload) logger.info(f"Removed player {player_id} from team {team_id} draft list") return True @@ -270,11 +273,7 @@ class DraftListService(BaseService[DraftList]): logger.error(f"Error removing player {player_id} from draft list: {e}") return False - async def clear_list( - self, - season: int, - team_id: int - ) -> bool: + async def clear_list(self, season: int, team_id: int) -> bool: """ Clear entire draft list for team. @@ -309,10 +308,7 @@ class DraftListService(BaseService[DraftList]): return False async def reorder_list( - self, - season: int, - team_id: int, - new_order: List[int] + self, season: int, team_id: int, new_order: List[int] ) -> bool: """ Reorder team's draft list. @@ -342,21 +338,23 @@ class DraftListService(BaseService[DraftList]): continue entry = entry_map[player_id] - draft_list_entries.append({ - 'season': entry.season, - 'team_id': entry.team_id, - 'player_id': entry.player_id, - 'rank': new_rank - }) + draft_list_entries.append( + { + "season": entry.season, + "team_id": entry.team_id, + "player_id": entry.player_id, + "rank": new_rank, + } + ) # POST reordered list (bulk replacement) client = await self.get_client() payload = { - 'count': len(draft_list_entries), - 'draft_list': draft_list_entries + "count": len(draft_list_entries), + "draft_list": draft_list_entries, } - await client.post(self.endpoint, payload) + await client.post(f"{self.endpoint}/", payload) logger.info(f"Reordered draft list for team {team_id}") return True @@ -365,12 +363,7 @@ class DraftListService(BaseService[DraftList]): logger.error(f"Error reordering draft list for team {team_id}: {e}") return False - async def move_entry_up( - self, - season: int, - team_id: int, - player_id: int - ) -> bool: + async def move_entry_up(self, season: int, team_id: int, player_id: int) -> bool: """ Move player up one position in draft list (higher priority). @@ -403,7 +396,9 @@ class DraftListService(BaseService[DraftList]): return False # Find entry above (rank - 1) - above_entry = next((e for e in entries if e.rank == current_entry.rank - 1), None) + above_entry = next( + (e for e in entries if e.rank == current_entry.rank - 1), None + ) if not above_entry: logger.error(f"Could not find entry above rank {current_entry.rank}") return False @@ -421,24 +416,26 @@ class DraftListService(BaseService[DraftList]): # Keep existing rank new_rank = entry.rank - draft_list_entries.append({ - 'season': entry.season, - 'team_id': entry.team_id, - 'player_id': entry.player_id, - 'rank': new_rank - }) + draft_list_entries.append( + { + "season": entry.season, + "team_id": entry.team_id, + "player_id": entry.player_id, + "rank": new_rank, + } + ) # Sort by rank - draft_list_entries.sort(key=lambda x: x['rank']) + draft_list_entries.sort(key=lambda x: x["rank"]) # POST updated list (bulk replacement) client = await self.get_client() payload = { - 'count': len(draft_list_entries), - 'draft_list': draft_list_entries + "count": len(draft_list_entries), + "draft_list": draft_list_entries, } - await client.post(self.endpoint, payload) + await client.post(f"{self.endpoint}/", payload) logger.info(f"Moved player {player_id} up to rank {current_entry.rank - 1}") return True @@ -447,12 +444,7 @@ class DraftListService(BaseService[DraftList]): logger.error(f"Error moving player {player_id} up in draft list: {e}") return False - async def move_entry_down( - self, - season: int, - team_id: int, - player_id: int - ) -> bool: + async def move_entry_down(self, season: int, team_id: int, player_id: int) -> bool: """ Move player down one position in draft list (lower priority). @@ -485,7 +477,9 @@ class DraftListService(BaseService[DraftList]): return False # Find entry below (rank + 1) - below_entry = next((e for e in entries if e.rank == current_entry.rank + 1), None) + below_entry = next( + (e for e in entries if e.rank == current_entry.rank + 1), None + ) if not below_entry: logger.error(f"Could not find entry below rank {current_entry.rank}") return False @@ -503,25 +497,29 @@ class DraftListService(BaseService[DraftList]): # Keep existing rank new_rank = entry.rank - draft_list_entries.append({ - 'season': entry.season, - 'team_id': entry.team_id, - 'player_id': entry.player_id, - 'rank': new_rank - }) + draft_list_entries.append( + { + "season": entry.season, + "team_id": entry.team_id, + "player_id": entry.player_id, + "rank": new_rank, + } + ) # Sort by rank - draft_list_entries.sort(key=lambda x: x['rank']) + draft_list_entries.sort(key=lambda x: x["rank"]) # POST updated list (bulk replacement) client = await self.get_client() payload = { - 'count': len(draft_list_entries), - 'draft_list': draft_list_entries + "count": len(draft_list_entries), + "draft_list": draft_list_entries, } - await client.post(self.endpoint, payload) - logger.info(f"Moved player {player_id} down to rank {current_entry.rank + 1}") + await client.post(f"{self.endpoint}/", payload) + logger.info( + f"Moved player {player_id} down to rank {current_entry.rank + 1}" + ) return True diff --git a/services/injury_service.py b/services/injury_service.py index 555a7a5..0164447 100644 --- a/services/injury_service.py +++ b/services/injury_service.py @@ -3,13 +3,14 @@ Injury service for Discord Bot v2.0 Handles injury-related operations including checking, creating, and clearing injuries. """ + import logging from typing import Optional, List from services.base_service import BaseService from models.injury import Injury -logger = logging.getLogger(f'{__name__}.InjuryService') +logger = logging.getLogger(f"{__name__}.InjuryService") class InjuryService(BaseService[Injury]): @@ -25,7 +26,7 @@ class InjuryService(BaseService[Injury]): def __init__(self): """Initialize injury service.""" - super().__init__(Injury, 'injuries') + super().__init__(Injury, "injuries") logger.debug("InjuryService initialized") async def get_active_injury(self, player_id: int, season: int) -> Optional[Injury]: @@ -41,25 +42,31 @@ class InjuryService(BaseService[Injury]): """ try: params = [ - ('player_id', str(player_id)), - ('season', str(season)), - ('is_active', 'true') + ("player_id", str(player_id)), + ("season", str(season)), + ("is_active", "true"), ] injuries = await self.get_all_items(params=params) if injuries: - logger.debug(f"Found active injury for player {player_id} in season {season}") + logger.debug( + f"Found active injury for player {player_id} in season {season}" + ) return injuries[0] - logger.debug(f"No active injury found for player {player_id} in season {season}") + logger.debug( + f"No active injury found for player {player_id} in season {season}" + ) return None except Exception as e: logger.error(f"Error getting active injury for player {player_id}: {e}") return None - async def get_injuries_by_player(self, player_id: int, season: int, active_only: bool = False) -> List[Injury]: + async def get_injuries_by_player( + self, player_id: int, season: int, active_only: bool = False + ) -> List[Injury]: """ Get all injuries for a player in a specific season. @@ -72,13 +79,10 @@ class InjuryService(BaseService[Injury]): List of injuries for the player """ try: - params = [ - ('player_id', str(player_id)), - ('season', str(season)) - ] + params = [("player_id", str(player_id)), ("season", str(season))] if active_only: - params.append(('is_active', 'true')) + params.append(("is_active", "true")) injuries = await self.get_all_items(params=params) logger.debug(f"Retrieved {len(injuries)} injuries for player {player_id}") @@ -88,7 +92,9 @@ class InjuryService(BaseService[Injury]): logger.error(f"Error getting injuries for player {player_id}: {e}") return [] - async def get_injuries_by_team(self, team_id: int, season: int, active_only: bool = True) -> List[Injury]: + async def get_injuries_by_team( + self, team_id: int, season: int, active_only: bool = True + ) -> List[Injury]: """ Get all injuries for a team in a specific season. @@ -101,13 +107,10 @@ class InjuryService(BaseService[Injury]): List of injuries for the team """ try: - params = [ - ('team_id', str(team_id)), - ('season', str(season)) - ] + params = [("team_id", str(team_id)), ("season", str(season))] if active_only: - params.append(('is_active', 'true')) + params.append(("is_active", "true")) injuries = await self.get_all_items(params=params) logger.debug(f"Retrieved {len(injuries)} injuries for team {team_id}") @@ -125,7 +128,7 @@ class InjuryService(BaseService[Injury]): start_week: int, start_game: int, end_week: int, - end_game: int + end_game: int, ) -> Optional[Injury]: """ Create a new injury record. @@ -144,22 +147,24 @@ class InjuryService(BaseService[Injury]): """ try: injury_data = { - 'season': season, - 'player_id': player_id, - 'total_games': total_games, - 'start_week': start_week, - 'start_game': start_game, - 'end_week': end_week, - 'end_game': end_game, - 'is_active': True + "season": season, + "player_id": player_id, + "total_games": total_games, + "start_week": start_week, + "start_game": start_game, + "end_week": end_week, + "end_game": end_game, + "is_active": True, } # Call the API to create the injury client = await self.get_client() - response = await client.post(self.endpoint, injury_data) + response = await client.post(f"{self.endpoint}/", injury_data) if not response: - logger.error(f"Failed to create injury for player {player_id}: No response from API") + logger.error( + f"Failed to create injury for player {player_id}: No response from API" + ) return None # Merge the request data with the response to ensure all required fields are present @@ -187,7 +192,9 @@ class InjuryService(BaseService[Injury]): """ try: # Note: API expects is_active as query parameter, not JSON body - updated_injury = await self.patch(injury_id, {'is_active': False}, use_query_params=True) + updated_injury = await self.patch( + injury_id, {"is_active": False}, use_query_params=True + ) if updated_injury: logger.info(f"Cleared injury {injury_id}") @@ -216,16 +223,18 @@ class InjuryService(BaseService[Injury]): try: client = await self.get_client() params = [ - ('season', str(season)), - ('is_active', 'true'), - ('sort', 'return-asc') + ("season", str(season)), + ("is_active", "true"), + ("sort", "return-asc"), ] response = await client.get(self.endpoint, params=params) - if response and 'injuries' in response: - logger.debug(f"Retrieved {len(response['injuries'])} active injuries for season {season}") - return response['injuries'] + if response and "injuries" in response: + logger.debug( + f"Retrieved {len(response['injuries'])} active injuries for season {season}" + ) + return response["injuries"] logger.debug(f"No active injuries found for season {season}") return [] diff --git a/services/transaction_service.py b/services/transaction_service.py index 80ce0e5..57c9c90 100644 --- a/services/transaction_service.py +++ b/services/transaction_service.py @@ -248,7 +248,7 @@ class TransactionService(BaseService[Transaction]): # POST batch to API client = await self.get_client() - response = await client.post(self.endpoint, data=batch_data) + response = await client.post(f"{self.endpoint}/", data=batch_data) # API returns a string like "2 transactions have been added" # We need to return the original Transaction objects (they won't have IDs assigned by API) diff --git a/tests/test_services_base_service.py b/tests/test_services_base_service.py index 4ef6602..19a3d42 100644 --- a/tests/test_services_base_service.py +++ b/tests/test_services_base_service.py @@ -1,6 +1,7 @@ """ Tests for BaseService functionality """ + import pytest from unittest.mock import AsyncMock @@ -10,6 +11,7 @@ from models.base import SBABaseModel class MockModel(SBABaseModel): """Mock model for testing BaseService.""" + id: int name: str value: int = 100 @@ -17,240 +19,229 @@ class MockModel(SBABaseModel): 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) + 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') + service = BaseService(MockModel, "test_endpoint") assert service.model_class == MockModel - assert service.endpoint == 'test_endpoint' + 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_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.name == "Test" assert result.value == 200 - mock_client.get.assert_called_once_with('mocks', object_id=1) - + 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) - + 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} - ] + "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) - + 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_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} + 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) - + 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} + 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) - + 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) - - + 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_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') - + + result = await base_service.get_by_field("name", "Test") + assert len(result) == 1 - mock_client.get.assert_called_once_with('mocks', params=[('name', 'Test')]) - + 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'} - ] + "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' - + 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'} - + 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'} - ] - + 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) - - + 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')]) + 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') - + + service = BaseService(TestModel, "test") + # Test with 'items' field - data = {'count': 2, 'items': [{'name': 'Item1'}, {'name': 'Item2'}]} + 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'}]} + 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'}]} + 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 \ No newline at end of file + assert count == 0