Merge pull request 'fix: trailing slash on all collection POST calls' (#75) from fix/trailing-slash-307-redirect into main
All checks were successful
Build Docker Image / build (push) Successful in 51s
All checks were successful
Build Docker Image / build (push) Successful in 51s
Reviewed-on: #75
This commit is contained in:
commit
9ba0713887
@ -245,7 +245,7 @@ class BaseService(Generic[T]):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
client = await self.get_client()
|
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:
|
if not response:
|
||||||
logger.warning(f"No response from {self.model_class.__name__} creation")
|
logger.warning(f"No response from {self.model_class.__name__} creation")
|
||||||
|
|||||||
@ -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.
|
Handles team draft list (auto-draft queue) operations. NO CACHING - lists change frequently.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
|
|
||||||
from services.base_service import BaseService
|
from services.base_service import BaseService
|
||||||
from models.draft_list import DraftList
|
from models.draft_list import DraftList
|
||||||
|
|
||||||
logger = logging.getLogger(f'{__name__}.DraftListService')
|
logger = logging.getLogger(f"{__name__}.DraftListService")
|
||||||
|
|
||||||
|
|
||||||
class DraftListService(BaseService[DraftList]):
|
class DraftListService(BaseService[DraftList]):
|
||||||
@ -32,7 +33,7 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize draft list service."""
|
"""Initialize draft list service."""
|
||||||
super().__init__(DraftList, 'draftlist')
|
super().__init__(DraftList, "draftlist")
|
||||||
logger.debug("DraftListService initialized")
|
logger.debug("DraftListService initialized")
|
||||||
|
|
||||||
def _extract_items_and_count_from_response(self, data):
|
def _extract_items_and_count_from_response(self, data):
|
||||||
@ -54,20 +55,16 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
return [], 0
|
return [], 0
|
||||||
|
|
||||||
# Get count
|
# Get count
|
||||||
count = data.get('count', 0)
|
count = data.get("count", 0)
|
||||||
|
|
||||||
# API returns items under 'picks' key (not 'draftlist')
|
# API returns items under 'picks' key (not 'draftlist')
|
||||||
if 'picks' in data and isinstance(data['picks'], list):
|
if "picks" in data and isinstance(data["picks"], list):
|
||||||
return data['picks'], count or len(data['picks'])
|
return data["picks"], count or len(data["picks"])
|
||||||
|
|
||||||
# Fallback to standard extraction
|
# Fallback to standard extraction
|
||||||
return super()._extract_items_and_count_from_response(data)
|
return super()._extract_items_and_count_from_response(data)
|
||||||
|
|
||||||
async def get_team_list(
|
async def get_team_list(self, season: int, team_id: int) -> List[DraftList]:
|
||||||
self,
|
|
||||||
season: int,
|
|
||||||
team_id: int
|
|
||||||
) -> List[DraftList]:
|
|
||||||
"""
|
"""
|
||||||
Get team's draft list ordered by rank.
|
Get team's draft list ordered by rank.
|
||||||
|
|
||||||
@ -82,8 +79,8 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
params = [
|
params = [
|
||||||
('season', str(season)),
|
("season", str(season)),
|
||||||
('team_id', str(team_id))
|
("team_id", str(team_id)),
|
||||||
# NOTE: API does not support 'sort' param - results must be sorted client-side
|
# NOTE: API does not support 'sort' param - results must be sorted client-side
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -100,11 +97,7 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
async def add_to_list(
|
async def add_to_list(
|
||||||
self,
|
self, season: int, team_id: int, player_id: int, rank: Optional[int] = None
|
||||||
season: int,
|
|
||||||
team_id: int,
|
|
||||||
player_id: int,
|
|
||||||
rank: Optional[int] = None
|
|
||||||
) -> Optional[List[DraftList]]:
|
) -> Optional[List[DraftList]]:
|
||||||
"""
|
"""
|
||||||
Add player to team's draft list.
|
Add player to team's draft list.
|
||||||
@ -133,10 +126,10 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
|
|
||||||
# Create new entry data
|
# Create new entry data
|
||||||
new_entry_data = {
|
new_entry_data = {
|
||||||
'season': season,
|
"season": season,
|
||||||
'team_id': team_id,
|
"team_id": team_id,
|
||||||
'player_id': player_id,
|
"player_id": player_id,
|
||||||
'rank': rank
|
"rank": rank,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build complete list for bulk replacement
|
# Build complete list for bulk replacement
|
||||||
@ -146,36 +139,42 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
for entry in current_list:
|
for entry in current_list:
|
||||||
if entry.rank >= rank:
|
if entry.rank >= rank:
|
||||||
# Shift down entries at or after insertion point
|
# Shift down entries at or after insertion point
|
||||||
draft_list_entries.append({
|
draft_list_entries.append(
|
||||||
'season': entry.season,
|
{
|
||||||
'team_id': entry.team_id,
|
"season": entry.season,
|
||||||
'player_id': entry.player_id,
|
"team_id": entry.team_id,
|
||||||
'rank': entry.rank + 1
|
"player_id": entry.player_id,
|
||||||
})
|
"rank": entry.rank + 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Keep existing rank for entries before insertion point
|
# Keep existing rank for entries before insertion point
|
||||||
draft_list_entries.append({
|
draft_list_entries.append(
|
||||||
'season': entry.season,
|
{
|
||||||
'team_id': entry.team_id,
|
"season": entry.season,
|
||||||
'player_id': entry.player_id,
|
"team_id": entry.team_id,
|
||||||
'rank': entry.rank
|
"player_id": entry.player_id,
|
||||||
})
|
"rank": entry.rank,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Add new entry
|
# Add new entry
|
||||||
draft_list_entries.append(new_entry_data)
|
draft_list_entries.append(new_entry_data)
|
||||||
|
|
||||||
# Sort by rank for consistency
|
# 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)
|
# POST entire list (bulk replacement)
|
||||||
client = await self.get_client()
|
client = await self.get_client()
|
||||||
payload = {
|
payload = {
|
||||||
'count': len(draft_list_entries),
|
"count": len(draft_list_entries),
|
||||||
'draft_list': draft_list_entries
|
"draft_list": draft_list_entries,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(f"Posting draft list for team {team_id}: {len(draft_list_entries)} entries")
|
logger.debug(
|
||||||
response = await client.post(self.endpoint, payload)
|
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}")
|
logger.debug(f"POST response: {response}")
|
||||||
|
|
||||||
# Verify by fetching the list back (API returns full objects)
|
# Verify by fetching the list back (API returns full objects)
|
||||||
@ -184,20 +183,21 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
|
|
||||||
# Verify the player was added
|
# Verify the player was added
|
||||||
if not any(entry.player_id == player_id for entry in verification):
|
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
|
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
|
return verification # Return full updated list
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error adding player {player_id} to draft list: {e}")
|
logger.error(f"Error adding player {player_id} to draft list: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def remove_from_list(
|
async def remove_from_list(self, entry_id: int) -> bool:
|
||||||
self,
|
|
||||||
entry_id: int
|
|
||||||
) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Remove entry from draft list by ID.
|
Remove entry from draft list by ID.
|
||||||
|
|
||||||
@ -209,14 +209,13 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
Returns:
|
Returns:
|
||||||
True if deletion succeeded
|
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
|
return False
|
||||||
|
|
||||||
async def remove_player_from_list(
|
async def remove_player_from_list(
|
||||||
self,
|
self, season: int, team_id: int, player_id: int
|
||||||
season: int,
|
|
||||||
team_id: int,
|
|
||||||
player_id: int
|
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Remove specific player from team's draft list.
|
Remove specific player from team's draft list.
|
||||||
@ -238,7 +237,9 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
# Check if player is in list
|
# Check if player is in list
|
||||||
player_found = any(entry.player_id == player_id for entry in current_list)
|
player_found = any(entry.player_id == player_id for entry in current_list)
|
||||||
if not player_found:
|
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
|
return False
|
||||||
|
|
||||||
# Build new list without the player, adjusting ranks
|
# Build new list without the player, adjusting ranks
|
||||||
@ -246,22 +247,24 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
new_rank = 1
|
new_rank = 1
|
||||||
for entry in current_list:
|
for entry in current_list:
|
||||||
if entry.player_id != player_id:
|
if entry.player_id != player_id:
|
||||||
draft_list_entries.append({
|
draft_list_entries.append(
|
||||||
'season': entry.season,
|
{
|
||||||
'team_id': entry.team_id,
|
"season": entry.season,
|
||||||
'player_id': entry.player_id,
|
"team_id": entry.team_id,
|
||||||
'rank': new_rank
|
"player_id": entry.player_id,
|
||||||
})
|
"rank": new_rank,
|
||||||
|
}
|
||||||
|
)
|
||||||
new_rank += 1
|
new_rank += 1
|
||||||
|
|
||||||
# POST updated list (bulk replacement)
|
# POST updated list (bulk replacement)
|
||||||
client = await self.get_client()
|
client = await self.get_client()
|
||||||
payload = {
|
payload = {
|
||||||
'count': len(draft_list_entries),
|
"count": len(draft_list_entries),
|
||||||
'draft_list': 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")
|
logger.info(f"Removed player {player_id} from team {team_id} draft list")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -270,11 +273,7 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
logger.error(f"Error removing player {player_id} from draft list: {e}")
|
logger.error(f"Error removing player {player_id} from draft list: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def clear_list(
|
async def clear_list(self, season: int, team_id: int) -> bool:
|
||||||
self,
|
|
||||||
season: int,
|
|
||||||
team_id: int
|
|
||||||
) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Clear entire draft list for team.
|
Clear entire draft list for team.
|
||||||
|
|
||||||
@ -309,10 +308,7 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def reorder_list(
|
async def reorder_list(
|
||||||
self,
|
self, season: int, team_id: int, new_order: List[int]
|
||||||
season: int,
|
|
||||||
team_id: int,
|
|
||||||
new_order: List[int]
|
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Reorder team's draft list.
|
Reorder team's draft list.
|
||||||
@ -342,21 +338,23 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
entry = entry_map[player_id]
|
entry = entry_map[player_id]
|
||||||
draft_list_entries.append({
|
draft_list_entries.append(
|
||||||
'season': entry.season,
|
{
|
||||||
'team_id': entry.team_id,
|
"season": entry.season,
|
||||||
'player_id': entry.player_id,
|
"team_id": entry.team_id,
|
||||||
'rank': new_rank
|
"player_id": entry.player_id,
|
||||||
})
|
"rank": new_rank,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# POST reordered list (bulk replacement)
|
# POST reordered list (bulk replacement)
|
||||||
client = await self.get_client()
|
client = await self.get_client()
|
||||||
payload = {
|
payload = {
|
||||||
'count': len(draft_list_entries),
|
"count": len(draft_list_entries),
|
||||||
'draft_list': 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}")
|
logger.info(f"Reordered draft list for team {team_id}")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -365,12 +363,7 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
logger.error(f"Error reordering draft list for team {team_id}: {e}")
|
logger.error(f"Error reordering draft list for team {team_id}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def move_entry_up(
|
async def move_entry_up(self, season: int, team_id: int, player_id: int) -> bool:
|
||||||
self,
|
|
||||||
season: int,
|
|
||||||
team_id: int,
|
|
||||||
player_id: int
|
|
||||||
) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Move player up one position in draft list (higher priority).
|
Move player up one position in draft list (higher priority).
|
||||||
|
|
||||||
@ -403,7 +396,9 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Find entry above (rank - 1)
|
# 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:
|
if not above_entry:
|
||||||
logger.error(f"Could not find entry above rank {current_entry.rank}")
|
logger.error(f"Could not find entry above rank {current_entry.rank}")
|
||||||
return False
|
return False
|
||||||
@ -421,24 +416,26 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
# Keep existing rank
|
# Keep existing rank
|
||||||
new_rank = entry.rank
|
new_rank = entry.rank
|
||||||
|
|
||||||
draft_list_entries.append({
|
draft_list_entries.append(
|
||||||
'season': entry.season,
|
{
|
||||||
'team_id': entry.team_id,
|
"season": entry.season,
|
||||||
'player_id': entry.player_id,
|
"team_id": entry.team_id,
|
||||||
'rank': new_rank
|
"player_id": entry.player_id,
|
||||||
})
|
"rank": new_rank,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Sort by 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)
|
# POST updated list (bulk replacement)
|
||||||
client = await self.get_client()
|
client = await self.get_client()
|
||||||
payload = {
|
payload = {
|
||||||
'count': len(draft_list_entries),
|
"count": len(draft_list_entries),
|
||||||
'draft_list': 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}")
|
logger.info(f"Moved player {player_id} up to rank {current_entry.rank - 1}")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -447,12 +444,7 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
logger.error(f"Error moving player {player_id} up in draft list: {e}")
|
logger.error(f"Error moving player {player_id} up in draft list: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def move_entry_down(
|
async def move_entry_down(self, season: int, team_id: int, player_id: int) -> bool:
|
||||||
self,
|
|
||||||
season: int,
|
|
||||||
team_id: int,
|
|
||||||
player_id: int
|
|
||||||
) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Move player down one position in draft list (lower priority).
|
Move player down one position in draft list (lower priority).
|
||||||
|
|
||||||
@ -485,7 +477,9 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Find entry below (rank + 1)
|
# 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:
|
if not below_entry:
|
||||||
logger.error(f"Could not find entry below rank {current_entry.rank}")
|
logger.error(f"Could not find entry below rank {current_entry.rank}")
|
||||||
return False
|
return False
|
||||||
@ -503,25 +497,29 @@ class DraftListService(BaseService[DraftList]):
|
|||||||
# Keep existing rank
|
# Keep existing rank
|
||||||
new_rank = entry.rank
|
new_rank = entry.rank
|
||||||
|
|
||||||
draft_list_entries.append({
|
draft_list_entries.append(
|
||||||
'season': entry.season,
|
{
|
||||||
'team_id': entry.team_id,
|
"season": entry.season,
|
||||||
'player_id': entry.player_id,
|
"team_id": entry.team_id,
|
||||||
'rank': new_rank
|
"player_id": entry.player_id,
|
||||||
})
|
"rank": new_rank,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Sort by 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)
|
# POST updated list (bulk replacement)
|
||||||
client = await self.get_client()
|
client = await self.get_client()
|
||||||
payload = {
|
payload = {
|
||||||
'count': len(draft_list_entries),
|
"count": len(draft_list_entries),
|
||||||
'draft_list': 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} down to rank {current_entry.rank + 1}")
|
logger.info(
|
||||||
|
f"Moved player {player_id} down to rank {current_entry.rank + 1}"
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@ -3,13 +3,14 @@ Injury service for Discord Bot v2.0
|
|||||||
|
|
||||||
Handles injury-related operations including checking, creating, and clearing injuries.
|
Handles injury-related operations including checking, creating, and clearing injuries.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
|
|
||||||
from services.base_service import BaseService
|
from services.base_service import BaseService
|
||||||
from models.injury import Injury
|
from models.injury import Injury
|
||||||
|
|
||||||
logger = logging.getLogger(f'{__name__}.InjuryService')
|
logger = logging.getLogger(f"{__name__}.InjuryService")
|
||||||
|
|
||||||
|
|
||||||
class InjuryService(BaseService[Injury]):
|
class InjuryService(BaseService[Injury]):
|
||||||
@ -25,7 +26,7 @@ class InjuryService(BaseService[Injury]):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize injury service."""
|
"""Initialize injury service."""
|
||||||
super().__init__(Injury, 'injuries')
|
super().__init__(Injury, "injuries")
|
||||||
logger.debug("InjuryService initialized")
|
logger.debug("InjuryService initialized")
|
||||||
|
|
||||||
async def get_active_injury(self, player_id: int, season: int) -> Optional[Injury]:
|
async def get_active_injury(self, player_id: int, season: int) -> Optional[Injury]:
|
||||||
@ -41,25 +42,31 @@ class InjuryService(BaseService[Injury]):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
params = [
|
params = [
|
||||||
('player_id', str(player_id)),
|
("player_id", str(player_id)),
|
||||||
('season', str(season)),
|
("season", str(season)),
|
||||||
('is_active', 'true')
|
("is_active", "true"),
|
||||||
]
|
]
|
||||||
|
|
||||||
injuries = await self.get_all_items(params=params)
|
injuries = await self.get_all_items(params=params)
|
||||||
|
|
||||||
if injuries:
|
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]
|
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
|
return None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting active injury for player {player_id}: {e}")
|
logger.error(f"Error getting active injury for player {player_id}: {e}")
|
||||||
return None
|
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.
|
Get all injuries for a player in a specific season.
|
||||||
|
|
||||||
@ -72,13 +79,10 @@ class InjuryService(BaseService[Injury]):
|
|||||||
List of injuries for the player
|
List of injuries for the player
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
params = [
|
params = [("player_id", str(player_id)), ("season", str(season))]
|
||||||
('player_id', str(player_id)),
|
|
||||||
('season', str(season))
|
|
||||||
]
|
|
||||||
|
|
||||||
if active_only:
|
if active_only:
|
||||||
params.append(('is_active', 'true'))
|
params.append(("is_active", "true"))
|
||||||
|
|
||||||
injuries = await self.get_all_items(params=params)
|
injuries = await self.get_all_items(params=params)
|
||||||
logger.debug(f"Retrieved {len(injuries)} injuries for player {player_id}")
|
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}")
|
logger.error(f"Error getting injuries for player {player_id}: {e}")
|
||||||
return []
|
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.
|
Get all injuries for a team in a specific season.
|
||||||
|
|
||||||
@ -101,13 +107,10 @@ class InjuryService(BaseService[Injury]):
|
|||||||
List of injuries for the team
|
List of injuries for the team
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
params = [
|
params = [("team_id", str(team_id)), ("season", str(season))]
|
||||||
('team_id', str(team_id)),
|
|
||||||
('season', str(season))
|
|
||||||
]
|
|
||||||
|
|
||||||
if active_only:
|
if active_only:
|
||||||
params.append(('is_active', 'true'))
|
params.append(("is_active", "true"))
|
||||||
|
|
||||||
injuries = await self.get_all_items(params=params)
|
injuries = await self.get_all_items(params=params)
|
||||||
logger.debug(f"Retrieved {len(injuries)} injuries for team {team_id}")
|
logger.debug(f"Retrieved {len(injuries)} injuries for team {team_id}")
|
||||||
@ -125,7 +128,7 @@ class InjuryService(BaseService[Injury]):
|
|||||||
start_week: int,
|
start_week: int,
|
||||||
start_game: int,
|
start_game: int,
|
||||||
end_week: int,
|
end_week: int,
|
||||||
end_game: int
|
end_game: int,
|
||||||
) -> Optional[Injury]:
|
) -> Optional[Injury]:
|
||||||
"""
|
"""
|
||||||
Create a new injury record.
|
Create a new injury record.
|
||||||
@ -144,22 +147,24 @@ class InjuryService(BaseService[Injury]):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
injury_data = {
|
injury_data = {
|
||||||
'season': season,
|
"season": season,
|
||||||
'player_id': player_id,
|
"player_id": player_id,
|
||||||
'total_games': total_games,
|
"total_games": total_games,
|
||||||
'start_week': start_week,
|
"start_week": start_week,
|
||||||
'start_game': start_game,
|
"start_game": start_game,
|
||||||
'end_week': end_week,
|
"end_week": end_week,
|
||||||
'end_game': end_game,
|
"end_game": end_game,
|
||||||
'is_active': True
|
"is_active": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Call the API to create the injury
|
# Call the API to create the injury
|
||||||
client = await self.get_client()
|
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:
|
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
|
return None
|
||||||
|
|
||||||
# Merge the request data with the response to ensure all required fields are present
|
# Merge the request data with the response to ensure all required fields are present
|
||||||
@ -187,7 +192,9 @@ class InjuryService(BaseService[Injury]):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Note: API expects is_active as query parameter, not JSON body
|
# 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:
|
if updated_injury:
|
||||||
logger.info(f"Cleared injury {injury_id}")
|
logger.info(f"Cleared injury {injury_id}")
|
||||||
@ -216,16 +223,18 @@ class InjuryService(BaseService[Injury]):
|
|||||||
try:
|
try:
|
||||||
client = await self.get_client()
|
client = await self.get_client()
|
||||||
params = [
|
params = [
|
||||||
('season', str(season)),
|
("season", str(season)),
|
||||||
('is_active', 'true'),
|
("is_active", "true"),
|
||||||
('sort', 'return-asc')
|
("sort", "return-asc"),
|
||||||
]
|
]
|
||||||
|
|
||||||
response = await client.get(self.endpoint, params=params)
|
response = await client.get(self.endpoint, params=params)
|
||||||
|
|
||||||
if response and 'injuries' in response:
|
if response and "injuries" in response:
|
||||||
logger.debug(f"Retrieved {len(response['injuries'])} active injuries for season {season}")
|
logger.debug(
|
||||||
return response['injuries']
|
f"Retrieved {len(response['injuries'])} active injuries for season {season}"
|
||||||
|
)
|
||||||
|
return response["injuries"]
|
||||||
|
|
||||||
logger.debug(f"No active injuries found for season {season}")
|
logger.debug(f"No active injuries found for season {season}")
|
||||||
return []
|
return []
|
||||||
|
|||||||
@ -248,7 +248,7 @@ class TransactionService(BaseService[Transaction]):
|
|||||||
|
|
||||||
# POST batch to API
|
# POST batch to API
|
||||||
client = await self.get_client()
|
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"
|
# 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)
|
# We need to return the original Transaction objects (they won't have IDs assigned by API)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Tests for BaseService functionality
|
Tests for BaseService functionality
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ from models.base import SBABaseModel
|
|||||||
|
|
||||||
class MockModel(SBABaseModel):
|
class MockModel(SBABaseModel):
|
||||||
"""Mock model for testing BaseService."""
|
"""Mock model for testing BaseService."""
|
||||||
|
|
||||||
id: int
|
id: int
|
||||||
name: str
|
name: str
|
||||||
value: int = 100
|
value: int = 100
|
||||||
@ -27,30 +29,30 @@ class TestBaseService:
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def base_service(self, mock_client):
|
def base_service(self, mock_client):
|
||||||
"""Create BaseService instance for testing."""
|
"""Create BaseService instance for testing."""
|
||||||
service = BaseService(MockModel, 'mocks', client=mock_client)
|
service = BaseService(MockModel, "mocks", client=mock_client)
|
||||||
return service
|
return service
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_init(self):
|
async def test_init(self):
|
||||||
"""Test service initialization."""
|
"""Test service initialization."""
|
||||||
service = BaseService(MockModel, 'test_endpoint')
|
service = BaseService(MockModel, "test_endpoint")
|
||||||
assert service.model_class == MockModel
|
assert service.model_class == MockModel
|
||||||
assert service.endpoint == 'test_endpoint'
|
assert service.endpoint == "test_endpoint"
|
||||||
assert service._client is None
|
assert service._client is None
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_by_id_success(self, base_service, mock_client):
|
async def test_get_by_id_success(self, base_service, mock_client):
|
||||||
"""Test successful get_by_id."""
|
"""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
|
mock_client.get.return_value = mock_data
|
||||||
|
|
||||||
result = await base_service.get_by_id(1)
|
result = await base_service.get_by_id(1)
|
||||||
|
|
||||||
assert isinstance(result, MockModel)
|
assert isinstance(result, MockModel)
|
||||||
assert result.id == 1
|
assert result.id == 1
|
||||||
assert result.name == 'Test'
|
assert result.name == "Test"
|
||||||
assert result.value == 200
|
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
|
@pytest.mark.asyncio
|
||||||
async def test_get_by_id_not_found(self, base_service, mock_client):
|
async def test_get_by_id_not_found(self, base_service, mock_client):
|
||||||
@ -60,17 +62,17 @@ class TestBaseService:
|
|||||||
result = await base_service.get_by_id(999)
|
result = await base_service.get_by_id(999)
|
||||||
|
|
||||||
assert result is None
|
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
|
@pytest.mark.asyncio
|
||||||
async def test_get_all_with_count(self, base_service, mock_client):
|
async def test_get_all_with_count(self, base_service, mock_client):
|
||||||
"""Test get_all with count response format."""
|
"""Test get_all with count response format."""
|
||||||
mock_data = {
|
mock_data = {
|
||||||
'count': 2,
|
"count": 2,
|
||||||
'mocks': [
|
"mocks": [
|
||||||
{'id': 1, 'name': 'Test1', 'value': 100},
|
{"id": 1, "name": "Test1", "value": 100},
|
||||||
{'id': 2, 'name': 'Test2', 'value': 200}
|
{"id": 2, "name": "Test2", "value": 200},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
mock_client.get.return_value = mock_data
|
mock_client.get.return_value = mock_data
|
||||||
|
|
||||||
@ -79,15 +81,12 @@ class TestBaseService:
|
|||||||
assert len(result) == 2
|
assert len(result) == 2
|
||||||
assert count == 2
|
assert count == 2
|
||||||
assert all(isinstance(item, MockModel) for item in result)
|
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
|
@pytest.mark.asyncio
|
||||||
async def test_get_all_items_convenience(self, base_service, mock_client):
|
async def test_get_all_items_convenience(self, base_service, mock_client):
|
||||||
"""Test get_all_items convenience method."""
|
"""Test get_all_items convenience method."""
|
||||||
mock_data = {
|
mock_data = {"count": 1, "mocks": [{"id": 1, "name": "Test", "value": 100}]}
|
||||||
'count': 1,
|
|
||||||
'mocks': [{'id': 1, 'name': 'Test', 'value': 100}]
|
|
||||||
}
|
|
||||||
mock_client.get.return_value = mock_data
|
mock_client.get.return_value = mock_data
|
||||||
|
|
||||||
result = await base_service.get_all_items()
|
result = await base_service.get_all_items()
|
||||||
@ -98,29 +97,29 @@ class TestBaseService:
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_create_success(self, base_service, mock_client):
|
async def test_create_success(self, base_service, mock_client):
|
||||||
"""Test successful object creation."""
|
"""Test successful object creation."""
|
||||||
input_data = {'name': 'New Item', 'value': 300}
|
input_data = {"name": "New Item", "value": 300}
|
||||||
response_data = {'id': 3, 'name': 'New Item', 'value': 300}
|
response_data = {"id": 3, "name": "New Item", "value": 300}
|
||||||
mock_client.post.return_value = response_data
|
mock_client.post.return_value = response_data
|
||||||
|
|
||||||
result = await base_service.create(input_data)
|
result = await base_service.create(input_data)
|
||||||
|
|
||||||
assert isinstance(result, MockModel)
|
assert isinstance(result, MockModel)
|
||||||
assert result.id == 3
|
assert result.id == 3
|
||||||
assert result.name == 'New Item'
|
assert result.name == "New Item"
|
||||||
mock_client.post.assert_called_once_with('mocks', input_data)
|
mock_client.post.assert_called_once_with("mocks/", input_data)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_update_success(self, base_service, mock_client):
|
async def test_update_success(self, base_service, mock_client):
|
||||||
"""Test successful object update."""
|
"""Test successful object update."""
|
||||||
update_data = {'name': 'Updated'}
|
update_data = {"name": "Updated"}
|
||||||
response_data = {'id': 1, 'name': 'Updated', 'value': 100}
|
response_data = {"id": 1, "name": "Updated", "value": 100}
|
||||||
mock_client.put.return_value = response_data
|
mock_client.put.return_value = response_data
|
||||||
|
|
||||||
result = await base_service.update(1, update_data)
|
result = await base_service.update(1, update_data)
|
||||||
|
|
||||||
assert isinstance(result, MockModel)
|
assert isinstance(result, MockModel)
|
||||||
assert result.name == 'Updated'
|
assert result.name == "Updated"
|
||||||
mock_client.put.assert_called_once_with('mocks', update_data, object_id=1)
|
mock_client.put.assert_called_once_with("mocks", update_data, object_id=1)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_delete_success(self, base_service, mock_client):
|
async def test_delete_success(self, base_service, mock_client):
|
||||||
@ -130,43 +129,39 @@ class TestBaseService:
|
|||||||
result = await base_service.delete(1)
|
result = await base_service.delete(1)
|
||||||
|
|
||||||
assert result is True
|
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
|
@pytest.mark.asyncio
|
||||||
async def test_get_by_field(self, base_service, mock_client):
|
async def test_get_by_field(self, base_service, mock_client):
|
||||||
"""Test get_by_field functionality."""
|
"""Test get_by_field functionality."""
|
||||||
mock_data = {
|
mock_data = {"count": 1, "mocks": [{"id": 1, "name": "Test", "value": 100}]}
|
||||||
'count': 1,
|
|
||||||
'mocks': [{'id': 1, 'name': 'Test', 'value': 100}]
|
|
||||||
}
|
|
||||||
mock_client.get.return_value = mock_data
|
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
|
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):
|
def test_extract_items_and_count_standard_format(self, base_service):
|
||||||
"""Test response parsing for standard format."""
|
"""Test response parsing for standard format."""
|
||||||
data = {
|
data = {
|
||||||
'count': 3,
|
"count": 3,
|
||||||
'mocks': [
|
"mocks": [
|
||||||
{'id': 1, 'name': 'Test1'},
|
{"id": 1, "name": "Test1"},
|
||||||
{'id': 2, 'name': 'Test2'},
|
{"id": 2, "name": "Test2"},
|
||||||
{'id': 3, 'name': 'Test3'}
|
{"id": 3, "name": "Test3"},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
items, count = base_service._extract_items_and_count_from_response(data)
|
items, count = base_service._extract_items_and_count_from_response(data)
|
||||||
|
|
||||||
assert len(items) == 3
|
assert len(items) == 3
|
||||||
assert count == 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):
|
def test_extract_items_and_count_single_object(self, base_service):
|
||||||
"""Test response parsing for single object."""
|
"""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)
|
items, count = base_service._extract_items_and_count_from_response(data)
|
||||||
|
|
||||||
@ -176,10 +171,7 @@ class TestBaseService:
|
|||||||
|
|
||||||
def test_extract_items_and_count_direct_list(self, base_service):
|
def test_extract_items_and_count_direct_list(self, base_service):
|
||||||
"""Test response parsing for direct list."""
|
"""Test response parsing for direct list."""
|
||||||
data = [
|
data = [{"id": 1, "name": "Test1"}, {"id": 2, "name": "Test2"}]
|
||||||
{'id': 1, 'name': 'Test1'},
|
|
||||||
{'id': 2, 'name': 'Test2'}
|
|
||||||
]
|
|
||||||
|
|
||||||
items, count = base_service._extract_items_and_count_from_response(data)
|
items, count = base_service._extract_items_and_count_from_response(data)
|
||||||
|
|
||||||
@ -201,13 +193,12 @@ class TestBaseServiceExtras:
|
|||||||
value: int = 100
|
value: int = 100
|
||||||
|
|
||||||
mock_client = AsyncMock()
|
mock_client = AsyncMock()
|
||||||
service = BaseService(TestModel, 'test', client=mock_client)
|
service = BaseService(TestModel, "test", client=mock_client)
|
||||||
|
|
||||||
|
|
||||||
# Test count method
|
# Test count method
|
||||||
mock_client.reset_mock()
|
mock_client.reset_mock()
|
||||||
mock_client.get.return_value = {'count': 42, 'test': []}
|
mock_client.get.return_value = {"count": 42, "test": []}
|
||||||
count = await service.count(params=[('active', 'true')])
|
count = await service.count(params=[("active", "true")])
|
||||||
assert count == 42
|
assert count == 42
|
||||||
|
|
||||||
# Test update_from_model with ID
|
# Test update_from_model with ID
|
||||||
@ -230,22 +221,22 @@ class TestBaseServiceExtras:
|
|||||||
class TestModel(SBABaseModel):
|
class TestModel(SBABaseModel):
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
service = BaseService(TestModel, 'test')
|
service = BaseService(TestModel, "test")
|
||||||
|
|
||||||
# Test with 'items' field
|
# 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)
|
items, count = service._extract_items_and_count_from_response(data)
|
||||||
assert len(items) == 2
|
assert len(items) == 2
|
||||||
assert count == 2
|
assert count == 2
|
||||||
|
|
||||||
# Test with 'data' field
|
# 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)
|
items, count = service._extract_items_and_count_from_response(data)
|
||||||
assert len(items) == 1
|
assert len(items) == 1
|
||||||
assert count == 1
|
assert count == 1
|
||||||
|
|
||||||
# Test with count but no recognizable list field
|
# 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)
|
items, count = service._extract_items_and_count_from_response(data)
|
||||||
assert len(items) == 0
|
assert len(items) == 0
|
||||||
assert count == 5
|
assert count == 5
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user