- Mark CRITICAL missing endpoint gap as resolved (database PR #173 merged)
- Add REF-API-06 through REF-API-10 covering the new GET /refractor/cards
list endpoint (team filter, card_type, tier, progress, pagination)
- Update prerequisites, execution checklist, and time estimates
- Total test cases: 87 (was 82)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Target environment: Dev Discord server (Guild ID: 613880856032968834)
Dev API: pddev.manticorum.comBot container: paper-dynasty_discord-app_1 on sba-botsDate created: 2026-03-25
This test plan is designed for browser automation (Playwright against the Discord
web client) or manual execution. Each test case specifies an exact slash command,
the expected bot response, and pass/fail criteria.
Before running these tests, ensure the following state exists:
Bot State
Bot is online and healthy: GET http://sba-bots:8080/health returns 200
Refractor cog is loaded: check bot logs for Loaded extension 'cogs.refractor'
Test user has the PD Players role on the dev server
Team and Card State
Test user owns a team (verify with /team or /myteam)
Team has at least 15 cards on its roster (needed for pagination tests)
At least one batter card, one SP card, and one RP card exist on the roster
At least one card has refractor state initialized in the database (the API must have a RefractorCardState row for this player+team pair)
Record the team ID, and at least 3 card IDs for use in tests:
CARD_BATTER -- a batter card ID with refractor state
CARD_SP -- a starting pitcher card ID with refractor state
CARD_RP -- a relief pitcher card ID with refractor state
CARD_NO_STATE -- a card ID that exists but has no RefractorCardState row
CARD_INVALID -- a card ID that does not exist (e.g. 999999)
API State
Refractor tracks are seeded: GET /api/v2/refractor/tracks returns at least 3 tracks (batter, sp, rp)
At least one RefractorCardState row exists for a card on the test team
Verify manually: GET /api/v2/refractor/cards/{CARD_BATTER} returns a valid response
Verify list endpoint: GET /api/v2/refractor/cards?team_id={TEAM_ID} returns cards for the test team
Data Setup Script (run against dev API)
If refractor state does not yet exist for test cards, trigger initialization:
# Force-evaluate a specific card to create its RefractorCardState
curl -X POST "https://pddev.manticorum.com/api/v2/refractor/cards/${CARD_BATTER}/evaluate"\
-H "Authorization: Bearer ${API_TOKEN}"
1. API Health Checks
These are pre-flight checks run before any Discord interaction. They verify the
API layer is functional. Execute via shell or Playwright network interception.
Message: "No cards are currently close to a tier advancement."
Pass criteria
No embed appears; plain text message about no close cards
Notes
T4 cards are fully evolved and cannot be "close" to any threshold
REF-19: Filter by season
Field
Value
Discord command
/refractor status season:1
Expected result
Only cards from season 1 appear (or empty message if none exist)
Pass criteria
Response is either a valid embed or the "no data" message
4. /refractor status -- Pagination
REF-20: Page 1 shows first 10 cards
Field
Value
Discord command
/refractor status page:1
Expected result
Embed shows up to 10 card entries; footer says Page 1/N
Pass criteria
1. At most 10 card entries in the description
2. Footer page number is 1
3. Total pages N matches ceil(total_cards / 10)
REF-21: Page 2 shows next batch
Field
Value
Discord command
/refractor status page:2
Expected result
Embed shows cards 11-20; footer says Page 2/N
Pass criteria
1. Different cards than page 1
2. Footer shows Page 2/N
Prerequisite
Team has > 10 cards with refractor state
REF-22: Page beyond total clamps to last page
Field
Value
Discord command
/refractor status page:999
Expected result
Embed shows the last page of cards
Pass criteria
1. Footer shows Page N/N (last page)
2. No error or empty response
REF-23: Page 0 clamps to page 1
Field
Value
Discord command
/refractor status page:0
Expected result
Embed shows page 1
Pass criteria
Footer shows Page 1/N
5. /refractor status -- Edge Cases and Errors
REF-30: User with no team
Field
Value
Description
Invoke command as a user who does not own a team
Discord command
/refractor status (from a user with no team)
Expected result
Plain text message: "You don't have a team. Sign up with /newteam first."
Pass criteria
1. No embed appears
2. Message mentions /newteam
3. Response is ephemeral
REF-31: Team with no refractor data
Field
Value
Description
Invoke command for a team that has cards but no RefractorCardState rows
Discord command
/refractor status (from a team with no refractor initialization)
Expected result
Plain text message: "No refractor data found for your team."
Pass criteria
1. No embed appears
2. Message mentions "no refractor data"
REF-32: Invalid card_type filter
Field
Value
Discord command
/refractor status card_type:xyz
Expected result
Empty result -- "No refractor data found for your team."
Pass criteria
No crash; clean empty-state message
REF-33: Negative tier filter
Field
Value
Discord command
/refractor status tier:-1
Expected result
Empty result or Discord input validation rejection
Pass criteria
No crash; either a clean message or Discord prevents submission
REF-34: Negative page number
Field
Value
Discord command
/refractor status page:-5
Expected result
Clamps to page 1
Pass criteria
Footer shows Page 1/N; no crash
6. Tier Badges on Card Embeds
These tests verify that tier badges appear in card embed titles across all
commands that display card embeds via get_card_embeds().
REF-40: Tier badge on /card command (player lookup)
Field
Value
Description
Look up a card that has a refractor tier > 0
Discord command
/card {player_name} (use a player known to have refractor state)
Expected result
Embed title is [BC] Player Name (or appropriate badge for their tier)
Pass criteria
1. Embed title starts with the correct tier badge in brackets
2. Player name follows the badge
3. Embed color is still from the card's rarity (not refractor-related)
REF-41: No badge for T0 card
Field
Value
Description
Look up a card with current_tier=0
Discord command
/card {player_name} (use a player at T0)
Expected result
Embed title is just Player Name with no bracket prefix
Pass criteria
Title does not contain [BC], [R], [GR], or [SF]
REF-42: No badge when refractor state is missing
Field
Value
Description
Look up a card that has no RefractorCardState row
Discord command
/card {player_name} (use a player with no refractor state)
Expected result
Embed title is just Player Name with no bracket prefix
Pass criteria
1. Title has no badge prefix
2. No error in bot logs about the refractor API call
3. Card display is otherwise normal
REF-43: Badge on /buy confirmation embed
Field
Value
Description
Start a card purchase for a player with refractor state
Discord command
/buy {player_name}
Expected result
The card embed shown during purchase confirmation includes the tier badge
Pass criteria
Embed title includes tier badge if the player has refractor state
Notes
The buy flow uses get_card_embeds(get_blank_team_card(...)). Since blank team cards have no team association, the refractor lookup by card_id may 404. Verify graceful fallback.
REF-44: Badge on pack opening cards
Field
Value
Description
Open a pack and check if revealed cards show tier badges
Discord command
/openpack (or equivalent pack opening command)
Expected result
Cards displayed via display_cards() -> get_card_embeds() show tier badges if applicable
Pass criteria
Cards with refractor state show badges; cards without state show no badge and no error
REF-45: Badge consistency between /card and /refractor status
Field
Value
Description
Compare the badge shown for the same player in both views
Discord command
Run both /card {player} and /refractor status for the same player
Expected result
The badge in the /card embed title ([BC], [R], etc.) matches the tier shown in /refractor status
Pass criteria
Tier badge letter matches: T1=[BC], T2=[R], T3=[GR], T4=[SF]
7. Post-Game Hook -- Stat Accumulation and Evaluation
These tests verify the end-to-end flow: play a game -> stats update -> refractor
evaluation -> optional tier-up notification.
Prerequisites for Game Tests
Two teams exist on the dev server (the test user's team + an AI opponent)
The test user's team has a valid lineup and starting pitcher set
Record the game ID from the game channel name after starting
Note: Cal will perform the test game manually in Discord. Sections 7 and 8
(REF-50 through REF-64) are not automated via Playwright — game simulation
requires interactive play that is impractical to automate through the Discord
web client. After the game completes, the verification checks (REF-52 through
REF-55, REF-60 through REF-64) can be validated via API calls and bot logs.
REF-50: Start a game against AI
Field
Value
Description
Start a new game to create a game context
Discord command
/new-game mlb-campaign league:Minor League away_team_abbrev:{user_team} home_team_abbrev:{ai_team}
Expected result
Game channel is created; game starts successfully
Pass criteria
A new channel appears; scorebug embed is posted
Notes
This is setup for REF-51 through REF-54
REF-51: Complete a game (manual or auto-roll)
Field
Value
Description
Play the game through to completion
Discord command
Use /log ab repeatedly or auto-roll to finish the game
Expected result
Game ends; final score is posted; game summary embed appears
Pass criteria
1. Game over message appears
2. No errors in bot logs during the post-game hook
REF-52: Verify season stats updated post-game
Field
Value
Description
After game completion, check that season stats were updated
Verification
Check bot logs for successful POST to season-stats/update-game/{game_id}
After game completion, check that refractor evaluation was called
Verification
Check bot logs for successful POST to refractor/evaluate-game/{game_id}
Pass criteria
1. Bot logs show the refractor evaluate-game POST was made
2. The call happened AFTER the season-stats call (ordering matters)
3. Log does not show "Post-game refractor processing failed"
REF-54: Verify refractor values changed after game
Field
Value
Description
After a completed game, check that formula values increased for participating players
Discord command
/refractor status (compare before/after values for a participating player)
Expected result
formula_value for batters who had PAs and pitchers who recorded outs should be higher than before the game
Pass criteria
At least one card's formula_value has increased
API check
curl "https://pddev.manticorum.com/api/v2/refractor/cards/{CARD_BATTER}" -H "Authorization: Bearer $TOKEN" -- compare current_value before and after
REF-55: Post-game hook is non-fatal
Field
Value
Description
Even if the refractor API fails, the game completion should succeed
Verification
This is tested via unit tests (test_complete_game_hook.py). For integration: verify that if the API has a momentary error, the game result is still saved and the channel reflects the final score.
Pass criteria
Game results persist even if refractor evaluation errors appear in logs
8. Tier-Up Notifications
REF-60: Tier-up embed format (T0 -> T1)
Field
Value
Description
When a card tiers up from T0 to T1 (Base Chrome), a notification embed is sent
Trigger
Complete a game where a player's formula_value crosses the T1 threshold
Expected result
An embed appears in the game channel with:
- Title: "Refractor Tier Up!"
- Description: **{Player Name}** reached **Tier 1 (Base Chrome)** on the **{Track Name}** track
- Color: green (0x2ECC71)
- Footer: "Paper Dynasty Refractor"
Pass criteria
1. Embed title is exactly "Refractor Tier Up!"
2. Player name appears bold in description
3. Tier number and name are correct
4. Track name is one of: Batter Track, Starting Pitcher Track, Relief Pitcher Track
- Description: **{Player Name}** has reached maximum refractor tier on the **{Track Name}** track
- Color: teal (0x1ABC9C)
- Extra field: "Rating Boosts" with value "Rating boosts coming in a future update!"
Pass criteria
1. Title is "SUPERFRACTOR!"
2. Description mentions "maximum refractor tier"
3. "Rating Boosts" field is present
REF-63: Multiple tier-ups in one game
Field
Value
Description
When multiple players tier up in the same game, each gets a separate notification
Trigger
Complete a game where 2+ players cross thresholds
Expected result
One embed per tier-up, posted sequentially in the game channel
Pass criteria
Each tier-up gets its own embed; no tier-ups are lost
REF-64: No notification when no tier-ups occur
Field
Value
Description
Most games will not produce any tier-ups; verify no spurious notifications
Trigger
Complete a game where no thresholds are crossed
Expected result
No tier-up embeds appear in the channel
Pass criteria
The only game-end messages are the standard game summary and rewards
9. Cross-Command Badge Propagation
These tests verify that tier badges appear (or correctly do not appear) in all
commands that display card information.
REF-70: /roster command -- cards show tier badges
Field
Value
Discord command
/roster or equivalent command that lists team cards
Expected result
If roster display uses get_card_embeds(), cards with refractor state show tier badges
Pass criteria
Cards at T1+ have badges; T0 cards have none
REF-71: /show-card defense (in-game) -- no badge expected
Field
Value
Description
During an active game, the /show-card defense command uses image_embed() directly, NOT get_card_embeds()
Discord command
/show-card defense position:Catcher (during an active game)
Expected result
Card image is shown without a tier badge in the embed title
Pass criteria
This is EXPECTED behavior -- in-game card display does not fetch refractor state
Notes
This is a known limitation, not a bug. Document for future consideration.
REF-72: /scouting view -- badge on scouted cards
Field
Value
Discord command
/scout {player_name} (if the scouting cog uses get_card_embeds)
Expected result
If the scouting view calls get_card_embeds, badges should appear
Pass criteria
Verify whether scouting uses get_card_embeds or its own embed builder
10. Force-Evaluate Endpoint (Admin/Debug)
REF-80: Force evaluate a single card
Field
Value
Description
Use the API to force-recalculate a card's refractor state
Command
curl -X POST "https://pddev.manticorum.com/api/v2/refractor/cards/${CARD_BATTER}/evaluate" -H "Authorization: Bearer $TOKEN"
Expected result
JSON response with updated current_tier, current_value
Pass criteria
Response includes tier and value fields; no 500 error
REF-81: Force evaluate a card with no stats
Field
Value
Command
curl -X POST "https://pddev.manticorum.com/api/v2/refractor/cards/${CARD_NO_STATS}/evaluate" -H "Authorization: Bearer $TOKEN"
Expected result
Either 404 or a response with current_tier: 0 and current_value: 0
Pass criteria
No 500 error; graceful handling
REF-82: Force evaluate nonexistent card
Field
Value
Command
curl -X POST "https://pddev.manticorum.com/api/v2/refractor/cards/999999/evaluate" -H "Authorization: Bearer $TOKEN"
Expected result
HTTP 404 with "Card 999999 not found"
Pass criteria
Status 404; clear error message
Known Gaps and Risks
RESOLVED: Team-level refractor cards list endpoint
The GET /api/v2/refractor/cards list endpoint was added in database PR #173
(merged 2026-03-25). It accepts team_id (required), card_type, tier,
season, progress, limit, and offset query parameters. The response
includes progress_pct (computed) and player_name (via LEFT JOIN on Player).
Sorting: current_tier DESC, current_value DESC. A non-unique index on
refractor_card_state.team_id was added for query performance.
Test cases REF-API-06 through REF-API-10 now cover this endpoint directly.
In-game card display does not show badges
The /show-card defense command in the gameplay cog uses image_embed() which
renders the card image directly. It does not call get_card_embeds() and
therefore does not fetch or display refractor tier badges. This is a design
decision, not a bug, but should be documented as a known limitation.
This is intentional -- helpers/main.py wraps the value in brackets when building
the embed title (f"[{badge}] "). The existing unit test
TestTierBadgesFormatConsistency in test_card_embed_refractor.py enforces this
contract. Both dicts must stay in sync.
Notification delivery is non-fatal
The tier-up notification send is wrapped in try/except. If Discord's API has a
momentary error, the notification is lost silently (logged but not retried). There
is no notification queue or retry mechanism. This is acceptable for the current
design but means tier-up notifications are best-effort.
Test Execution Checklist
Run order for Playwright automation:
Execute REF-API-01 through REF-API-10 (API health + list endpoint)
Execute REF-01 through REF-06 (basic /refractor status)
Execute REF-10 through REF-19 (filters)
Execute REF-20 through REF-23 (pagination)
Execute REF-30 through REF-34 (edge cases)
Execute REF-40 through REF-45 (tier badges on card embeds)
Execute REF-50 through REF-55 (post-game hook -- requires live game)
Execute REF-60 through REF-64 (tier-up notifications -- requires threshold crossing)
Execute REF-70 through REF-72 (cross-command badge propagation)
Execute REF-80 through REF-82 (force-evaluate API)
Approximate Time Estimates
API health checks + list endpoint (REF-API-01 through REF-API-10): 2-3 minutes
/refractor status tests (REF-01 through REF-34): 10-15 minutes
Tier badge tests (REF-40 through REF-45): 5-10 minutes
Game simulation tests (REF-50 through REF-55): 15-30 minutes (depends on game length)
Tier-up notification tests (REF-60 through REF-64): Requires setup; 10-20 minutes
Cross-command tests (REF-70 through REF-72): 5 minutes
Force-evaluate API tests (REF-80 through REF-82): 2-3 minutes
Total estimated time: 50-90 minutes for full suite (87 test cases)