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:
parent
129971e96b
commit
9257852c3e
@ -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):
|
||||
|
||||
@ -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()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user