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.
81 lines
2.7 KiB
Go
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
|
|
}
|
|
}
|
|
}
|