package web import ( "net/http/httptest" "strings" "testing" "pete/internal/storage" ) // TestArtPaletteSplitsFamilies is the regression for the whole point of W3: two // different kinds of dispatch must not render the same image. Before this, every // card in the section was the same violet gradient with a swapped emoji. func TestArtPaletteSplitsFamilies(t *testing.T) { death := advPaletteFor("death", "") siege := advPaletteFor("siege_win", "") zone := advPaletteFor("zone_clear", "") if death == siege || siege == zone || death == zone { t.Errorf("families share a palette: death=%v siege=%v zone=%v", death, siege, zone) } // Treasure is tinted by the rarity gogobee already computes and currently // spends on an adjective in a sentence. leg := advPaletteFor("treasure_found", "legendary") rare := advPaletteFor("treasure_found", "rare") if leg == rare { t.Errorf("legendary and rare hoards render identically: %v", leg) } // Case shouldn't matter — the rarity word arrives however gogobee wrote it. if advPaletteFor("treasure_found", "Legendary") != leg { t.Error("rarity match is case-sensitive") } // An unrecognised rarity still gets the story-grade treatment rather than // falling through to the neutral card. if advPaletteFor("treasure_found", "mythic") != leg { t.Error("unknown rarity dropped to the neutral palette") } } // TestRealmFirstEarnsCeremony pins that the priority/bulletin split gogobee // already computes now changes how a card LOOKS, not just whether Matrix gets // pinged. A first-ever clear and the ninth repeat of it must be distinguishable. func TestRealmFirstEarnsCeremony(t *testing.T) { cases := []struct { eventType, tier string want bool }{ {"zone_first", "priority", true}, {"zone_clear", "bulletin", false}, {"boss_first", "priority", true}, {"boss_kill", "bulletin", false}, {"treasure_found", "priority", true}, // realm-first hoard {"treasure_found", "bulletin", false}, // someone else already pulled it {"death", "priority", false}, // priority, but not a "first" } for _, c := range cases { if got := advIsRealmFirst(c.eventType, c.tier); got != c.want { t.Errorf("%s/%s ceremony = %v, want %v", c.eventType, c.tier, got, c.want) } } // And it reaches the card as a ribbon. ev := &storage.AdvEvent{EventType: "zone_first", Tier: "priority", Subject: "Josie", Zone: "The Sump", Level: 9} card := advArtCardFor("zone_first", ev) if card.Ceremony == "" { t.Error("realm-first card has no ribbon") } if !strings.Contains(advRenderArt(card), card.Ceremony) { t.Error("ribbon text never reached the SVG") } plain := advArtCardFor("zone_clear", &storage.AdvEvent{EventType: "zone_clear", Tier: "bulletin", Subject: "Josie", Zone: "The Sump"}) if plain.Ceremony != "" { t.Error("a repeat clear got the realm-first ribbon") } } // TestArtCardCarriesNouns: the card names the thing the dispatch is about, and // which field that is differs per family. A siege is about the boss; a treasure // is about the item. func TestArtCardCarriesNouns(t *testing.T) { treasure := advArtCardFor("treasure_found", &storage.AdvEvent{ EventType: "treasure_found", Tier: "bulletin", Subject: "Josie", Zone: "The Sump", Stakes: "Ring of Nine Sorrows", Outcome: "epic", Level: 12, }) if treasure.Noun != "Ring of Nine Sorrows" { t.Errorf("treasure noun = %q, want the item", treasure.Noun) } if treasure.Label != "EPIC" { t.Errorf("treasure label = %q, want the rarity", treasure.Label) } svg := advRenderArt(treasure) for _, want := range []string{"Ring of Nine Sorrows", "Josie", "The Sump", "level 12", "EPIC"} { if !strings.Contains(svg, want) { t.Errorf("treasure card missing %q", want) } } boss := advArtCardFor("boss_kill", &storage.AdvEvent{ EventType: "boss_kill", Subject: "Josie", Boss: "Aldric the Pale", Zone: "dragons_lair", Level: 14, }) if boss.Noun != "Aldric the Pale" { t.Errorf("boss noun = %q, want the boss", boss.Noun) } // A dispatch with no stored fact still renders — that is every story from // before the fact table, plus the window where the best-effort fact insert // failed. It degrades to the old emblem, not to a broken image. bare := advArtCardFor("death", nil) if bare.Noun != "" || bare.Label == "" { t.Errorf("factless card = %+v, want label-only", bare) } if b := advRenderArt(bare); !strings.Contains(b, "alert(1)`, Zone: "the Underforge", Level: 3}) a := advRenderArt(card) if a != advRenderArt(card) { t.Error("render is not deterministic") } if strings.Contains(a, "