package gog import ( "context" "net/http" "net/http/httptest" "os" "strings" "testing" ) // serveHome serves the given HTML for all requests. func serveHome(t *testing.T, html string) *Client { t.Helper() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "text/html; charset=UTF-8") w.Write([]byte(html)) })) t.Cleanup(srv.Close) c := NewClient() c.HTTP = srv.Client() // Point the client at the test server by overriding the home URL via // BaseURL-like behavior: homeURL is a package const, so we test through // the extraction path instead. _ = srv return c } // The live homepage embeds the giveaway in gogcom-store-state JSON. func TestLiveFixture_GiveawayFromStoreState(t *testing.T) { html, err := os.ReadFile("testdata/home.html") if err != nil { t.Fatal(err) } state := extractStoreState(string(html)) if state == "" { t.Fatal("gogcom-store-state script not found in live fixture") } sec, err := parseGiveawaySection([]byte(state)) if err != nil { t.Fatalf("parse giveaway: %v", err) } if sec == nil { t.Fatal("no giveaway section found in live fixture") } if sec.Product == nil || sec.Product.Title == "" || sec.Product.Slug == "" { t.Fatalf("giveaway product incomplete: %+v", sec.Product) } if sec.Product.Slug != "a_new_beginning" { t.Errorf("slug = %q, want a_new_beginning (live giveaway Oct 2026)", sec.Product.Slug) } if sec.Product.ID == "" { t.Error("product id missing") } if sec.EndDate.IsZero() { t.Error("giveaway endDate missing") } } // Regression: the section is identified by STRUCTURE (endDate + product with // slug/title), not by a fixed section UUID — survives layout changes. func TestGiveawayRecognitionIsStructural(t *testing.T) { // A section with endDate + product anywhere in the state is found, // even under an arbitrary key. state := `{"someKey":{"x":{"endDate":"2026-11-02T16:00:59.000+02:00","product":{"id":"123","title":"Test Game","slug":"test_game"}}},"other":{"endDate":"2020-01-01T00:00:00Z","product":{"id":"0","title":"Old","slug":"old"}}}` sec, err := parseGiveawaySection([]byte(state)) if err != nil { t.Fatal(err) } if sec == nil || sec.Product.Slug != "test_game" { t.Fatalf("structural detection failed: %+v", sec) } } // No store-state script → explicit error (the client must not pretend // success with an empty list). func TestMissingStoreStateIsAnError(t *testing.T) { if state := extractStoreState("nothing here"); state != "" { t.Fatalf("expected empty state, got %q", state) } } // A store-state without any giveaway object → nil section, no error. func TestStateWithoutGiveawayYieldsNoSection(t *testing.T) { sec, err := parseGiveawaySection([]byte(`{"pageMetadata":{"locale":"en-US"}}`)) if err != nil { t.Fatal(err) } if sec != nil { t.Fatalf("expected no section, got %+v", sec) } } // Broken store-state JSON → error, not panic. func TestBrokenStoreStateJSONIsAnError(t *testing.T) { if _, err := parseGiveawaySection([]byte("{not json")); err == nil { t.Fatal("expected error for broken JSON") } } // The extraction handles the script tag with trailing content after . func TestExtractStoreStateTrimsScript(t *testing.T) { html := `` state := extractStoreState(html) if strings.TrimSpace(state) != `{"a":1}` { t.Fatalf("state = %q", state) } } // End-to-end via HTTP: client fetches the (test) homepage and extracts the // giveaway. Uses a package-level URL override to avoid network. func TestGetFreeGamesEndToEnd(t *testing.T) { html, err := os.ReadFile("testdata/home.html") if err != nil { t.Fatal(err) } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write(html) })) t.Cleanup(srv.Close) c := NewClient() c.HTTP = srv.Client() c.HomeURL = srv.URL games, err := c.GetFreeGames(context.Background()) if err != nil { t.Fatal(err) } if len(games) != 1 { t.Fatalf("want exactly 1 giveaway, got %d", len(games)) } g := games[0] if g.Store != "gog" || g.Kind != "free-now" { t.Errorf("store/kind = %s/%s, want gog/free-now", g.Store, g.Kind) } if !strings.HasPrefix(g.ID, "gog-") { t.Errorf("id = %q, want gog- prefix", g.ID) } if g.URL == "" { t.Error("url missing") } }