feat: add refractor-test execute phase with step-by-step reporting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-04-09 07:53:10 -05:00
parent 129971e96b
commit 9257852c3e
2 changed files with 298 additions and 2 deletions

View File

@ -245,8 +245,157 @@ class DevToolsCog(commands.Cog):
opposing_player_id: int,
num_plays: int,
):
"""Execute phase placeholder — implemented in Task 5."""
raise NotImplementedError("Execute phase not yet implemented")
"""Execute the refractor integration test chain.
Creates synthetic game data, runs the real refractor pipeline,
and reports pass/fail at each step. Stops on first failure.
"""
results = []
game_id = None
# Remove the "Executing..." field
if len(embed.fields) > 1:
embed.remove_field(len(embed.fields) - 1)
# Helper to update the embed with current results
async def update_embed(
final: bool = False, view: discord.ui.View | None = None
):
results_text = "\n".join(results)
# Remove old results field if present, add new one
while len(embed.fields) > 1:
embed.remove_field(len(embed.fields) - 1)
embed.add_field(name="Results", value=results_text, inline=False)
kwargs = {"embed": embed}
if view is not None:
kwargs["view"] = view
await interaction.edit_original_response(**kwargs)
try:
# Step 1: Create game
game_data = build_game_data(team_id=team_id, season=CURRENT_SEASON)
game_resp = await db_post("games", payload=game_data)
game_id = game_resp["id"]
results.append(f"✅ Game created (#{game_id})")
await update_embed()
except Exception as e:
results.append(f"❌ Game creation failed: {e}")
await update_embed()
return
try:
# Step 2: Create plays
if card_type == "batter":
plays = build_batter_plays(
game_id, player_id, team_id, opposing_player_id, num_plays
)
else:
plays = build_pitcher_plays(
game_id, player_id, team_id, opposing_player_id, num_plays
)
await db_post("plays", payload={"plays": plays})
results.append(f"{num_plays} plays inserted")
await update_embed()
except Exception as e:
results.append(f"❌ Play insertion failed: {e}")
await update_embed(
final=True, view=CleanupView(interaction.user.id, game_id, embed)
)
return
try:
# Step 3: Create pitcher decision
pitcher_id = opposing_player_id if card_type == "batter" else player_id
decision_data = build_decision_data(
game_id, pitcher_id, team_id, CURRENT_SEASON
)
await db_post("decisions", payload=decision_data)
results.append("✅ Pitcher decision inserted")
await update_embed()
except Exception as e:
results.append(f"❌ Decision insertion failed: {e}")
await update_embed(
final=True, view=CleanupView(interaction.user.id, game_id, embed)
)
return
try:
# Step 4: Update season stats
stats_resp = await db_post(f"season-stats/update-game/{game_id}")
if stats_resp and stats_resp.get("skipped"):
results.append("⚠️ Season stats skipped (already processed)")
else:
updated = stats_resp.get("updated", "?") if stats_resp else "?"
results.append(f"✅ Season stats updated ({updated} players)")
await update_embed()
except Exception as e:
results.append(f"❌ Season stats update failed: {e}")
results.append("⏭️ Skipped: evaluate-game (depends on season stats)")
await update_embed(
final=True, view=CleanupView(interaction.user.id, game_id, embed)
)
return
try:
# Step 5: Evaluate refractor
eval_resp = await db_post(f"refractor/evaluate-game/{game_id}")
tier_ups = eval_resp.get("tier_ups", []) if eval_resp else []
if tier_ups:
for tu in tier_ups:
old_t = tu.get("old_tier", "?")
new_t = tu.get("new_tier", "?")
variant = tu.get("variant_created", "")
results.append(f"✅ Tier-up detected! T{old_t} → T{new_t}")
if variant:
results.append(f"✅ Variant card created (variant: {variant})")
else:
evaluated = eval_resp.get("evaluated", "?") if eval_resp else "?"
results.append(f"⚠️ No tier-up detected (evaluated {evaluated} cards)")
await update_embed()
except Exception as e:
results.append(f"❌ Evaluate-game failed: {e}")
await update_embed(
final=True, view=CleanupView(interaction.user.id, game_id, embed)
)
return
# Step 6: Trigger card render (if tier-up)
if tier_ups:
for tu in tier_ups:
variant = tu.get("variant_created")
if not variant:
continue
try:
today = date.today().isoformat()
render_resp = await db_get(
f"players/{tu['player_id']}/{card_type_key}card/{today}/{variant}",
none_okay=True,
)
if render_resp:
results.append("✅ Card rendered + S3 upload triggered")
img_url = (
render_resp
if isinstance(render_resp, str)
else render_resp.get("image_url")
)
if (
img_url
and isinstance(img_url, str)
and img_url.startswith("http")
):
embed.set_image(url=img_url)
else:
results.append(
"⚠️ Card render returned no data (may still be processing)"
)
except Exception as e:
results.append(f"⚠️ Card render failed (non-fatal): {e}")
# Final update with cleanup buttons
await update_embed(
final=True, view=CleanupView(interaction.user.id, game_id, embed)
)
async def setup(bot: commands.Bot):

