fix: add trailing slashes to all collection POST calls
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>
This commit is contained in:
Cal Corum 2026-03-09 19:34:36 -05:00
parent f6a25aa16d
commit ba55ed3109
5 changed files with 252 additions and 254 deletions

View File

@ -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")

View File

@ -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

View File

@ -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 []

View File

@ -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)

View File

@ -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
assert count == 0