Files
Pete/internal/web/zz_drive_test.go
prosolis 8ec13eab5b games: the casino moves out, and gets a clock of its own
The tables were living in the news app's shell: Pete's face in the header
and the footer, the channel nav, search, the reader, the weather canvas,
the PWA. A casino is not a news page with a felt on it.

So it gets its own layout. What carries over is the design language — the
four palette vars, Fredoka/Nunito, the fat rounded cards, the dropped
shadow. What doesn't is every control it has no use for. gamesPage stops
embedding the news pageData, which is what keeps the furniture from
drifting back one convenient field at a time.

It keeps a clock, but tells a different joke with it: Casinopolis by day,
Casino Night Zone from six, palette and felt and the sign over the door all
changing together. The rule lives in roomAt() for the first paint and again
in the browser, so a player abroad gets their own evening.

And the cards are cards now — corner indices in both corners, the bottom
one upside down as printed, pips on the three-by-seven grid every real deck
has used for four hundred years, courts as a letter with the suit over each
shoulder. Driven in a real browser, both rooms, dealt through to a payout.
2026-07-13 23:40:33 -07:00

81 lines
2.7 KiB
Go

package web
import (
"encoding/json"
"fmt"
"io/fs"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"time"
"pete/internal/config"
"pete/internal/storage"
)
// TestDrive is not a test. It is a hand crank: it stands the casino up on a real
// port with one funded player so a browser can be pointed at it. Skipped unless
// PETE_DRIVE is set. Delete it if it ever stops earning its keep.
func TestDrive(t *testing.T) {
if os.Getenv("PETE_DRIVE") == "" {
t.Skip("set PETE_DRIVE=1 to hand-drive the casino")
}
// Built through New() with the tables already open, because the /games routes
// are registered at construction — newCasino() switches them on afterwards,
// which is fine for handler tests and useless for a browser.
storage.Close()
if err := storage.Init(filepath.Join(t.TempDir(), "drive.db")); err != nil {
t.Fatal(err)
}
defer storage.Close()
s, err := New(config.WebConfig{
SiteTitle: "Pete",
ListenAddr: ":0",
BaseURL: "http://127.0.0.1",
Games: config.GamesConfig{Enabled: true, MatrixServer: "parodia.dev"},
}, nil, true, config.AdventureConfig{}, nil)
if err != nil {
t.Fatal(err)
}
// New() only mounts the casino when auth is live, and auth means an OIDC
// discovery call. Sign the cookie by hand and mount the routes here instead —
// the handshake is not what a browser is here to look at.
s.auth = &Authenticator{secret: []byte("test-secret-key-at-least-16")}
fund(t, 5000)
static, err := fs.Sub(staticFS, "static")
if err != nil {
t.Fatal(err)
}
mux := http.NewServeMux()
mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(static))))
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(200) })
mux.HandleFunc("GET /games", s.handleLobby)
mux.HandleFunc("GET /games/blackjack", s.handleBlackjack)
mux.HandleFunc("GET /api/games/table", s.handleTable)
mux.HandleFunc("POST /api/games/buyin", s.handleBuyIn)
mux.HandleFunc("POST /api/games/cashout", s.handleCashOut)
mux.HandleFunc("POST /api/games/blackjack/deal", s.handleDeal)
mux.HandleFunc("POST /api/games/blackjack/move", s.handleMove)
srv := httptest.NewServer(mux)
defer srv.Close()
payload, _ := json.Marshal(SessionUser{
Sub: "sub-1", Username: "reala", Exp: time.Now().Add(time.Hour).Unix(),
})
fmt.Printf("DRIVE_URL=%s\nDRIVE_COOKIE=%s=%s\n", srv.URL, sessionCookie, s.auth.sign(payload))
// Long enough for a browser to buy in, deal a few hands, and be screenshotted.
deadline := time.Now().Add(3 * time.Minute)
for time.Now().Before(deadline) {
time.Sleep(500 * time.Millisecond)
if _, err := http.Get(srv.URL + "/healthz"); err != nil {
return
}
}
}