Add draft pause/resume functionality
- Add paused field to DraftData model - Add pause_draft() and resume_draft() methods to DraftService - Add /draft-admin pause and /draft-admin resume commands - Block picks in /draft command when draft is paused - Skip auto-draft in draft_monitor when draft is paused - Update status embeds to show paused state - Add comprehensive tests for pause/resume When paused: - Timer is stopped (set to False) - Deadline is set far in future - All /draft picks are blocked - Auto-draft monitor skips processing When resumed: - Timer is restarted with fresh deadline - Picks are allowed again 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
647ae1ca15
commit
ff62529ee3
@ -336,6 +336,108 @@ class DraftAdminGroup(app_commands.Group):
|
|||||||
embed = EmbedTemplate.success("Deadline Reset", description)
|
embed = EmbedTemplate.success("Deadline Reset", description)
|
||||||
await interaction.followup.send(embed=embed)
|
await interaction.followup.send(embed=embed)
|
||||||
|
|
||||||
|
@app_commands.command(name="pause", description="Pause the draft (block all picks)")
|
||||||
|
@league_admin_only()
|
||||||
|
@logged_command("/draft-admin pause")
|
||||||
|
async def draft_admin_pause(self, interaction: discord.Interaction):
|
||||||
|
"""Pause the draft, blocking all manual and auto-draft picks."""
|
||||||
|
await interaction.response.defer()
|
||||||
|
|
||||||
|
# Get draft data
|
||||||
|
draft_data = await draft_service.get_draft_data()
|
||||||
|
if not draft_data:
|
||||||
|
embed = EmbedTemplate.error(
|
||||||
|
"Draft Not Found",
|
||||||
|
"Could not retrieve draft configuration."
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if already paused
|
||||||
|
if draft_data.paused:
|
||||||
|
embed = EmbedTemplate.warning(
|
||||||
|
"Already Paused",
|
||||||
|
"The draft is already paused."
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Pause the draft
|
||||||
|
updated = await draft_service.pause_draft(draft_data.id)
|
||||||
|
|
||||||
|
if not updated:
|
||||||
|
embed = EmbedTemplate.error(
|
||||||
|
"Pause Failed",
|
||||||
|
"Failed to pause the draft."
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Success message
|
||||||
|
description = (
|
||||||
|
"The draft has been **paused**.\n\n"
|
||||||
|
"**Effects:**\n"
|
||||||
|
"• All `/draft` picks are blocked\n"
|
||||||
|
"• Auto-draft will not fire\n"
|
||||||
|
"• Timer has been stopped\n\n"
|
||||||
|
"Use `/draft-admin resume` to restart the timer and allow picks."
|
||||||
|
)
|
||||||
|
|
||||||
|
embed = EmbedTemplate.warning("Draft Paused", description)
|
||||||
|
await interaction.followup.send(embed=embed)
|
||||||
|
|
||||||
|
@app_commands.command(name="resume", description="Resume the draft (allow picks)")
|
||||||
|
@league_admin_only()
|
||||||
|
@logged_command("/draft-admin resume")
|
||||||
|
async def draft_admin_resume(self, interaction: discord.Interaction):
|
||||||
|
"""Resume the draft, allowing manual and auto-draft picks again."""
|
||||||
|
await interaction.response.defer()
|
||||||
|
|
||||||
|
# Get draft data
|
||||||
|
draft_data = await draft_service.get_draft_data()
|
||||||
|
if not draft_data:
|
||||||
|
embed = EmbedTemplate.error(
|
||||||
|
"Draft Not Found",
|
||||||
|
"Could not retrieve draft configuration."
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if already unpaused
|
||||||
|
if not draft_data.paused:
|
||||||
|
embed = EmbedTemplate.warning(
|
||||||
|
"Not Paused",
|
||||||
|
"The draft is not currently paused."
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Resume the draft
|
||||||
|
updated = await draft_service.resume_draft(draft_data.id)
|
||||||
|
|
||||||
|
if not updated:
|
||||||
|
embed = EmbedTemplate.error(
|
||||||
|
"Resume Failed",
|
||||||
|
"Failed to resume the draft."
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Build success message
|
||||||
|
description = "The draft has been **resumed**.\n\nPicks are now allowed."
|
||||||
|
|
||||||
|
# Add timer info if active
|
||||||
|
if updated.timer and updated.pick_deadline:
|
||||||
|
deadline_timestamp = int(updated.pick_deadline.timestamp())
|
||||||
|
description += f"\n\n⏱️ **Timer Active** - Current deadline <t:{deadline_timestamp}:R>"
|
||||||
|
|
||||||
|
# Ensure monitor is running
|
||||||
|
monitor_status = self._ensure_monitor_running()
|
||||||
|
description += monitor_status
|
||||||
|
|
||||||
|
embed = EmbedTemplate.success("Draft Resumed", description)
|
||||||
|
await interaction.followup.send(embed=embed)
|
||||||
|
|
||||||
@app_commands.command(name="resync-sheet", description="Resync all picks to Google Sheet")
|
@app_commands.command(name="resync-sheet", description="Resync all picks to Google Sheet")
|
||||||
@league_admin_only()
|
@league_admin_only()
|
||||||
@logged_command("/draft-admin resync-sheet")
|
@logged_command("/draft-admin resync-sheet")
|
||||||
|
|||||||
@ -160,6 +160,15 @@ class DraftPicksCog(commands.Cog):
|
|||||||
await interaction.followup.send(embed=embed)
|
await interaction.followup.send(embed=embed)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Check if draft is paused
|
||||||
|
if draft_data.paused:
|
||||||
|
embed = await create_pick_illegal_embed(
|
||||||
|
"Draft Paused",
|
||||||
|
"The draft is currently paused. Please wait for an administrator to resume."
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed)
|
||||||
|
return
|
||||||
|
|
||||||
# Get current pick
|
# Get current pick
|
||||||
current_pick = await draft_pick_service.get_pick(
|
current_pick = await draft_pick_service.get_pick(
|
||||||
config.sba_season,
|
config.sba_season,
|
||||||
|
|||||||
@ -15,6 +15,7 @@ class DraftData(SBABaseModel):
|
|||||||
|
|
||||||
currentpick: int = Field(0, description="Current pick number in progress")
|
currentpick: int = Field(0, description="Current pick number in progress")
|
||||||
timer: bool = Field(False, description="Whether draft timer is active")
|
timer: bool = Field(False, description="Whether draft timer is active")
|
||||||
|
paused: bool = Field(False, description="Whether draft is paused (blocks all picks)")
|
||||||
pick_deadline: Optional[datetime] = Field(None, description="Deadline for current pick")
|
pick_deadline: Optional[datetime] = Field(None, description="Deadline for current pick")
|
||||||
result_channel: Optional[int] = Field(None, description="Discord channel ID for draft results")
|
result_channel: Optional[int] = Field(None, description="Discord channel ID for draft results")
|
||||||
ping_channel: Optional[int] = Field(None, description="Discord channel ID for draft pings")
|
ping_channel: Optional[int] = Field(None, description="Discord channel ID for draft pings")
|
||||||
@ -32,16 +33,26 @@ class DraftData(SBABaseModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_draft_active(self) -> bool:
|
def is_draft_active(self) -> bool:
|
||||||
"""Check if the draft is currently active."""
|
"""Check if the draft is currently active (timer running and not paused)."""
|
||||||
return self.timer
|
return self.timer and not self.paused
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_pick_expired(self) -> bool:
|
def is_pick_expired(self) -> bool:
|
||||||
"""Check if the current pick deadline has passed."""
|
"""Check if the current pick deadline has passed."""
|
||||||
if not self.pick_deadline:
|
if not self.pick_deadline:
|
||||||
return False
|
return False
|
||||||
return datetime.now() > self.pick_deadline
|
return datetime.now() > self.pick_deadline
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_make_picks(self) -> bool:
|
||||||
|
"""Check if picks are allowed (not paused)."""
|
||||||
|
return not self.paused
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
status = "Active" if self.is_draft_active else "Inactive"
|
if self.paused:
|
||||||
|
status = "PAUSED"
|
||||||
|
elif self.timer:
|
||||||
|
status = "Active"
|
||||||
|
else:
|
||||||
|
status = "Inactive"
|
||||||
return f"Draft {status}: Pick {self.currentpick} ({self.pick_minutes}min timer)"
|
return f"Draft {status}: Pick {self.currentpick} ({self.pick_minutes}min timer)"
|
||||||
@ -338,6 +338,82 @@ class DraftService(BaseService[DraftData]):
|
|||||||
logger.error(f"Error resetting draft deadline: {e}")
|
logger.error(f"Error resetting draft deadline: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
async def pause_draft(self, draft_id: int) -> Optional[DraftData]:
|
||||||
|
"""
|
||||||
|
Pause the draft, blocking all picks (manual and auto) and stopping the timer.
|
||||||
|
|
||||||
|
When paused:
|
||||||
|
- /draft command will reject picks with "Draft is paused" message
|
||||||
|
- Auto-draft monitor will skip auto-drafting
|
||||||
|
- Timer is stopped (deadline set far in future)
|
||||||
|
- On resume, timer will restart with fresh deadline
|
||||||
|
|
||||||
|
Args:
|
||||||
|
draft_id: DraftData database ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated DraftData with paused=True and timer stopped
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Pause the draft AND stop the timer
|
||||||
|
# Set deadline far in future so it doesn't expire while paused
|
||||||
|
updates = {
|
||||||
|
'paused': True,
|
||||||
|
'timer': False,
|
||||||
|
'pick_deadline': datetime.now() + timedelta(days=690)
|
||||||
|
}
|
||||||
|
updated = await self.update_draft_data(draft_id, updates)
|
||||||
|
|
||||||
|
if updated:
|
||||||
|
logger.info("Draft paused - all picks blocked and timer stopped")
|
||||||
|
else:
|
||||||
|
logger.error("Failed to pause draft")
|
||||||
|
|
||||||
|
return updated
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error pausing draft: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def resume_draft(self, draft_id: int) -> Optional[DraftData]:
|
||||||
|
"""
|
||||||
|
Resume the draft, allowing picks again and restarting the timer.
|
||||||
|
|
||||||
|
When resumed:
|
||||||
|
- Timer is restarted with fresh deadline based on pick_minutes
|
||||||
|
- All picks (manual and auto) are allowed again
|
||||||
|
|
||||||
|
Args:
|
||||||
|
draft_id: DraftData database ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated DraftData with paused=False and timer restarted
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get current draft data to get pick_minutes setting
|
||||||
|
current_data = await self.get_draft_data()
|
||||||
|
pick_minutes = current_data.pick_minutes if current_data else 2
|
||||||
|
|
||||||
|
# Resume the draft AND restart the timer with fresh deadline
|
||||||
|
new_deadline = datetime.now() + timedelta(minutes=pick_minutes)
|
||||||
|
updates = {
|
||||||
|
'paused': False,
|
||||||
|
'timer': True,
|
||||||
|
'pick_deadline': new_deadline
|
||||||
|
}
|
||||||
|
updated = await self.update_draft_data(draft_id, updates)
|
||||||
|
|
||||||
|
if updated:
|
||||||
|
logger.info(f"Draft resumed - timer restarted with {pick_minutes}min deadline")
|
||||||
|
else:
|
||||||
|
logger.error("Failed to resume draft")
|
||||||
|
|
||||||
|
return updated
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error resuming draft: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Global service instance
|
# Global service instance
|
||||||
draft_service = DraftService()
|
draft_service = DraftService()
|
||||||
|
|||||||
@ -100,6 +100,11 @@ class DraftMonitorTask:
|
|||||||
self.monitor_loop.cancel()
|
self.monitor_loop.cancel()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# CRITICAL: Skip auto-draft if paused (but keep monitoring)
|
||||||
|
if draft_data.paused:
|
||||||
|
self.logger.debug("Draft is paused - skipping auto-draft actions")
|
||||||
|
return
|
||||||
|
|
||||||
# Check if we need to take action
|
# Check if we need to take action
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
deadline = draft_data.pick_deadline
|
deadline = draft_data.pick_deadline
|
||||||
|
|||||||
@ -39,13 +39,14 @@ def create_draft_data(**overrides) -> dict:
|
|||||||
"""
|
"""
|
||||||
Create complete draft data matching API response format.
|
Create complete draft data matching API response format.
|
||||||
|
|
||||||
API returns: id, currentpick, timer, pick_deadline, result_channel,
|
API returns: id, currentpick, timer, paused, pick_deadline, result_channel,
|
||||||
ping_channel, pick_minutes
|
ping_channel, pick_minutes
|
||||||
"""
|
"""
|
||||||
base_data = {
|
base_data = {
|
||||||
'id': 1,
|
'id': 1,
|
||||||
'currentpick': 25,
|
'currentpick': 25,
|
||||||
'timer': True,
|
'timer': True,
|
||||||
|
'paused': False, # New field for draft pause feature
|
||||||
'pick_deadline': (datetime.now() + timedelta(minutes=10)).isoformat(),
|
'pick_deadline': (datetime.now() + timedelta(minutes=10)).isoformat(),
|
||||||
'result_channel': '123456789012345678', # API returns as string
|
'result_channel': '123456789012345678', # API returns as string
|
||||||
'ping_channel': '987654321098765432', # API returns as string
|
'ping_channel': '987654321098765432', # API returns as string
|
||||||
@ -450,6 +451,160 @@ class TestDraftService:
|
|||||||
assert patch_call[0][1]['ping_channel'] == 111111111111111111
|
assert patch_call[0][1]['ping_channel'] == 111111111111111111
|
||||||
assert patch_call[0][1]['result_channel'] == 222222222222222222
|
assert patch_call[0][1]['result_channel'] == 222222222222222222
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# pause_draft() tests
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_pause_draft_success(self, service, mock_client):
|
||||||
|
"""
|
||||||
|
Test successfully pausing the draft.
|
||||||
|
|
||||||
|
Verifies:
|
||||||
|
- PATCH is called with paused=True, timer=False, and far-future deadline
|
||||||
|
- Updated draft data with paused=True is returned
|
||||||
|
- Timer is stopped when draft is paused (prevents deadline expiry during pause)
|
||||||
|
"""
|
||||||
|
updated_data = create_draft_data(paused=True, timer=False)
|
||||||
|
mock_client.patch.return_value = updated_data
|
||||||
|
|
||||||
|
result = await service.pause_draft(draft_id=1)
|
||||||
|
|
||||||
|
assert result is not None
|
||||||
|
assert result.paused is True
|
||||||
|
assert result.timer is False
|
||||||
|
|
||||||
|
# Verify PATCH was called with all pause-related updates
|
||||||
|
patch_call = mock_client.patch.call_args
|
||||||
|
patch_data = patch_call[0][1]
|
||||||
|
assert patch_data['paused'] is True
|
||||||
|
assert patch_data['timer'] is False
|
||||||
|
assert 'pick_deadline' in patch_data # Far-future deadline set
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_pause_draft_failure(self, service, mock_client):
|
||||||
|
"""
|
||||||
|
Test handling of failed pause operation.
|
||||||
|
|
||||||
|
Verifies service returns None when PATCH fails.
|
||||||
|
"""
|
||||||
|
mock_client.patch.return_value = None
|
||||||
|
|
||||||
|
result = await service.pause_draft(draft_id=1)
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_pause_draft_api_error(self, service, mock_client):
|
||||||
|
"""
|
||||||
|
Test error handling when pause API call fails.
|
||||||
|
|
||||||
|
Verifies service returns None on exception rather than crashing.
|
||||||
|
"""
|
||||||
|
mock_client.patch.side_effect = Exception("API unavailable")
|
||||||
|
|
||||||
|
result = await service.pause_draft(draft_id=1)
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# resume_draft() tests
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_resume_draft_success(self, service, mock_client):
|
||||||
|
"""
|
||||||
|
Test successfully resuming the draft.
|
||||||
|
|
||||||
|
Verifies:
|
||||||
|
- Current draft data is fetched to get pick_minutes
|
||||||
|
- PATCH is called with paused=False, timer=True, and fresh deadline
|
||||||
|
- Timer is restarted when draft is resumed
|
||||||
|
"""
|
||||||
|
# First call: get_draft_data to fetch pick_minutes
|
||||||
|
current_data = create_draft_data(paused=True, timer=False, pick_minutes=5)
|
||||||
|
mock_client.get.return_value = {'count': 1, 'draftdata': [current_data]}
|
||||||
|
|
||||||
|
# Second call: patch returns updated data
|
||||||
|
updated_data = create_draft_data(paused=False, timer=True, pick_minutes=5)
|
||||||
|
mock_client.patch.return_value = updated_data
|
||||||
|
|
||||||
|
result = await service.resume_draft(draft_id=1)
|
||||||
|
|
||||||
|
assert result is not None
|
||||||
|
assert result.paused is False
|
||||||
|
assert result.timer is True
|
||||||
|
|
||||||
|
# Verify PATCH was called with all resume-related updates
|
||||||
|
patch_call = mock_client.patch.call_args
|
||||||
|
patch_data = patch_call[0][1]
|
||||||
|
assert patch_data['paused'] is False
|
||||||
|
assert patch_data['timer'] is True
|
||||||
|
assert 'pick_deadline' in patch_data # Fresh deadline set
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_resume_draft_failure(self, service, mock_client):
|
||||||
|
"""
|
||||||
|
Test handling of failed resume operation.
|
||||||
|
|
||||||
|
Verifies service returns None when PATCH fails.
|
||||||
|
"""
|
||||||
|
# First call: get_draft_data succeeds
|
||||||
|
current_data = create_draft_data(paused=True, timer=False)
|
||||||
|
mock_client.get.return_value = {'count': 1, 'draftdata': [current_data]}
|
||||||
|
|
||||||
|
# PATCH fails
|
||||||
|
mock_client.patch.return_value = None
|
||||||
|
|
||||||
|
result = await service.resume_draft(draft_id=1)
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_resume_draft_api_error(self, service, mock_client):
|
||||||
|
"""
|
||||||
|
Test error handling when resume API call fails.
|
||||||
|
|
||||||
|
Verifies service returns None on exception rather than crashing.
|
||||||
|
"""
|
||||||
|
# First call: get_draft_data succeeds
|
||||||
|
current_data = create_draft_data(paused=True, timer=False)
|
||||||
|
mock_client.get.return_value = {'count': 1, 'draftdata': [current_data]}
|
||||||
|
|
||||||
|
# PATCH fails with exception
|
||||||
|
mock_client.patch.side_effect = Exception("API unavailable")
|
||||||
|
|
||||||
|
result = await service.resume_draft(draft_id=1)
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_pause_resume_roundtrip(self, service, mock_client):
|
||||||
|
"""
|
||||||
|
Test pausing and then resuming the draft.
|
||||||
|
|
||||||
|
Verifies the complete pause/resume workflow:
|
||||||
|
1. Pause stops the timer
|
||||||
|
2. Resume restarts the timer with fresh deadline
|
||||||
|
"""
|
||||||
|
# First pause - timer should be stopped
|
||||||
|
paused_data = create_draft_data(paused=True, timer=False)
|
||||||
|
mock_client.patch.return_value = paused_data
|
||||||
|
|
||||||
|
pause_result = await service.pause_draft(draft_id=1)
|
||||||
|
assert pause_result.paused is True
|
||||||
|
assert pause_result.timer is False
|
||||||
|
|
||||||
|
# Then resume - timer should be restarted
|
||||||
|
# resume_draft first fetches current data to get pick_minutes
|
||||||
|
mock_client.get.return_value = {'count': 1, 'draftdata': [paused_data]}
|
||||||
|
resumed_data = create_draft_data(paused=False, timer=True)
|
||||||
|
mock_client.patch.return_value = resumed_data
|
||||||
|
|
||||||
|
resume_result = await service.resume_draft(draft_id=1)
|
||||||
|
assert resume_result.paused is False
|
||||||
|
assert resume_result.timer is True
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# DraftPickService Tests
|
# DraftPickService Tests
|
||||||
@ -1362,6 +1517,72 @@ class TestDraftDataModel:
|
|||||||
assert active.is_draft_active is True
|
assert active.is_draft_active is True
|
||||||
assert inactive.is_draft_active is False
|
assert inactive.is_draft_active is False
|
||||||
|
|
||||||
|
def test_is_draft_active_when_paused(self):
|
||||||
|
"""
|
||||||
|
Test that is_draft_active returns False when draft is paused.
|
||||||
|
|
||||||
|
Even if timer is True, is_draft_active should be False when paused
|
||||||
|
because no picks should be processed.
|
||||||
|
"""
|
||||||
|
paused_with_timer = DraftData(
|
||||||
|
id=1, currentpick=1, timer=True, paused=True, pick_minutes=2
|
||||||
|
)
|
||||||
|
paused_no_timer = DraftData(
|
||||||
|
id=1, currentpick=1, timer=False, paused=True, pick_minutes=2
|
||||||
|
)
|
||||||
|
active_not_paused = DraftData(
|
||||||
|
id=1, currentpick=1, timer=True, paused=False, pick_minutes=2
|
||||||
|
)
|
||||||
|
|
||||||
|
assert paused_with_timer.is_draft_active is False
|
||||||
|
assert paused_no_timer.is_draft_active is False
|
||||||
|
assert active_not_paused.is_draft_active is True
|
||||||
|
|
||||||
|
def test_can_make_picks_property(self):
|
||||||
|
"""
|
||||||
|
Test can_make_picks property correctly reflects pause state.
|
||||||
|
|
||||||
|
can_make_picks should be True only when not paused,
|
||||||
|
regardless of timer state.
|
||||||
|
"""
|
||||||
|
# Not paused - can make picks
|
||||||
|
not_paused = DraftData(
|
||||||
|
id=1, currentpick=1, timer=True, paused=False, pick_minutes=2
|
||||||
|
)
|
||||||
|
assert not_paused.can_make_picks is True
|
||||||
|
|
||||||
|
# Paused - cannot make picks
|
||||||
|
paused = DraftData(
|
||||||
|
id=1, currentpick=1, timer=True, paused=True, pick_minutes=2
|
||||||
|
)
|
||||||
|
assert paused.can_make_picks is False
|
||||||
|
|
||||||
|
# Not paused, timer off - can still make picks (manual draft)
|
||||||
|
manual_draft = DraftData(
|
||||||
|
id=1, currentpick=1, timer=False, paused=False, pick_minutes=2
|
||||||
|
)
|
||||||
|
assert manual_draft.can_make_picks is True
|
||||||
|
|
||||||
|
def test_draft_data_str_shows_paused_status(self):
|
||||||
|
"""
|
||||||
|
Test that __str__ displays paused status when draft is paused.
|
||||||
|
|
||||||
|
Users should clearly see when the draft is paused.
|
||||||
|
"""
|
||||||
|
paused = DraftData(
|
||||||
|
id=1, currentpick=25, timer=True, paused=True, pick_minutes=2
|
||||||
|
)
|
||||||
|
active = DraftData(
|
||||||
|
id=1, currentpick=25, timer=True, paused=False, pick_minutes=2
|
||||||
|
)
|
||||||
|
inactive = DraftData(
|
||||||
|
id=1, currentpick=25, timer=False, paused=False, pick_minutes=2
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "PAUSED" in str(paused)
|
||||||
|
assert "Active" in str(active)
|
||||||
|
assert "Inactive" in str(inactive)
|
||||||
|
|
||||||
def test_is_pick_expired_property(self):
|
def test_is_pick_expired_property(self):
|
||||||
"""Test is_pick_expired property."""
|
"""Test is_pick_expired property."""
|
||||||
# Expired deadline
|
# Expired deadline
|
||||||
|
|||||||
@ -125,10 +125,17 @@ async def create_draft_status_embed(
|
|||||||
Returns:
|
Returns:
|
||||||
Discord embed with draft status
|
Discord embed with draft status
|
||||||
"""
|
"""
|
||||||
embed = EmbedTemplate.info(
|
# Use warning color if paused
|
||||||
title="Draft Status",
|
if draft_data.paused:
|
||||||
description=f"Currently on {format_pick_display(draft_data.currentpick)}"
|
embed = EmbedTemplate.warning(
|
||||||
)
|
title="Draft Status - PAUSED",
|
||||||
|
description=f"Currently on {format_pick_display(draft_data.currentpick)}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
embed = EmbedTemplate.info(
|
||||||
|
title="Draft Status",
|
||||||
|
description=f"Currently on {format_pick_display(draft_data.currentpick)}"
|
||||||
|
)
|
||||||
|
|
||||||
# On the clock
|
# On the clock
|
||||||
if current_pick.owner:
|
if current_pick.owner:
|
||||||
@ -138,8 +145,13 @@ async def create_draft_status_embed(
|
|||||||
inline=True
|
inline=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Timer status
|
# Timer status (show paused state prominently)
|
||||||
timer_status = "✅ Active" if draft_data.timer else "⏹️ Inactive"
|
if draft_data.paused:
|
||||||
|
timer_status = "⏸️ PAUSED"
|
||||||
|
elif draft_data.timer:
|
||||||
|
timer_status = "✅ Active"
|
||||||
|
else:
|
||||||
|
timer_status = "⏹️ Inactive"
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="Timer",
|
name="Timer",
|
||||||
value=f"{timer_status} ({draft_data.pick_minutes} min)",
|
value=f"{timer_status} ({draft_data.pick_minutes} min)",
|
||||||
@ -161,6 +173,14 @@ async def create_draft_status_embed(
|
|||||||
inline=True
|
inline=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Pause status (if paused, show prominent warning)
|
||||||
|
if draft_data.paused:
|
||||||
|
embed.add_field(
|
||||||
|
name="Pause Status",
|
||||||
|
value="🚫 **Draft is paused** - No picks allowed until admin resumes",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
# Lock status
|
# Lock status
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="Lock Status",
|
name="Lock Status",
|
||||||
@ -427,11 +447,19 @@ async def create_admin_draft_info_embed(
|
|||||||
Returns:
|
Returns:
|
||||||
Discord embed with admin information
|
Discord embed with admin information
|
||||||
"""
|
"""
|
||||||
embed = EmbedTemplate.create_base_embed(
|
# Use warning color if paused
|
||||||
title="⚙️ Draft Administration",
|
if draft_data.paused:
|
||||||
description="Current draft configuration and state",
|
embed = EmbedTemplate.create_base_embed(
|
||||||
color=EmbedColors.INFO
|
title="⚙️ Draft Administration - PAUSED",
|
||||||
)
|
description="Current draft configuration and state",
|
||||||
|
color=EmbedColors.WARNING
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
embed = EmbedTemplate.create_base_embed(
|
||||||
|
title="⚙️ Draft Administration",
|
||||||
|
description="Current draft configuration and state",
|
||||||
|
color=EmbedColors.INFO
|
||||||
|
)
|
||||||
|
|
||||||
# Current pick
|
# Current pick
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
@ -440,11 +468,20 @@ async def create_admin_draft_info_embed(
|
|||||||
inline=True
|
inline=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Timer status
|
# Timer status (show paused prominently)
|
||||||
timer_emoji = "✅" if draft_data.timer else "⏹️"
|
if draft_data.paused:
|
||||||
|
timer_emoji = "⏸️"
|
||||||
|
timer_text = "PAUSED"
|
||||||
|
elif draft_data.timer:
|
||||||
|
timer_emoji = "✅"
|
||||||
|
timer_text = "Active"
|
||||||
|
else:
|
||||||
|
timer_emoji = "⏹️"
|
||||||
|
timer_text = "Inactive"
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="Timer Status",
|
name="Timer Status",
|
||||||
value=f"{timer_emoji} {'Active' if draft_data.timer else 'Inactive'}",
|
value=f"{timer_emoji} {timer_text}",
|
||||||
inline=True
|
inline=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -455,6 +492,14 @@ async def create_admin_draft_info_embed(
|
|||||||
inline=True
|
inline=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Pause status (prominent if paused)
|
||||||
|
if draft_data.paused:
|
||||||
|
embed.add_field(
|
||||||
|
name="Pause Status",
|
||||||
|
value="🚫 **PAUSED** - No picks allowed\nUse `/draft-admin resume` to allow picks",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
# Channels
|
# Channels
|
||||||
ping_channel_value = f"<#{draft_data.ping_channel}>" if draft_data.ping_channel else "Not configured"
|
ping_channel_value = f"<#{draft_data.ping_channel}>" if draft_data.ping_channel else "Not configured"
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user