View File

@ -239,3 +239,150 @@ class TestRefractorTestSetup:
mock_exec.assert_not_called()
call_kwargs = mock_interaction.edit_original_response.call_args[1]
assert "not found" in call_kwargs["content"].lower()
class TestRefractorTestExecute:
"""Test the execution phase: API calls, step-by-step reporting,
stop-on-failure behavior."""
@pytest.fixture
def mock_interaction(self):
interaction = AsyncMock(spec=discord.Interaction)
interaction.user = MagicMock()
interaction.user.id = 12345
interaction.response = AsyncMock()
interaction.edit_original_response = AsyncMock()
return interaction
@pytest.fixture
def mock_bot(self):
return MagicMock(spec=commands.Bot)
@pytest.fixture
def base_embed(self):
embed = discord.Embed(title="Refractor Integration Test")
embed.add_field(name="Setup", value="test setup", inline=False)
embed.add_field(name="", value="⏳ Executing...", inline=False)
return embed
async def test_successful_batter_flow(self, mock_interaction, mock_bot, base_embed):
"""Full happy path: game created, plays inserted, stats updated,
tier-up detected, card rendered."""
from cogs.dev_tools import DevToolsCog
cog = DevToolsCog(mock_bot)
game_response = {"id": 42}
plays_response = {"count": 3}
decisions_response = {"count": 1}
stats_response = {"updated": 1, "skipped": False}
eval_response = {
"evaluated": 1,
"tier_ups": [
{
"player_id": 100,
"team_id": 31,
"player_name": "Mike Trout",
"old_tier": 0,
"new_tier": 1,
"current_value": 45,
"track_name": "Batter Track",
"variant_created": "abc123",
}
],
}
with (
patch("cogs.dev_tools.db_post", new_callable=AsyncMock) as mock_post,
patch("cogs.dev_tools.db_get", new_callable=AsyncMock) as mock_get,
):
mock_post.side_effect = [
game_response, # POST games
plays_response, # POST plays
decisions_response, # POST decisions
stats_response, # POST season-stats/update-game
eval_response, # POST refractor/evaluate-game
]
mock_get.return_value = {"image_url": "https://s3.example.com/card.png"}
await cog._execute_refractor_test(
interaction=mock_interaction,
embed=base_embed,
player_id=100,
player_name="Mike Trout",
team_id=31,
card_type="batter",
card_type_key="batting",
opposing_player_id=200,
num_plays=3,
)
assert mock_interaction.edit_original_response.call_count >= 1
final_call = mock_interaction.edit_original_response.call_args_list[-1]
final_embed = final_call[1]["embed"]
result_text = "\n".join(f.value for f in final_embed.fields if f.value)
assert "" in result_text
assert "game" in result_text.lower()
async def test_stops_on_game_creation_failure(
self, mock_interaction, mock_bot, base_embed
):
"""If game creation fails, stop immediately and show error."""
from cogs.dev_tools import DevToolsCog
cog = DevToolsCog(mock_bot)
with patch(
"cogs.dev_tools.db_post",
new_callable=AsyncMock,
side_effect=Exception("500 Server Error"),
):
await cog._execute_refractor_test(
interaction=mock_interaction,
embed=base_embed,
player_id=100,
player_name="Mike Trout",
team_id=31,
card_type="batter",
card_type_key="batting",
opposing_player_id=200,
num_plays=3,
)
final_call = mock_interaction.edit_original_response.call_args_list[-1]
final_embed = final_call[1]["embed"]
result_text = "\n".join(f.value for f in final_embed.fields if f.value)
assert "" in result_text
async def test_no_tierup_still_reports_success(
self, mock_interaction, mock_bot, base_embed
):
"""If evaluate-game returns no tier-ups, report it clearly."""
from cogs.dev_tools import DevToolsCog
cog = DevToolsCog(mock_bot)
with patch("cogs.dev_tools.db_post", new_callable=AsyncMock) as mock_post:
mock_post.side_effect = [
{"id": 42}, # game
{"count": 3}, # plays
{"count": 1}, # decisions
{"updated": 1, "skipped": False}, # stats
{"evaluated": 1, "tier_ups": []}, # no tier-ups
]
await cog._execute_refractor_test(
interaction=mock_interaction,
embed=base_embed,
player_id=100,
player_name="Mike Trout",
team_id=31,
card_type="batter",
card_type_key="batting",
opposing_player_id=200,
num_plays=3,
)
final_call = mock_interaction.edit_original_response.call_args_list[-1]
final_embed = final_call[1]["embed"]
result_text = "\n".join(f.value for f in final_embed.fields if f.value)
assert "no tier-up" in result_text.lower()