feat: trigger variant card renders after post-game tier-ups

After refractor tier-ups, the bot hits the card render URL for each
variant card to trigger Playwright render + S3 upload as a side
effect. Fire-and-forget — failures are logged but never raised.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-04-06 17:10:13 -05:00
parent 9ee4a76cd6
commit 730d4b4f60
2 changed files with 95 additions and 0 deletions

View File

@ -1,5 +1,6 @@
import asyncio
import copy
import datetime
import logging
import discord
from discord import SelectOption
@ -4243,6 +4244,34 @@ async def get_game_summary_embed(
return game_embed
async def _trigger_variant_renders(tier_ups: list) -> None:
"""Fire-and-forget: hit card render URLs to trigger S3 upload for new variants.
Each tier-up with a variant_created value gets a GET request to the card
render endpoint, which triggers Playwright render + S3 upload as a side effect.
Failures are logged but never raised.
"""
today = datetime.date.today().isoformat()
for tier_up in tier_ups:
variant = tier_up.get("variant_created")
if not variant:
continue
player_id = tier_up["player_id"]
track = tier_up.get("track_name", "Batter")
card_type = "pitching" if track.lower() == "pitcher" else "batting"
try:
await db_get(
f"players/{player_id}/{card_type}card/{today}/{variant}",
none_okay=True,
)
except Exception:
logger.warning(
"Failed to trigger variant render for player %d variant %d (non-fatal)",
player_id,
variant,
)
async def complete_game(
session: Session,
interaction: discord.Interaction,
@ -4353,6 +4382,7 @@ async def complete_game(
if evo_result and evo_result.get("tier_ups"):
for tier_up in evo_result["tier_ups"]:
await notify_tier_completion(interaction.channel, tier_up)
await _trigger_variant_renders(evo_result["tier_ups"])
except Exception as e:
logger.warning(f"Post-game refractor processing failed (non-fatal): {e}")

View File

@ -0,0 +1,65 @@
"""Tests for post-game refractor card render trigger."""
from unittest.mock import AsyncMock, patch
import pytest
from command_logic.logic_gameplay import _trigger_variant_renders
class TestTriggerVariantRenders:
"""Fire-and-forget card render calls after tier-ups."""
@pytest.mark.asyncio
async def test_calls_render_url_for_each_tier_up(self):
"""Each tier-up with variant_created triggers a card render GET request."""
tier_ups = [
{"player_id": 100, "variant_created": 7, "track_name": "Batter"},
{"player_id": 200, "variant_created": 3, "track_name": "Pitcher"},
]
with patch(
"command_logic.logic_gameplay.db_get", new_callable=AsyncMock
) as mock_get:
mock_get.return_value = None
await _trigger_variant_renders(tier_ups)
assert mock_get.call_count == 2
call_args_list = [call.args[0] for call in mock_get.call_args_list]
assert any("100" in url and "7" in url for url in call_args_list)
assert any("200" in url and "3" in url for url in call_args_list)
@pytest.mark.asyncio
async def test_skips_tier_ups_without_variant(self):
"""Tier-ups without variant_created are skipped."""
tier_ups = [
{"player_id": 100, "track_name": "Batter"},
]
with patch(
"command_logic.logic_gameplay.db_get", new_callable=AsyncMock
) as mock_get:
await _trigger_variant_renders(tier_ups)
mock_get.assert_not_called()
@pytest.mark.asyncio
async def test_api_failure_does_not_raise(self):
"""Render trigger failures are swallowed — fire-and-forget."""
tier_ups = [
{"player_id": 100, "variant_created": 7, "track_name": "Batter"},
]
with patch(
"command_logic.logic_gameplay.db_get", new_callable=AsyncMock
) as mock_get:
mock_get.side_effect = Exception("API down")
await _trigger_variant_renders(tier_ups)
@pytest.mark.asyncio
async def test_empty_tier_ups_is_noop(self):
"""Empty tier_ups list does nothing."""
with patch(
"command_logic.logic_gameplay.db_get", new_callable=AsyncMock
) as mock_get:
await _trigger_variant_renders([])
mock_get.assert_not_called()