feat: enforce FA lock deadline after week 14 #122
@ -277,6 +277,35 @@ class TransactionBuilder:
|
||||
Returns:
|
||||
Tuple of (success: bool, error_message: str). If success is True, error_message is empty.
|
||||
"""
|
||||
# Fetch current state once if needed by FA lock or pending-transaction check
|
||||
is_fa_pickup = (
|
||||
move.from_roster == RosterType.FREE_AGENCY
|
||||
and move.to_roster != RosterType.FREE_AGENCY
|
||||
)
|
||||
needs_current_state = is_fa_pickup or (
|
||||
check_pending_transactions and next_week is None
|
||||
)
|
||||
|
||||
current_week: Optional[int] = None
|
||||
if needs_current_state:
|
||||
try:
|
||||
current_state = await league_service.get_current_state()
|
||||
current_week = current_state.week if current_state else 1
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not get current week: {e}")
|
||||
current_week = 1
|
||||
|
||||
# Block adding players FROM Free Agency after the FA lock deadline
|
||||
if is_fa_pickup and current_week is not None:
|
||||
config = get_config()
|
||||
if current_week >= config.fa_lock_week:
|
||||
error_msg = (
|
||||
f"Free agency is closed (week {current_week}, deadline was week {config.fa_lock_week}). "
|
||||
f"Cannot add {move.player.name} from FA."
|
||||
)
|
||||
logger.warning(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
# Check if player is already in a move in this transaction builder
|
||||
existing_move = self.get_move_for_player(move.player.id)
|
||||
if existing_move:
|
||||
@ -299,23 +328,15 @@ class TransactionBuilder:
|
||||
return False, error_msg
|
||||
|
||||
# Check if player is already in another team's pending transaction for next week
|
||||
# This prevents duplicate claims that would need to be resolved at freeze time
|
||||
# Only applies to /dropadd (scheduled moves), not /ilmove (immediate moves)
|
||||
if check_pending_transactions:
|
||||
if next_week is None:
|
||||
try:
|
||||
current_state = await league_service.get_current_state()
|
||||
next_week = (current_state.week + 1) if current_state else 1
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Could not get current week for pending transaction check: {e}"
|
||||
)
|
||||
next_week = 1
|
||||
next_week = (current_week + 1) if current_week else 1
|
||||
|
||||
is_pending, claiming_team = (
|
||||
await transaction_service.is_player_in_pending_transaction(
|
||||
player_id=move.player.id, week=next_week, season=self.season
|
||||
)
|
||||
(
|
||||
is_pending,
|
||||
claiming_team,
|
||||
) = await transaction_service.is_player_in_pending_transaction(
|
||||
player_id=move.player.id, week=next_week, season=self.season
|
||||
)
|
||||
|
||||
if is_pending:
|
||||
|
||||
@ -115,6 +115,13 @@ class TestTransactionBuilder:
|
||||
svc.get_current_roster.return_value = mock_roster
|
||||
return svc
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_league_service(self):
|
||||
"""Patch league_service for all tests so FA lock check uses week 10 (before deadline)."""
|
||||
with patch("services.transaction_builder.league_service") as mock_ls:
|
||||
mock_ls.get_current_state = AsyncMock(return_value=MagicMock(week=10))
|
||||
yield mock_ls
|
||||
|
||||
@pytest.fixture
|
||||
def builder(self, mock_team, mock_roster_service):
|
||||
"""Create a TransactionBuilder for testing with injected roster service."""
|
||||
@ -152,6 +159,50 @@ class TestTransactionBuilder:
|
||||
assert builder.is_empty is False
|
||||
assert move in builder.moves
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_move_from_fa_blocked_after_deadline(self, builder, mock_player):
|
||||
"""Test that adding a player FROM Free Agency is blocked after fa_lock_week."""
|
||||
move = TransactionMove(
|
||||
player=mock_player,
|
||||
from_roster=RosterType.FREE_AGENCY,
|
||||
to_roster=RosterType.MAJOR_LEAGUE,
|
||||
to_team=builder.team,
|
||||
)
|
||||
|
||||
with patch(
|
||||
"services.transaction_builder.league_service"
|
||||
) as mock_league_service:
|
||||
mock_league_service.get_current_state = AsyncMock(
|
||||
return_value=MagicMock(week=15)
|
||||
)
|
||||
|
||||
success, error_message = await builder.add_move(
|
||||
move, check_pending_transactions=False
|
||||
)
|
||||
|
||||
assert success is False
|
||||
assert "Free agency is closed" in error_message
|
||||
assert builder.move_count == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_drop_to_fa_allowed_after_deadline(self, builder, mock_player):
|
||||
"""Test that dropping a player TO Free Agency is still allowed after fa_lock_week."""
|
||||
move = TransactionMove(
|
||||
player=mock_player,
|
||||
from_roster=RosterType.MAJOR_LEAGUE,
|
||||
to_roster=RosterType.FREE_AGENCY,
|
||||
from_team=builder.team,
|
||||
)
|
||||
|
||||
# Drop to FA doesn't trigger the FA lock check (autouse fixture provides week 10)
|
||||
success, error_message = await builder.add_move(
|
||||
move, check_pending_transactions=False
|
||||
)
|
||||
|
||||
assert success is True
|
||||
assert error_message == ""
|
||||
assert builder.move_count == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_duplicate_move_fails(self, builder, mock_player):
|
||||
"""Test that adding duplicate moves for same player fails."""
|
||||
@ -809,6 +860,13 @@ class TestPendingTransactionValidation:
|
||||
"""Create a mock player for testing."""
|
||||
return Player(id=12472, name="Test Player", wara=2.5, season=12, pos_1="OF")
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_league_service(self):
|
||||
"""Patch league_service so FA lock check and week resolution use week 10."""
|
||||
with patch("services.transaction_builder.league_service") as mock_ls:
|
||||
mock_ls.get_current_state = AsyncMock(return_value=MagicMock(week=10))
|
||||
yield mock_ls
|
||||
|
||||
@pytest.fixture
|
||||
def builder(self, mock_team):
|
||||
"""Create a TransactionBuilder for testing."""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user