paper-dynasty-discord/tests/refractor-integration-test-plan.md
Cal Corum 035cd8888f
All checks were successful
Ruff Lint / lint (pull_request) Successful in 22s
fix: move health server from port 8080 to 8081 (#130)
Adminer is exposed on host port 8080, shadowing the bot health endpoint.
Change health server default to 8081 to avoid the conflict.

Closes #130

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 23:33:09 -05:00

701 lines
32 KiB
Markdown

# Refractor System -- In-App Integration Test Plan
**Target environment**: Dev Discord server (Guild ID: `613880856032968834`)
**Dev API**: `pddev.manticorum.com`
**Bot container**: `paper-dynasty_discord-app_1` on `sba-bots`
**Date 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.
---
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [API Health Checks](#1-api-health-checks)
3. [/refractor status -- Basic Functionality](#2-refractor-status----basic-functionality)
4. [/refractor status -- Filters](#3-refractor-status----filters)
5. [/refractor status -- Pagination](#4-refractor-status----pagination)
6. [/refractor status -- Edge Cases and Errors](#5-refractor-status----edge-cases-and-errors)
7. [Tier Badges on Card Embeds](#6-tier-badges-on-card-embeds)
8. [Post-Game Hook -- Stat Accumulation and Evaluation](#7-post-game-hook----stat-accumulation-and-evaluation)
9. [Tier-Up Notifications](#8-tier-up-notifications)
10. [Cross-Command Badge Propagation](#9-cross-command-badge-propagation)
11. [Known Gaps and Risks](#known-gaps-and-risks)
---
## Prerequisites
Before running these tests, ensure the following state exists:
### Bot State
- [ ] Bot is online and healthy: `GET http://sba-bots:8081/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:
```bash
# 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.
### REF-API-01: Bot health endpoint
| Field | Value |
|---|---|
| **Command** | `curl -sf http://sba-bots:8081/health` |
| **Expected** | HTTP 200, body contains health status |
| **Pass criteria** | Non-empty 200 response |
### REF-API-02: Refractor tracks endpoint responds
| Field | Value |
|---|---|
| **Command** | `curl -s "https://pddev.manticorum.com/api/v2/refractor/tracks" -H "Authorization: Bearer $TOKEN"` |
| **Expected** | JSON with `count >= 3` and `items` array containing batter, sp, rp tracks |
| **Pass criteria** | `count` field >= 3; each item has `card_type`, `t1_threshold`, `t2_threshold`, `t3_threshold`, `t4_threshold` |
### REF-API-03: Single card refractor state endpoint
| Field | Value |
|---|---|
| **Command** | `curl -s "https://pddev.manticorum.com/api/v2/refractor/cards/${CARD_BATTER}" -H "Authorization: Bearer $TOKEN"` |
| **Expected** | JSON with `player_id`, `team_id`, `current_tier`, `current_value`, `fully_evolved`, `next_threshold`, `track` |
| **Pass criteria** | `current_tier` is an integer 0-4; `track` object exists with threshold fields |
### REF-API-04: Card state 404 for nonexistent card
| Field | Value |
|---|---|
| **Command** | `curl -s -o /dev/null -w "%{http_code}" "https://pddev.manticorum.com/api/v2/refractor/cards/999999" -H "Authorization: Bearer $TOKEN"` |
| **Expected** | HTTP 404 |
| **Pass criteria** | Status code is exactly 404 |
### REF-API-05: Old evolution endpoint removed
| Field | Value |
|---|---|
| **Command** | `curl -s -o /dev/null -w "%{http_code}" "https://pddev.manticorum.com/api/v2/evolution/cards/1" -H "Authorization: Bearer $TOKEN"` |
| **Expected** | HTTP 404 |
| **Pass criteria** | Status code is 404 (confirms evolution->refractor rename is complete) |
### REF-API-06: Team-level card list endpoint
| Field | Value |
|---|---|
| **Command** | `curl -s "https://pddev.manticorum.com/api/v2/refractor/cards?team_id=${TEAM_ID}" -H "Authorization: Bearer $TOKEN"` |
| **Expected** | JSON with `count` >= 1 and `items` array containing card state objects |
| **Pass criteria** | 1. `count` reflects total cards with refractor state for the team |
| | 2. Each item has `player_id`, `team_id`, `current_tier`, `current_value`, `progress_pct`, `player_name` |
| | 3. Items sorted by `current_tier` DESC, `current_value` DESC |
### REF-API-07: Card list with card_type filter
| Field | Value |
|---|---|
| **Command** | `curl -s "https://pddev.manticorum.com/api/v2/refractor/cards?team_id=${TEAM_ID}&card_type=batter" -H "Authorization: Bearer $TOKEN"` |
| **Expected** | JSON with only batter card states |
| **Pass criteria** | All items have batter track; count <= total from REF-API-06 |
### REF-API-08: Card list with tier filter
| Field | Value |
|---|---|
| **Command** | `curl -s "https://pddev.manticorum.com/api/v2/refractor/cards?team_id=${TEAM_ID}&tier=0" -H "Authorization: Bearer $TOKEN"` |
| **Expected** | JSON with only T0 card states |
| **Pass criteria** | All items have `current_tier: 0` |
### REF-API-09: Card list with progress=close filter
| Field | Value |
|---|---|
| **Command** | `curl -s "https://pddev.manticorum.com/api/v2/refractor/cards?team_id=${TEAM_ID}&progress=close" -H "Authorization: Bearer $TOKEN"` |
| **Expected** | JSON with only cards at >= 80% of next tier threshold |
| **Pass criteria** | Each item's `progress_pct` >= 80.0; no fully evolved cards |
### REF-API-10: Card list pagination
| Field | Value |
|---|---|
| **Command** | `curl -s "https://pddev.manticorum.com/api/v2/refractor/cards?team_id=${TEAM_ID}&limit=2&offset=0" -H "Authorization: Bearer $TOKEN"` |
| **Expected** | JSON with `count` reflecting total (not page size) and `items` array with at most 2 entries |
| **Pass criteria** | 1. `count` same as REF-API-06 (total matching, not page size) |
| | 2. `items` length <= 2 |
---
## 2. /refractor status -- Basic Functionality
### REF-01: Basic status command (no filters)
| Field | Value |
|---|---|
| **Description** | Invoke /refractor status with no arguments; verify the embed appears |
| **Discord command** | `/refractor status` |
| **Expected result** | An ephemeral embed with: |
| | - Title: `{team short name} Refractor Status` |
| | - Purple embed color (hex `0x6F42C1` = RGB 111, 66, 193) |
| | - Description containing card entries (player names, progress bars, tier labels) |
| | - Footer: `Page 1/N . M card(s) total` |
| **Pass criteria** | 1. Embed title contains team short name and "Refractor Status" |
| | 2. Embed color is purple (`#6F42C1`) |
| | 3. At least one card entry is visible in the description |
| | 4. Footer contains page number and total card count |
| | 5. Response is ephemeral (only visible to the invoking user) |
### REF-02: Card entry format -- batter
| Field | Value |
|---|---|
| **Description** | Verify a batter card entry has correct format in the status embed |
| **Discord command** | `/refractor status card_type:batter` |
| **Expected result** | Each entry in the embed follows this pattern: |
| | Line 1: `**{badge} Player Name** (Tier Label)` |
| | Line 2: `[====------] value/threshold (PA+TB x 2) -- T{n} -> T{n+1}` |
| **Pass criteria** | 1. Player name appears in bold (`**...**`) |
| | 2. Tier label is one of: Base Card, Base Chrome, Refractor, Gold Refractor, Superfractor |
| | 3. Progress bar has format `[====------]` (10 chars of `=` and `-` between brackets) |
| | 4. Formula label shows `PA+TB x 2` for batters |
| | 5. Tier progression arrow shows `T{current} -> T{next}` |
### REF-03: Card entry format -- starting pitcher
| Field | Value |
|---|---|
| **Description** | Verify SP cards show the correct formula label |
| **Discord command** | `/refractor status card_type:sp` |
| **Expected result** | SP card entries show `IP+K` as the formula label |
| **Pass criteria** | Formula label in progress line is `IP+K` (not `PA+TB x 2`) |
### REF-04: Card entry format -- relief pitcher
| Field | Value |
|---|---|
| **Description** | Verify RP cards show the correct formula label |
| **Discord command** | `/refractor status card_type:rp` |
| **Expected result** | RP card entries show `IP+K` as the formula label |
| **Pass criteria** | Formula label in progress line is `IP+K` |
### REF-05: Tier badge display per tier
| Field | Value |
|---|---|
| **Description** | Verify correct tier badges appear for each tier level |
| **Discord command** | `/refractor status` (examine entries across tiers) |
| **Expected result** | Badge mapping: |
| | T0 (Base Card): no badge prefix |
| | T1 (Base Chrome): `[BC]` prefix |
| | T2 (Refractor): `[R]` prefix |
| | T3 (Gold Refractor): `[GR]` prefix |
| | T4 (Superfractor): `[SF]` prefix |
| **Pass criteria** | Each card's badge matches its tier per the mapping above |
### REF-06: Fully evolved card display
| Field | Value |
|---|---|
| **Description** | Verify T4 (Superfractor) cards show the fully evolved indicator |
| **Discord command** | `/refractor status tier:4` |
| **Expected result** | Fully evolved cards show: |
| | Line 1: `**[SF] Player Name** (Superfractor)` |
| | Line 2: `[==========] FULLY EVOLVED (star)` |
| **Pass criteria** | 1. Progress bar is completely filled (`[==========]`) |
| | 2. Text says "FULLY EVOLVED" with a star character |
| | 3. No tier progression arrow (no `->` text) |
---
## 3. /refractor status -- Filters
### REF-10: Filter by card_type=batter
| Field | Value |
|---|---|
| **Discord command** | `/refractor status card_type:batter` |
| **Expected result** | Only batter cards appear; formula label is `PA+TB x 2` on all entries |
| **Pass criteria** | No entries show `IP+K` formula label |
### REF-11: Filter by card_type=sp
| Field | Value |
|---|---|
| **Discord command** | `/refractor status card_type:sp` |
| **Expected result** | Only SP cards appear; formula label is `IP+K` on all entries |
| **Pass criteria** | No entries show `PA+TB x 2` formula label |
### REF-12: Filter by card_type=rp
| Field | Value |
|---|---|
| **Discord command** | `/refractor status card_type:rp` |
| **Expected result** | Only RP cards appear; formula label is `IP+K` on all entries |
| **Pass criteria** | No entries show `PA+TB x 2` formula label |
### REF-13: Filter by tier=0
| Field | Value |
|---|---|
| **Discord command** | `/refractor status tier:0` |
| **Expected result** | Only T0 (Base Card) entries appear; no tier badges on any entry |
| **Pass criteria** | No entries contain `[BC]`, `[R]`, `[GR]`, or `[SF]` badges |
### REF-14: Filter by tier=1
| Field | Value |
|---|---|
| **Discord command** | `/refractor status tier:1` |
| **Expected result** | Only T1 entries appear; all show `[BC]` badge and `(Base Chrome)` label |
| **Pass criteria** | Every entry contains `[BC]` and `(Base Chrome)` |
### REF-15: Filter by tier=4
| Field | Value |
|---|---|
| **Discord command** | `/refractor status tier:4` |
| **Expected result** | Only T4 entries appear; all show `[SF]` badge and `FULLY EVOLVED` |
| **Pass criteria** | Every entry contains `[SF]`, `(Superfractor)`, and `FULLY EVOLVED` |
### REF-16: Filter by progress=close
| Field | Value |
|---|---|
| **Discord command** | `/refractor status progress:close` |
| **Expected result** | Only cards within 80% of their next tier threshold appear |
| **Pass criteria** | 1. For each entry, the formula_value/next_threshold ratio >= 0.8 |
| | 2. No fully evolved (T4) cards appear |
| | 3. If no cards qualify, message says "No cards are currently close to a tier advancement." |
### REF-17: Combined filter -- tier + card_type
| Field | Value |
|---|---|
| **Discord command** | `/refractor status card_type:batter tier:1` |
| **Expected result** | Only T1 batter cards appear |
| **Pass criteria** | All entries have `[BC]` badge AND `PA+TB x 2` formula label |
### REF-18: Combined filter -- tier=4 + progress=close (empty result)
| Field | Value |
|---|---|
| **Discord command** | `/refractor status tier:4 progress:close` |
| **Expected result** | 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}` |
| **Pass criteria** | 1. Bot logs show the season-stats POST was made |
| | 2. No error logged for that call |
| **API check** | `curl "https://pddev.manticorum.com/api/v2/season-stats?team_id={team_id}" -H "Authorization: Bearer $TOKEN"` returns updated stats |
### REF-53: Verify refractor evaluation triggered post-game
| Field | Value |
|---|---|
| **Description** | 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 |
| | 5. Footer text is "Paper Dynasty Refractor" |
### REF-61: Tier-up embed colors per tier
| Field | Value |
|---|---|
| **Description** | Each tier has a distinct embed color |
| **Expected colors** | T1: green (`0x2ECC71`), T2: gold (`0xF1C40F`), T3: purple (`0x9B59B6`), T4: teal (`0x1ABC9C`) |
| **Pass criteria** | Embed color matches the target tier |
| **Notes** | May require manual API manipulation to trigger specific tier transitions |
### REF-62: Superfractor notification (T3 -> T4)
| Field | Value |
|---|---|
| **Description** | The Superfractor tier-up has special formatting |
| **Trigger** | A player crosses the T4 threshold |
| **Expected result** | Embed with: |
| | - Title: "SUPERFRACTOR!" (not "Refractor Tier Up!") |
| | - 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.
### Tier badge format inconsistency (by design)
Two `TIER_BADGES` dicts exist:
- `cogs/refractor.py`: `{1: "[BC]", 2: "[R]", 3: "[GR]", 4: "[SF]"}` (with brackets)
- `helpers/main.py`: `{1: "BC", 2: "R", 3: "GR", 4: "SF"}` (without brackets)
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:
1. [~] Execute REF-API-01 through REF-API-10 (API health + list endpoint)
- Tested 2026-03-25: REF-API-03 (single card ✓), REF-API-06 (list ✓), REF-API-07 (card_type filter ✓), REF-API-10 (pagination ✓)
- Not yet tested: REF-API-01, REF-API-02, REF-API-04, REF-API-05, REF-API-08, REF-API-09
2. [~] Execute REF-01 through REF-06 (basic /refractor status)
- Tested 2026-03-25: REF-01 (embed appears ✓), REF-02 (batter entry format ✓), REF-05 (tier badges [BC] ✓)
- Bugs found and fixed: wrong response key ("cards" vs "items"), wrong field names (formula_value vs current_value, card_type nesting), limit=500 exceeding API max, floating point display
- Not yet tested: REF-03 (SP format), REF-04 (RP format), REF-06 (fully evolved)
3. [~] Execute REF-10 through REF-19 (filters)
- Tested 2026-03-25: REF-10 (card_type=batter ✓ after fix)
- Choice dropdown menus added for all filter params (PR #126)
- Not yet tested: REF-11 through REF-19
4. [~] Execute REF-20 through REF-23 (pagination)
- Tested 2026-03-25: REF-20 (page 1 footer ✓), pagination buttons added (PR #127)
- Not yet tested: REF-21 (page 2), REF-22 (beyond total), REF-23 (page 0)
5. [ ] Execute REF-30 through REF-34 (edge cases)
6. [ ] Execute REF-40 through REF-45 (tier badges on card embeds)
7. [ ] Execute REF-50 through REF-55 (post-game hook -- requires live game)
8. [ ] Execute REF-60 through REF-64 (tier-up notifications -- requires threshold crossing)
9. [ ] Execute REF-70 through REF-72 (cross-command badge propagation)
10. [~] Execute REF-80 through REF-82 (force-evaluate API)
- Tested 2026-03-25: REF-80 (force evaluate ✓ — used to seed 100 cards for team 31)
- Not yet tested: REF-81, REF-82
### 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)