fix: wire WP-14 tier-up notification embeds into post-game hook
All checks were successful
Ruff Lint / lint (pull_request) Successful in 25s
All checks were successful
Ruff Lint / lint (pull_request) Successful in 25s
Replace the logging-only stub in logic_gameplay.py with the real notify_tier_completion from helpers/refractor_notifs.py. Tier-up events now send Discord embeds instead of just logging. - Import notify_tier_completion from helpers.refractor_notifs - Remove 16-line stub function and redundant inline logging - Update tests: verify real embed-sending behavior, replace bug-documenting T1-5 diagnostic with shape validation guards Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8c0c2eb21a
commit
571a86fe7e
@ -23,6 +23,7 @@ from helpers import (
|
||||
position_name_to_abbrev,
|
||||
team_role,
|
||||
)
|
||||
from helpers.refractor_notifs import notify_tier_completion
|
||||
from in_game.ai_manager import get_starting_lineup
|
||||
from in_game.game_helpers import PUBLIC_FIELDS_CATEGORY_NAME, legal_check
|
||||
from in_game.gameplay_models import (
|
||||
@ -4242,24 +4243,6 @@ async def get_game_summary_embed(
|
||||
return game_embed
|
||||
|
||||
|
||||
async def notify_tier_completion(channel: discord.TextChannel, tier_up: dict) -> None:
|
||||
"""Stub for WP-14: log evolution tier-up events.
|
||||
|
||||
WP-14 will replace this with a full Discord embed notification. For now we
|
||||
only log the event so that the WP-13 hook has a callable target and the
|
||||
tier-up data is visible in the application log.
|
||||
|
||||
Args:
|
||||
channel: The Discord channel where the game was played.
|
||||
tier_up: Dict from the evolution API, expected to contain at minimum
|
||||
'player_id', 'old_tier', and 'new_tier' keys.
|
||||
"""
|
||||
logger.info(
|
||||
f"[WP-14 stub] notify_tier_completion called for channel={channel.id if channel else 'N/A'} "
|
||||
f"tier_up={tier_up}"
|
||||
)
|
||||
|
||||
|
||||
async def complete_game(
|
||||
session: Session,
|
||||
interaction: discord.Interaction,
|
||||
@ -4369,12 +4352,6 @@ async def complete_game(
|
||||
evo_result = await db_post(f"refractor/evaluate-game/{db_game['id']}")
|
||||
if evo_result and evo_result.get("tier_ups"):
|
||||
for tier_up in evo_result["tier_ups"]:
|
||||
# WP-14 will implement full Discord notification; stub for now
|
||||
logger.info(
|
||||
f"Refractor tier-up for player {tier_up.get('player_id')}: "
|
||||
f"{tier_up.get('old_tier')} -> {tier_up.get('new_tier')} "
|
||||
f"(game {db_game['id']})"
|
||||
)
|
||||
await notify_tier_completion(interaction.channel, tier_up)
|
||||
except Exception as e:
|
||||
logger.warning(f"Post-game refractor processing failed (non-fatal): {e}")
|
||||
|
||||
@ -14,8 +14,6 @@ Key design constraints being tested:
|
||||
notify_tier_completion so WP-14 can present them to the player.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock, call, patch
|
||||
|
||||
@ -179,23 +177,29 @@ async def test_hook_no_tier_ups_does_not_call_notify():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_notify_tier_completion_stub_logs_and_does_not_raise(caplog):
|
||||
async def test_notify_tier_completion_sends_embed_and_does_not_raise():
|
||||
"""
|
||||
The WP-14 stub must log the event and return cleanly.
|
||||
notify_tier_completion sends a Discord embed and does not raise.
|
||||
|
||||
Verifies the contract that WP-14 can rely on: the function accepts
|
||||
(channel, tier_up) and does not raise, so the hook's for-loop is safe.
|
||||
Now that WP-14 is wired, the function imported via logic_gameplay is the
|
||||
real embed-sending implementation from helpers.refractor_notifs.
|
||||
"""
|
||||
from command_logic.logic_gameplay import notify_tier_completion
|
||||
|
||||
channel = _make_channel(channel_id=123)
|
||||
tier_up = {"player_id": 77, "old_tier": 0, "new_tier": 1}
|
||||
channel = AsyncMock()
|
||||
# Full API response shape — the evaluate-game endpoint returns all these keys
|
||||
tier_up = {
|
||||
"player_id": 77,
|
||||
"team_id": 1,
|
||||
"player_name": "Mike Trout",
|
||||
"old_tier": 0,
|
||||
"new_tier": 1,
|
||||
"current_value": 45.0,
|
||||
"track_name": "Batter Track",
|
||||
}
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
await notify_tier_completion(channel, tier_up)
|
||||
|
||||
# At minimum one log message should reference the channel or tier_up data
|
||||
assert any(
|
||||
"notify_tier_completion" in rec.message or "77" in rec.message
|
||||
for rec in caplog.records
|
||||
)
|
||||
channel.send.assert_called_once()
|
||||
embed = channel.send.call_args.kwargs["embed"]
|
||||
assert "Mike Trout" in embed.description
|
||||
|
||||
@ -260,63 +260,43 @@ class TestNotifyTierCompletion:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# T1-5: tier_up dict shape mismatch — WP-14 integration blocker
|
||||
# T1-5: tier_up dict shape validation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestTierUpDictShapeMismatch:
|
||||
class TestTierUpDictShapeValidation:
|
||||
"""
|
||||
T1-5: Expose the latent integration bug where the post-game hook passes a
|
||||
minimal tier_up dict (only player_id, old_tier, new_tier) but
|
||||
build_tier_up_embed expects player_name, old_tier, new_tier, track_name,
|
||||
and current_value.
|
||||
T1-5: Verify build_tier_up_embed handles valid API shapes correctly and
|
||||
rejects malformed input.
|
||||
|
||||
Why this matters: the hook test (test_complete_game_hook.py) confirms the
|
||||
plumbing forwards tier_up dicts from the API response to notify_tier_completion.
|
||||
However, the real API response may omit player_name/track_name. If
|
||||
build_tier_up_embed does a bare dict access (tier_up["player_name"]) without
|
||||
a fallback, it will raise KeyError in production. This test documents the
|
||||
current behaviour (crash vs. graceful degradation) so WP-14 implementers
|
||||
know to either harden the embed builder or ensure the API always returns
|
||||
the full shape.
|
||||
The evaluate-game API endpoint returns the full shape (player_name,
|
||||
old_tier, new_tier, track_name, current_value). These tests guard the
|
||||
contract between the API response and the embed builder.
|
||||
"""
|
||||
|
||||
def test_minimal_stub_shape_raises_key_error(self):
|
||||
def test_empty_dict_raises_key_error(self):
|
||||
"""
|
||||
Calling build_tier_up_embed with only {player_id, old_tier, new_tier}
|
||||
(the minimal shape used by the post-game hook stub) raises KeyError
|
||||
because player_name and track_name are accessed via bare dict lookup.
|
||||
|
||||
This is the latent bug: the hook passes stub-shaped dicts but the embed
|
||||
builder expects the full notification shape. WP-14 must ensure either
|
||||
(a) the API returns the full shape or (b) build_tier_up_embed degrades
|
||||
gracefully with .get() fallbacks.
|
||||
An empty dict must raise KeyError — guards against callers passing
|
||||
unrelated or completely malformed data.
|
||||
"""
|
||||
minimal_stub = {
|
||||
"player_id": 101,
|
||||
"old_tier": 1,
|
||||
"new_tier": 2,
|
||||
}
|
||||
# Document that this raises — it's the bug we're exposing, not a passing test.
|
||||
with pytest.raises(KeyError):
|
||||
build_tier_up_embed(minimal_stub)
|
||||
build_tier_up_embed({})
|
||||
|
||||
def test_full_shape_does_not_raise(self):
|
||||
def test_full_api_shape_builds_embed(self):
|
||||
"""
|
||||
Confirm that supplying the full expected shape (player_name, old_tier,
|
||||
new_tier, track_name, current_value) does NOT raise, establishing the
|
||||
correct contract for callers.
|
||||
The full shape returned by the evaluate-game endpoint builds a valid
|
||||
embed without error.
|
||||
"""
|
||||
full_shape = make_tier_up(
|
||||
player_name="Mike Trout",
|
||||
old_tier=1,
|
||||
new_tier=2,
|
||||
track_name="Batter",
|
||||
track_name="Batter Track",
|
||||
current_value=150,
|
||||
)
|
||||
# Must not raise
|
||||
embed = build_tier_up_embed(full_shape)
|
||||
assert embed is not None
|
||||
assert "Mike Trout" in embed.description
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Loading…
Reference in New Issue
Block a user