client_test.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. package steam
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "net/http/httptest"
  6. "os"
  7. "testing"
  8. )
  9. // The live free-search page (maxprice=free&specials=1) contains rows whose
  10. // price text is "Free" — those must be extracted with title, appid and URL.
  11. func TestLiveFixture_FreeToKeepExtracted(t *testing.T) {
  12. // Use a server that serves the fixture for the search URL; GetFreeGames
  13. // also queries featuredcategories — serve an empty JSON there.
  14. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  15. raw, err := os.ReadFile("testdata/search_free.html")
  16. if err != nil {
  17. t.Errorf("fixture: %v", err)
  18. return
  19. }
  20. w.Write(raw)
  21. }))
  22. t.Cleanup(srv.Close)
  23. c := NewClient()
  24. c.HTTP = srv.Client()
  25. // Redirect the client to the test server by rewriting the package-level
  26. // URLs through a custom transport? Simpler: the Client has fixed URLs, so
  27. // instead we test parseSearchRows directly on the fixture file.
  28. f, err := os.Open("testdata/search_free.html")
  29. if err != nil {
  30. t.Fatal(err)
  31. }
  32. defer f.Close()
  33. games, err := c.parseSearchRows(f, parseResultRow)
  34. if err != nil {
  35. t.Fatal(err)
  36. }
  37. if len(games) == 0 {
  38. t.Fatal("no free-to-keep games parsed from live fixture")
  39. }
  40. for _, g := range games {
  41. if g.ID == "" || g.Slug == "" || g.URL == "" {
  42. t.Errorf("game %+v missing id/slug/url", g.Title)
  43. }
  44. if g.Kind != "free-to-keep" {
  45. t.Errorf("game %q kind = %q, want free-to-keep", g.Title, g.Kind)
  46. }
  47. }
  48. }
  49. // The specials page (any discount) must yield rows with real prices and
  50. // percents — the deal data Paul asked for (e.g. "-60%", "19,99€", "7,99€").
  51. func TestLiveFixture_DealsHavePricesAndPercent(t *testing.T) {
  52. c := NewClient()
  53. f, err := os.Open("testdata/search_specials.html")
  54. if err != nil {
  55. t.Fatal(err)
  56. }
  57. defer f.Close()
  58. games, err := c.parseSearchRows(f, parseDealRow)
  59. if err != nil {
  60. t.Fatal(err)
  61. }
  62. if len(games) == 0 {
  63. t.Fatal("no deals parsed from live specials fixture")
  64. }
  65. for _, g := range games {
  66. if g.DiscountPct <= 0 {
  67. t.Errorf("deal %q has DiscountPct %d, want > 0", g.Title, g.DiscountPct)
  68. }
  69. if g.FinalPriceFmt == "" {
  70. t.Errorf("deal %q has no final price", g.Title)
  71. }
  72. if g.Kind != "deal" {
  73. t.Errorf("deal %q kind = %q", g.Title, g.Kind)
  74. }
  75. }
  76. // Sorted by discount (deepest first) is enforced by GetDeals.
  77. }
  78. // Regression: parseSearchRows must use the passed-in parse function, not
  79. // hardcode parseResultRow (the Oct 2026 bug that made GetDeals return 0).
  80. func TestParseSearchRowsUsesProvidedParseFn(t *testing.T) {
  81. f, err := os.Open("testdata/search_specials.html")
  82. if err != nil {
  83. t.Fatal(err)
  84. }
  85. defer f.Close()
  86. c := NewClient()
  87. deals, err := c.parseSearchRows(f, parseDealRow)
  88. if err != nil {
  89. t.Fatal(err)
  90. }
  91. if len(deals) == 0 {
  92. t.Fatal("parseDealRow was not applied — parse fn wiring broken (regression)")
  93. }
  94. }
  95. // Free rows on the free page must never carry a real price (they are free).
  96. func TestLiveFixture_FreeRowsHaveNoFinalPrice(t *testing.T) {
  97. c := NewClient()
  98. f, err := os.Open("testdata/search_free.html")
  99. if err != nil {
  100. t.Fatal(err)
  101. }
  102. defer f.Close()
  103. games, err := c.parseSearchRows(f, parseResultRow)
  104. if err != nil {
  105. t.Fatal(err)
  106. }
  107. for _, g := range games {
  108. if g.FinalPriceFmt != "" {
  109. t.Errorf("free game %q has final price %q", g.Title, g.FinalPriceFmt)
  110. }
  111. }
  112. }
  113. // The featured-categories API (free weekend) must parse without error on the
  114. // live fixture; free-weekend items carry kind "free-weekend".
  115. func TestLiveFixture_FeaturedCategoriesParses(t *testing.T) {
  116. raw, err := os.ReadFile("testdata/featured.json")
  117. if err != nil {
  118. t.Fatal(err)
  119. }
  120. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
  121. w.Write(raw)
  122. }))
  123. t.Cleanup(srv.Close)
  124. sections, err := decodeFeaturedSections(raw)
  125. if err != nil {
  126. t.Fatalf("decode featured fixture: %v", err)
  127. }
  128. if len(sections) == 0 {
  129. t.Fatal("no sections in featured fixture")
  130. }
  131. }
  132. // decodeFeatured decodes the featuredcategories JSON the same way
  133. // getFreeWeekend does.
  134. func decodeFeatured(raw []byte) (map[string]featuredSection, error) {
  135. var sections map[string]featuredSection
  136. err := json.Unmarshal(raw, &sections)
  137. return sections, err
  138. }