package web import ( "bytes" "image/png" "net/http" "net/http/httptest" "net/url" "regexp" "strings" "testing" ) // The casino had no share card for as long as it existed, and the reason is worth // keeping in a test rather than in a memory: every route was behind sign-in, so a // link pasted into a chat window unfurled as whatever the *auth screen* said. Meta // tags on a page nobody unauthenticated can fetch are meta tags nobody reads. // // So the two things these tests hold down are: a stranger gets a real page at the // front door, and a stranger gets the picture. Everything else stays shut. func TestTheDoorIsOpenToStrangers(t *testing.T) { s := newCasino(t) mux := http.NewServeMux() s.casinoRoutes(mux) w := httptest.NewRecorder() mux.ServeHTTP(w, httptest.NewRequest("GET", "/games", nil)) if w.Code != http.StatusOK { t.Fatalf("GET /games as a stranger = %d, want 200: the front door has to be a page an unfurl bot can read", w.Code) } body := w.Body.String() for _, want := range []string{ `property="og:image"`, `property="og:title"`, `property="og:description"`, `name="twitter:card"`, "og.png", "Sign in", } { if !strings.Contains(body, want) { t.Errorf("the door is missing %q", want) } } if strings.Contains(body, "unknown page") { t.Fatal("games_door is not in the games template set in server.go") } // A door you can play from is not a door. if strings.Contains(body, `data-move=`) { t.Error("the door is serving a table to somebody who has not signed in") } } func TestTheTablesStayShutToStrangers(t *testing.T) { s := newCasino(t) mux := http.NewServeMux() s.casinoRoutes(mux) for _, path := range []string{"/games/blackjack", "/games/holdem", "/games/uno", "/games/trivia", "/games/hangman", "/games/solitaire"} { w := httptest.NewRecorder() mux.ServeHTTP(w, httptest.NewRequest("GET", path, nil)) if w.Code != http.StatusFound { t.Errorf("GET %s as a stranger = %d, want a 302 to sign-in", path, w.Code) } } } func TestTheShareCardNeedsNoSignIn(t *testing.T) { s := newCasino(t) mux := http.NewServeMux() s.casinoRoutes(mux) w := httptest.NewRecorder() mux.ServeHTTP(w, httptest.NewRequest("GET", "/games/og.png", nil)) if w.Code != http.StatusOK { t.Fatalf("GET /games/og.png as a stranger = %d, want 200", w.Code) } if ct := w.Header().Get("Content-Type"); ct != "image/png" { t.Errorf("Content-Type = %q, want image/png", ct) } img, err := png.Decode(bytes.NewReader(w.Body.Bytes())) if err != nil { t.Fatalf("the share card is not a PNG: %v", err) } if b := img.Bounds(); b.Dx() != ogWidth || b.Dy() != ogHeight { t.Errorf("share card is %dx%d, want %dx%d — every unfurler crops to that", b.Dx(), b.Dy(), ogWidth, ogHeight) } } // An og:image that 404s is worth exactly as much as no og:image, and the two ways // the casino is reachable disagree about where the picture lives: on the games host // it's /og.png (hostRouter puts the /games back on), and anywhere else it's // /games/og.png. So take the URL the page actually advertises and go and get it. func TestTheAdvertisedShareCardIsReallyThere(t *testing.T) { for _, host := range []string{"", "games.parodia.dev"} { name := "no games host" if host != "" { name = host } t.Run(name, func(t *testing.T) { s := newCasino(t) s.cfg.Games.Host = host mux := http.NewServeMux() s.casinoRoutes(mux) srv := s.hostRouter(mux) // what production actually serves // Ask the door where its picture is. w := httptest.NewRecorder() door := httptest.NewRequest("GET", "/games", nil) if host != "" { door.Host = host } srv.ServeHTTP(w, door) img := ogImageURL(t, w.Body.String()) // Then go and fetch it, the way a chat server would. u, err := url.Parse(img) if err != nil { t.Fatalf("og:image %q is not a URL: %v", img, err) } if u.Host == "" { t.Fatalf("og:image %q is relative — no unfurler will resolve it", img) } w = httptest.NewRecorder() get := httptest.NewRequest("GET", u.Path, nil) get.Host = u.Host srv.ServeHTTP(w, get) if w.Code != http.StatusOK { t.Fatalf("the door advertises og:image %s, and fetching it gives %d", img, w.Code) } if ct := w.Header().Get("Content-Type"); ct != "image/png" { t.Errorf("og:image %s served %q, want image/png", img, ct) } }) } } func ogImageURL(t *testing.T, body string) string { t.Helper() m := regexp.MustCompile(`property="og:image" content="([^"]+)"`).FindStringSubmatch(body) if m == nil { t.Fatal("the door serves no og:image at all") } return m[1] } // Both rooms have to have a card, because the room is picked at request time and a // missing one serves zero bytes rather than an error. func TestTheShareCardKnowsBothRooms(t *testing.T) { cards, err := drawShareCards() if err != nil { t.Fatal(err) } for _, rm := range []room{roomDay, roomNight} { if len(cards[rm.Slug]) == 0 { t.Errorf("no share card for %s", rm.Slug) } } if len(cards) != len(ogPalettes) { t.Errorf("drew %d cards for %d palettes", len(cards), len(ogPalettes)) } }