diff --git a/tests/refractor-integration-test-plan.md b/tests/refractor-integration-test-plan.md new file mode 100644 index 0000000..a515b33 --- /dev/null +++ b/tests/refractor-integration-test-plan.md @@ -0,0 +1,659 @@ +# 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: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 + +### 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:8080/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) | + +--- + +## 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 + +### 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 + +### CRITICAL: Missing team-level refractor cards list endpoint + +The `/refractor status` slash command calls `db_get("refractor/cards", params=[("team_id", team_id)])`, +which sends `GET /api/v2/refractor/cards?team_id=X` to the API. + +However, the API only defines these refractor routes: +- `GET /refractor/tracks` -- list all tracks +- `GET /refractor/tracks/{track_id}` -- single track +- `GET /refractor/cards/{card_id}` -- single card state by card ID +- `POST /refractor/cards/{card_id}/evaluate` -- force evaluate one card +- `POST /refractor/evaluate-game/{game_id}` -- evaluate all players in a game + +There is **no** `GET /refractor/cards` (list) endpoint that accepts a `team_id` +query parameter. This means `/refractor status` will likely receive a 404 or +routing error from the API, causing the "No refractor data found for your team" +fallback message for ALL users. + +**Action required**: Either: +1. Add a `GET /refractor/cards` list endpoint to the database API that accepts + `team_id`, `card_type`, `season`, and `tier` query parameters, OR +2. Restructure the bot command to fetch the team's card list first, then call + `GET /refractor/cards/{card_id}` for each card individually (N+1 problem). + +This gap should be verified immediately by running REF-01 against the dev server. +If the command returns "No refractor data found for your team" even when +refractor state exists, this is the cause. + +### 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-05 (API health) +2. [ ] Execute REF-01 through REF-06 (basic /refractor status) +3. [ ] Execute REF-10 through REF-19 (filters) +4. [ ] Execute REF-20 through REF-23 (pagination) +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) + +### Approximate Time Estimates +- API health checks: 1-2 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-85 minutes for full suite diff --git a/tests/refractor-preflight.sh b/tests/refractor-preflight.sh new file mode 100755 index 0000000..940b452 --- /dev/null +++ b/tests/refractor-preflight.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# refractor-preflight.sh — run from workstation after dev deploy +# Verifies the Refractor system endpoints and bot health + +echo "=== Dev API ===" +# Refractor endpoint exists (expect 401 = auth required) +STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://pddev.manticorum.com/api/v2/refractor/cards/1") +[ "$STATUS" = "401" ] && echo "PASS: refractor/cards responds (401)" || echo "FAIL: refractor/cards ($STATUS, expected 401)" + +# Old evolution endpoint removed (expect 404) +STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://pddev.manticorum.com/api/v2/evolution/cards/1") +[ "$STATUS" = "404" ] && echo "PASS: evolution/cards removed (404)" || echo "FAIL: evolution/cards ($STATUS, expected 404)" + +echo "" +echo "=== Discord Bot ===" +# Health check +curl -sf http://sba-bots:8080/health >/dev/null 2>&1 && echo "PASS: bot health OK" || echo "FAIL: bot health endpoint" + +# Recent refractor activity in logs +echo "" +echo "=== Recent Bot Logs (refractor) ===" +ssh sba-bots "docker logs --since 10m paper-dynasty_discord-app_1 2>&1 | grep -i refract" || echo "(no recent refractor activity)"