"""Tests for BE-AGT-003 — multi-slot competition handling. Covers: - Winner selection (highest priority) - Remaining slots marked Deferred with priority += 1 - Priority capping at MAX_PRIORITY (99) - Empty input edge case - Single slot (no competition) - defer_all_slots when agent is not idle """ import pytest from datetime import date, time from app.models.calendar import SlotStatus, SlotType, TimeSlot from app.services.slot_competition import ( CompetitionResult, MAX_PRIORITY, defer_all_slots, resolve_slot_competition, ) def _make_slot(db, user_id: int, *, priority: int, status=SlotStatus.NOT_STARTED) -> TimeSlot: """Helper — create a minimal TimeSlot in the test DB.""" slot = TimeSlot( user_id=user_id, date=date(2026, 4, 1), slot_type=SlotType.WORK, estimated_duration=30, scheduled_at=time(9, 0), priority=priority, status=status, ) db.add(slot) db.flush() return slot # --------------------------------------------------------------------------- # resolve_slot_competition # --------------------------------------------------------------------------- class TestResolveSlotCompetition: """Tests for resolve_slot_competition.""" def test_empty_input(self, db, seed): result = resolve_slot_competition(db, []) assert result.winner is None assert result.deferred == [] def test_single_slot_no_competition(self, db, seed): slot = _make_slot(db, 1, priority=50) result = resolve_slot_competition(db, [slot]) assert result.winner is slot assert result.deferred == [] # Winner should NOT be modified assert slot.status == SlotStatus.NOT_STARTED assert slot.priority == 50 def test_winner_is_first_slot(self, db, seed): """Input is pre-sorted by priority desc; first slot wins.""" high = _make_slot(db, 1, priority=80) mid = _make_slot(db, 1, priority=50) low = _make_slot(db, 1, priority=10) slots = [high, mid, low] result = resolve_slot_competition(db, slots) assert result.winner is high assert len(result.deferred) == 2 assert mid in result.deferred assert low in result.deferred def test_deferred_slots_status_and_priority(self, db, seed): """Deferred slots get status=DEFERRED and priority += 1.""" winner = _make_slot(db, 1, priority=80) loser1 = _make_slot(db, 1, priority=50) loser2 = _make_slot(db, 1, priority=10) resolve_slot_competition(db, [winner, loser1, loser2]) # Winner untouched assert winner.status == SlotStatus.NOT_STARTED assert winner.priority == 80 # Losers deferred + bumped assert loser1.status == SlotStatus.DEFERRED assert loser1.priority == 51 assert loser2.status == SlotStatus.DEFERRED assert loser2.priority == 11 def test_priority_capped_at_max(self, db, seed): """Priority bump should not exceed MAX_PRIORITY.""" winner = _make_slot(db, 1, priority=99) at_cap = _make_slot(db, 1, priority=99) resolve_slot_competition(db, [winner, at_cap]) assert at_cap.status == SlotStatus.DEFERRED assert at_cap.priority == MAX_PRIORITY # stays at 99, not 100 def test_already_deferred_slots_get_bumped(self, db, seed): """Slots that were already DEFERRED still get priority bumped.""" winner = _make_slot(db, 1, priority=90) already_deferred = _make_slot(db, 1, priority=40, status=SlotStatus.DEFERRED) result = resolve_slot_competition(db, [winner, already_deferred]) assert already_deferred.status == SlotStatus.DEFERRED assert already_deferred.priority == 41 def test_tie_breaking_first_wins(self, db, seed): """When priorities are equal, the first in the list wins.""" a = _make_slot(db, 1, priority=50) b = _make_slot(db, 1, priority=50) result = resolve_slot_competition(db, [a, b]) assert result.winner is a assert b in result.deferred assert b.status == SlotStatus.DEFERRED # --------------------------------------------------------------------------- # defer_all_slots # --------------------------------------------------------------------------- class TestDeferAllSlots: """Tests for defer_all_slots (agent not idle).""" def test_empty_input(self, db, seed): result = defer_all_slots(db, []) assert result == [] def test_all_slots_deferred(self, db, seed): s1 = _make_slot(db, 1, priority=70) s2 = _make_slot(db, 1, priority=30) result = defer_all_slots(db, [s1, s2]) assert len(result) == 2 assert s1.status == SlotStatus.DEFERRED assert s1.priority == 71 assert s2.status == SlotStatus.DEFERRED assert s2.priority == 31 def test_priority_cap_in_defer_all(self, db, seed): s = _make_slot(db, 1, priority=99) defer_all_slots(db, [s]) assert s.priority == MAX_PRIORITY def test_already_deferred_still_bumped(self, db, seed): """Even if already DEFERRED, priority still increases.""" s = _make_slot(db, 1, priority=45, status=SlotStatus.DEFERRED) defer_all_slots(db, [s]) assert s.status == SlotStatus.DEFERRED assert s.priority == 46