fix(dev_tools): use unified cards/{id} endpoint for /dev refractor-test #168
No reviewers
Labels
No Label
ai-changes-requested
ai-failed
ai-pr-opened
ai-reviewed
ai-reviewing
ai-working
autonomous
bug
enhancement
feature
in-queue
performance
security
size:M
size:S
tech-debt
tests
todo
type:feature
type:stability
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: cal/paper-dynasty-discord#168
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "fix/refractor-test-unified-cards-lookup"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The previous two-step battingcards→pitchingcards fallback caused card ID
collisions. Example:
/dev refractor-test card_id:494resolved to CameronMaybin (battingcards row 494, 2018 Marlins) instead of the intended
pitcher Grayson Rodriguez (pitchingcards row 494, 2023 Orioles).
Changes
db_get("cards", object_id=card_id)lookup;cards.idisglobally unique across the card-instance table
player.pos_1:SP→sp,RP/CP→rp,else →
batterteam_idsourced fromcard["team"]["id"](the card instance'sowning team);
get_team_by_ownerfallback removedTestRefractorTestSetuprewritten for the single-endpoint contractwith a
unified_card_responsefixture factory and pos_1 derivationcoverage (SP/RP/CP/batter)
Semantic shift
card_idnow refers to the globally-unique card-instance ID from thecardstable, not a batting/pitching template ID. For a dev-onlycommand this is acceptable — instance IDs are already discoverable
via
/refractor status.Tests
python -m pytest tests/test_dev_tools.py tests/test_refractor_test_data.pyOut of scope
SENTINEL_PITCHER_ID = 3opposing-player default — pre-existing, unchangedPaired with the spec PR in
cal/paper-dynasty-umbrella(
fix/refractor-test-unified-cards-lookup).Spec:
docs/superpowers/specs/2026-04-11-refractor-test-unified-cards-lookup-design.mdThe previous two-step battingcards→pitchingcards fallback caused card ID collisions — e.g. card 494 resolving to Cameron Maybin (batting) instead of the intended pitcher Grayson Rodriguez. The unified cards endpoint is keyed on globally-unique card instance IDs and carries player, team, and variant in a single response. - Single db_get("cards", object_id=card_id) lookup - Card type derived from player.pos_1 (SP→sp, RP/CP→rp, else→batter) - team_id sourced from card["team"]["id"] (no get_team_by_owner fallback) - TestRefractorTestSetup rewritten for the single-endpoint contract Spec: docs/superpowers/specs/2026-04-11-refractor-test-unified-cards-lookup-design.mdAI Code Review
Files Reviewed
cogs/dev_tools.py(modified)tests/test_dev_tools.py(modified)Findings
Correctness
No issues found. The core fix is correct:
db_get("cards", object_id=card_id)replaces the two-step batting/pitching fallback —cards.idis globally unique so no template-ID collision.card["player"]["player_id"]is the correct field path for the unified endpoint (differs from the oldcard["player"]["id"]on template endpoints).(card.get("team") or {}).get("id")safely handlesteam: nullcards and correctly gates on a hard error instead of silently falling back toget_team_by_owner.card_type_defaultis always assigned before theif card_state:branch, so it's never used uninitialized.AUTH_TOKENis{"Authorization": "Bearer ..."}(dict) — correct type foraiohttp.ClientSession(headers=...), consistent withapi_calls.pyusage throughout.Security
No issues found.
card_idis anintparameter from Discord's app_command layer — no injection risk.AUTH_TOKENis not user-controlled.Style & Conventions
No issues found. Logger rename to
"discord_app"matches the named logger pattern used in other cogs. Structured debug logging added for state-lookup is appropriate for a dev tool.Suggestions
Dead mock in
test_successful_batter_flow(unchanged test, broken by this PR's implementation change). The render step now usesaiohttp.ClientSessiondirectly instead ofdb_get. The existing mockmock_get.return_value = {"image_url": "https://s3.example.com/card.png"}is dead — it's never consumed. The aiohttp call will throw aClientConnectorErrorat test time, which is silently caught by theexcept Exceptionhandler. The test still passes because it only asserts"✅"(from game creation, not render). The render step's success path is effectively untested. Consider either mocking aiohttp in that test or removing the dead mock and adding a comment that the render step is covered by exception-handling only.No
ClientTimeouton render call. Every other HTTP call inapi_calls.pywraps withClientTimeout(total=timeout). The new render path omits it — the session could hang indefinitely if the render endpoint is slow. Acceptable for a dev-only tool but worth noting for consistency.embed.set_image()silently removed. The old render path extracted an image URL and calledembed.set_image(url=img_url)on success, displaying the freshly-rendered card in the embed. The new path drops this entirely. The PR body doesn't mention the behavior change. Not a blocker for a dev tool, but worth documenting.Verdict: COMMENT
Core fix is correct, well-motivated (the Cameron Maybin vs Grayson Rodriguez collision example makes the bug clear), and the setup-phase test coverage is thorough (SP/RP/CP/batter derivation, no-team guard, single-endpoint assertion, no-fallback assertion). The suggestions above are non-blocking.
Automated review by Claude PR Reviewer
AI Code Review
Files Reviewed
cogs/dev_tools.py(modified)tests/test_dev_tools.py(modified)Findings
Correctness
No issues found.
db_get("cards", object_id=card_id)call with correct keyword arg — matchesdb_getsignature inapi_calls.py.card["player"]["player_id"]field name correctly matches the unified endpoint's response shape (distinct from the old template tables that used"id").(card.get("team") or {}).get("id")safely handles bothteam=Noneand a missing"team"key without raising.pos_1derivation (or ""guard +.upper()) correctly handles None and mixed-case values. SP → sp, RP/CP → rp, else → batter matches spec.card_type_defaultis read in theelse: card_type = card_type_defaultbranch — the Pyright false-positive is a known non-issue per review scope.get_req_url(api_ver=2)andAUTH_TOKENheader dict — both verified exports ofapi_calls.py.aiohttpis imported at the top of the file, not lazily mid-function.get_team_by_ownerfully removed from both usage site and the import line.Security
No issues found. This cog is only loaded when
DATABASE != prod. No user-controlled data reaches SQL or shell.AUTH_TOKENis sourced from environment, not hardcoded.Style & Conventions
No issues found. Logger name changed to
"discord_app"which aligns with the named-logger pattern used across the project.Suggestions
Non-blocking observations only:
Stale
@app_commands.describestring (cogs/dev_tools.pyline 111): Still says"The batting or pitching card ID to test"— could be updated to reflect the card-instance ID semantic in a follow-up. Low priority for a dev-only command.Dead
mock_getintest_successful_batter_flow(tests/test_dev_tools.pyline 427):mock_get.return_valuetargets the olddb_get-based render path. The render step now usesaiohttpdirectly, so this mock is inert. The test still passes because the aiohttp call is caught by the non-fatalexcepthandler. Consider patchingaiohttp.ClientSessionhere in a follow-up to make the happy-path render assertion meaningful.Verdict: APPROVED
Clean, targeted fix. The unified endpoint lookup eliminates the template-ID collision correctly, the pos_1 derivation is well-guarded, team extraction handles the None case properly, and the new test suite meaningfully covers all four pos_1 branches plus both error paths. All new imports are verified present in
api_calls.py.(Note: Gitea single-user repos block the APPROVED review state — merge with
pd-pr merge --no-approve.)Automated review by Claude PR Reviewer
Merging after pr-pipeline review. Two reviewer notes:
Docstring update (stale @app_commands.describe string) — fixed in commit
c8424e6on this branch.Dead mock in test_successful_batter_flow — pre-existing weakness, deferred to a follow-up issue:
#169
Pairing spec merged in cal/paper-dynasty-umbrella#7.