package steam import ( "encoding/json" "net/http" "net/http/httptest" "os" "testing" ) // The live free-search page (maxprice=free&specials=1) contains rows whose // price text is "Free" — those must be extracted with title, appid and URL. func TestLiveFixture_FreeToKeepExtracted(t *testing.T) { // Use a server that serves the fixture for the search URL; GetFreeGames // also queries featuredcategories — serve an empty JSON there. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { raw, err := os.ReadFile("testdata/search_free.html") if err != nil { t.Errorf("fixture: %v", err) return } w.Write(raw) })) t.Cleanup(srv.Close) c := NewClient() c.HTTP = srv.Client() // Redirect the client to the test server by rewriting the package-level // URLs through a custom transport? Simpler: the Client has fixed URLs, so // instead we test parseSearchRows directly on the fixture file. f, err := os.Open("testdata/search_free.html") if err != nil { t.Fatal(err) } defer f.Close() games, err := c.parseSearchRows(f, parseResultRow) if err != nil { t.Fatal(err) } if len(games) == 0 { t.Fatal("no free-to-keep games parsed from live fixture") } for _, g := range games { if g.ID == "" || g.Slug == "" || g.URL == "" { t.Errorf("game %+v missing id/slug/url", g.Title) } if g.Kind != "free-to-keep" { t.Errorf("game %q kind = %q, want free-to-keep", g.Title, g.Kind) } } } // The specials page (any discount) must yield rows with real prices and // percents — the deal data Paul asked for (e.g. "-60%", "19,99€", "7,99€"). func TestLiveFixture_DealsHavePricesAndPercent(t *testing.T) { c := NewClient() f, err := os.Open("testdata/search_specials.html") if err != nil { t.Fatal(err) } defer f.Close() games, err := c.parseSearchRows(f, parseDealRow) if err != nil { t.Fatal(err) } if len(games) == 0 { t.Fatal("no deals parsed from live specials fixture") } for _, g := range games { if g.DiscountPct <= 0 { t.Errorf("deal %q has DiscountPct %d, want > 0", g.Title, g.DiscountPct) } if g.FinalPriceFmt == "" { t.Errorf("deal %q has no final price", g.Title) } if g.Kind != "deal" { t.Errorf("deal %q kind = %q", g.Title, g.Kind) } } // Sorted by discount (deepest first) is enforced by GetDeals. } // Regression: parseSearchRows must use the passed-in parse function, not // hardcode parseResultRow (the Oct 2026 bug that made GetDeals return 0). func TestParseSearchRowsUsesProvidedParseFn(t *testing.T) { f, err := os.Open("testdata/search_specials.html") if err != nil { t.Fatal(err) } defer f.Close() c := NewClient() deals, err := c.parseSearchRows(f, parseDealRow) if err != nil { t.Fatal(err) } if len(deals) == 0 { t.Fatal("parseDealRow was not applied — parse fn wiring broken (regression)") } } // Free rows on the free page must never carry a real price (they are free). func TestLiveFixture_FreeRowsHaveNoFinalPrice(t *testing.T) { c := NewClient() f, err := os.Open("testdata/search_free.html") if err != nil { t.Fatal(err) } defer f.Close() games, err := c.parseSearchRows(f, parseResultRow) if err != nil { t.Fatal(err) } for _, g := range games { if g.FinalPriceFmt != "" { t.Errorf("free game %q has final price %q", g.Title, g.FinalPriceFmt) } } } // The featured-categories API (free weekend) must parse without error on the // live fixture; free-weekend items carry kind "free-weekend". func TestLiveFixture_FeaturedCategoriesParses(t *testing.T) { raw, err := os.ReadFile("testdata/featured.json") if err != nil { t.Fatal(err) } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write(raw) })) t.Cleanup(srv.Close) sections, err := decodeFeaturedSections(raw) if err != nil { t.Fatalf("decode featured fixture: %v", err) } if len(sections) == 0 { t.Fatal("no sections in featured fixture") } } // decodeFeatured decodes the featuredcategories JSON the same way // getFreeWeekend does. func decodeFeatured(raw []byte) (map[string]featuredSection, error) { var sections map[string]featuredSection err := json.Unmarshal(raw, §ions) return sections, err }