cricinfo-cli-go 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/AGENTS.md +63 -0
  2. package/CONTRIBUTORS.md +75 -0
  3. package/LICENSE +21 -0
  4. package/Makefile +131 -0
  5. package/README.md +130 -0
  6. package/bin/cricinfo.js +44 -0
  7. package/cmd/cricinfo/main.go +15 -0
  8. package/go.mod +10 -0
  9. package/go.sum +10 -0
  10. package/internal/app/app.go +11 -0
  11. package/internal/app/app_test.go +122 -0
  12. package/internal/buildinfo/buildinfo.go +16 -0
  13. package/internal/cli/analysis.go +262 -0
  14. package/internal/cli/analysis_test.go +175 -0
  15. package/internal/cli/competitions.go +154 -0
  16. package/internal/cli/competitions_test.go +165 -0
  17. package/internal/cli/leagues.go +297 -0
  18. package/internal/cli/leagues_test.go +194 -0
  19. package/internal/cli/matches.go +403 -0
  20. package/internal/cli/matches_test.go +413 -0
  21. package/internal/cli/players.go +263 -0
  22. package/internal/cli/players_test.go +384 -0
  23. package/internal/cli/root.go +141 -0
  24. package/internal/cli/search.go +119 -0
  25. package/internal/cli/teams.go +214 -0
  26. package/internal/cli/teams_test.go +192 -0
  27. package/internal/cricinfo/analysis.go +1401 -0
  28. package/internal/cricinfo/analysis_phase15_test.go +267 -0
  29. package/internal/cricinfo/client.go +471 -0
  30. package/internal/cricinfo/client_test.go +280 -0
  31. package/internal/cricinfo/cmd/fixture-refresh/main.go +145 -0
  32. package/internal/cricinfo/competitions.go +405 -0
  33. package/internal/cricinfo/competitions_phase13_test.go +234 -0
  34. package/internal/cricinfo/coverage_ledger.go +122 -0
  35. package/internal/cricinfo/coverage_ledger_test.go +253 -0
  36. package/internal/cricinfo/decode.go +115 -0
  37. package/internal/cricinfo/decode_test.go +100 -0
  38. package/internal/cricinfo/entity_index.go +618 -0
  39. package/internal/cricinfo/entity_index_test.go +175 -0
  40. package/internal/cricinfo/fixture_matrix.go +243 -0
  41. package/internal/cricinfo/fixture_matrix_test.go +49 -0
  42. package/internal/cricinfo/fixtures_test.go +264 -0
  43. package/internal/cricinfo/historical_hydration.go +1641 -0
  44. package/internal/cricinfo/historical_phase14_test.go +542 -0
  45. package/internal/cricinfo/leagues.go +1210 -0
  46. package/internal/cricinfo/leagues_phase12_test.go +324 -0
  47. package/internal/cricinfo/live_leagues_test.go +169 -0
  48. package/internal/cricinfo/live_matches_test.go +203 -0
  49. package/internal/cricinfo/live_matrix_test.go +118 -0
  50. package/internal/cricinfo/live_players_test.go +122 -0
  51. package/internal/cricinfo/live_search_test.go +86 -0
  52. package/internal/cricinfo/live_smoke_test.go +213 -0
  53. package/internal/cricinfo/live_teams_test.go +104 -0
  54. package/internal/cricinfo/matches.go +1508 -0
  55. package/internal/cricinfo/matches_phase7_test.go +207 -0
  56. package/internal/cricinfo/matches_phase9_test.go +253 -0
  57. package/internal/cricinfo/normalize_entities.go +1727 -0
  58. package/internal/cricinfo/normalize_leagues.go +346 -0
  59. package/internal/cricinfo/players.go +1332 -0
  60. package/internal/cricinfo/players_phase10_test.go +174 -0
  61. package/internal/cricinfo/players_phase11_test.go +373 -0
  62. package/internal/cricinfo/render_contract.go +1088 -0
  63. package/internal/cricinfo/render_phase4_test.go +633 -0
  64. package/internal/cricinfo/renderer.go +1689 -0
  65. package/internal/cricinfo/resolver.go +813 -0
  66. package/internal/cricinfo/resolver_test.go +244 -0
  67. package/internal/cricinfo/teams.go +603 -0
  68. package/internal/cricinfo/teams_phase8_test.go +231 -0
  69. package/internal/cricinfo/testdata/fixtures/README.md +43 -0
  70. package/internal/cricinfo/testdata/fixtures/aux-competition-metadata/broadcasts.json +11 -0
  71. package/internal/cricinfo/testdata/fixtures/aux-competition-metadata/officials.json +150 -0
  72. package/internal/cricinfo/testdata/fixtures/details-plays/detail-110.json +157 -0
  73. package/internal/cricinfo/testdata/fixtures/details-plays/detail-52545007.json +145 -0
  74. package/internal/cricinfo/testdata/fixtures/details-plays/detail-52559021.json +143 -0
  75. package/internal/cricinfo/testdata/fixtures/details-plays/plays.json +15 -0
  76. package/internal/cricinfo/testdata/fixtures/endpoint-matrix.tsv +19 -0
  77. package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/fow-1.json +12 -0
  78. package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/fow.json +42 -0
  79. package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/innings-1-2.json +38 -0
  80. package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/partnership-1.json +31 -0
  81. package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/partnerships.json +42 -0
  82. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/calendar-offdays.json +20 -0
  83. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/calendar-ondays.json +21 -0
  84. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/calendar.json +14 -0
  85. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-2025.json +13 -0
  86. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-group-1.json +13 -0
  87. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-groups.json +11 -0
  88. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-type-1.json +13 -0
  89. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-types.json +11 -0
  90. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/seasons.json +30 -0
  91. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/standings-item-1.json +72 -0
  92. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/standings-root.json +3 -0
  93. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/standings.json +15 -0
  94. package/internal/cricinfo/testdata/fixtures/matches-competitions/competition.json +460 -0
  95. package/internal/cricinfo/testdata/fixtures/matches-competitions/event-1529474.json +86 -0
  96. package/internal/cricinfo/testdata/fixtures/matches-competitions/matchcards-1527966.json +368 -0
  97. package/internal/cricinfo/testdata/fixtures/matches-competitions/situation-1529474.json +10 -0
  98. package/internal/cricinfo/testdata/fixtures/players/athlete-1361257-statistics.json +126 -0
  99. package/internal/cricinfo/testdata/fixtures/players/athlete-1361257.json +113 -0
  100. package/internal/cricinfo/testdata/fixtures/players/roster-1361257-linescores-1-1-statistics-0.json +208 -0
  101. package/internal/cricinfo/testdata/fixtures/players/roster-1361257-linescores-1-2-statistics-0.json +252 -0
  102. package/internal/cricinfo/testdata/fixtures/players/roster-1361257-linescores.json +74 -0
  103. package/internal/cricinfo/testdata/fixtures/players/roster-1361257-statistics-0.json +1008 -0
  104. package/internal/cricinfo/testdata/fixtures/root-discovery/events.json +72 -0
  105. package/internal/cricinfo/testdata/fixtures/root-discovery/root.json +28 -0
  106. package/internal/cricinfo/testdata/fixtures/team-competitor/competitor-789643.json +40 -0
  107. package/internal/cricinfo/testdata/fixtures/team-competitor/leaders-789643.json +353 -0
  108. package/internal/cricinfo/testdata/fixtures/team-competitor/records-789643.json +91 -0
  109. package/internal/cricinfo/testdata/fixtures/team-competitor/roster-1147772-object.json +231 -0
  110. package/internal/cricinfo/testdata/fixtures/team-competitor/roster-1147772.json +235 -0
  111. package/internal/cricinfo/testdata/fixtures/team-competitor/roster-789643.json +322 -0
  112. package/internal/cricinfo/testdata/fixtures/team-competitor/scores-789643.json +19 -0
  113. package/internal/cricinfo/testdata/fixtures/team-competitor/statistics-789643.json +629 -0
  114. package/internal/cricinfo/testdata/fixtures/team-competitor/team-789643-athletes.json +7 -0
  115. package/internal/cricinfo/testdata/fixtures/team-competitor/team-789643.json +67 -0
  116. package/internal/cricinfo/testdata/golden/match-empty.golden +1 -0
  117. package/internal/cricinfo/testdata/golden/match-list.golden +2 -0
  118. package/internal/cricinfo/testdata/golden/match-partial.golden +3 -0
  119. package/internal/cricinfo/types.go +54 -0
  120. package/package.json +51 -0
  121. package/scripts/postinstall.js +153 -0
@@ -0,0 +1,324 @@
1
+ package cricinfo
2
+
3
+ import (
4
+ "context"
5
+ "net/http"
6
+ "net/http/httptest"
7
+ "path/filepath"
8
+ "strings"
9
+ "testing"
10
+ "time"
11
+ )
12
+
13
+ func TestPhase12FixtureNormalizationForCalendarSectionsAndStandingsChains(t *testing.T) {
14
+ t.Parallel()
15
+
16
+ onDaysBody := mustReadFixtureBytes(t, "leagues-seasons-standings/calendar-ondays.json")
17
+ onDays, err := NormalizeCalendarDays(onDaysBody)
18
+ if err != nil {
19
+ t.Fatalf("NormalizeCalendarDays ondays error: %v", err)
20
+ }
21
+ if len(onDays) == 0 {
22
+ t.Fatalf("expected calendar day entries from ondays fixture")
23
+ }
24
+ if onDays[0].DayType != "ondays" {
25
+ t.Fatalf("expected ondays type, got %+v", onDays[0])
26
+ }
27
+ if len(onDays[0].Sections) == 0 || onDays[0].Sections[0] != "Regular Season" {
28
+ t.Fatalf("expected section labels from ondays fixture, got %+v", onDays[0])
29
+ }
30
+
31
+ offDaysBody := mustReadFixtureBytes(t, "leagues-seasons-standings/calendar-offdays.json")
32
+ offDays, err := NormalizeCalendarDays(offDaysBody)
33
+ if err != nil {
34
+ t.Fatalf("NormalizeCalendarDays offdays error: %v", err)
35
+ }
36
+ if len(offDays) == 0 || offDays[0].DayType != "offdays" {
37
+ t.Fatalf("expected offdays entries, got %+v", offDays)
38
+ }
39
+
40
+ standingsItemBody := mustReadFixtureBytes(t, "leagues-seasons-standings/standings-item-1.json")
41
+ group, err := NormalizeStandingsGroup(standingsItemBody)
42
+ if err != nil {
43
+ t.Fatalf("NormalizeStandingsGroup error: %v", err)
44
+ }
45
+ if group.ID != "1" || len(group.Entries) == 0 {
46
+ t.Fatalf("expected standings group entries from standings fixture, got %+v", group)
47
+ }
48
+ if !strings.Contains(group.Entries[0].ScoreSummary, "Rank 1") || !strings.Contains(group.Entries[0].ScoreSummary, "Pts 16") {
49
+ t.Fatalf("expected rank/points summary in standings entry, got %+v", group.Entries[0])
50
+ }
51
+ }
52
+
53
+ func TestLeagueServicePhase12NavigationAndStandingsTraversal(t *testing.T) {
54
+ t.Parallel()
55
+
56
+ service := newPhase12LeagueTestService(t)
57
+
58
+ ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
59
+ defer cancel()
60
+
61
+ listResult, err := service.List(ctx, LeagueListOptions{Limit: 2})
62
+ if err != nil {
63
+ t.Fatalf("LeagueService.List error: %v", err)
64
+ }
65
+ if listResult.Kind != EntityLeague || listResult.Status == ResultStatusError {
66
+ t.Fatalf("unexpected list result: %+v", listResult)
67
+ }
68
+ if len(listResult.Items) == 0 {
69
+ t.Fatalf("expected list items from /leagues")
70
+ }
71
+
72
+ showResult, err := service.Show(ctx, "Mirwais Nika")
73
+ if err != nil {
74
+ t.Fatalf("LeagueService.Show error: %v", err)
75
+ }
76
+ if showResult.Kind != EntityLeague || showResult.Status == ResultStatusError {
77
+ t.Fatalf("unexpected show result: %+v", showResult)
78
+ }
79
+ shownLeague, ok := showResult.Data.(*League)
80
+ if !ok {
81
+ t.Fatalf("expected show data type League, got %T", showResult.Data)
82
+ }
83
+ if shownLeague.ID != "19138" {
84
+ t.Fatalf("expected resolved league id 19138, got %+v", shownLeague)
85
+ }
86
+
87
+ eventsResult, err := service.Events(ctx, "Mirwais Nika", LeagueListOptions{Limit: 5})
88
+ if err != nil {
89
+ t.Fatalf("LeagueService.Events error: %v", err)
90
+ }
91
+ if eventsResult.Kind != EntityMatch {
92
+ t.Fatalf("expected events result kind %q, got %q", EntityMatch, eventsResult.Kind)
93
+ }
94
+ if len(eventsResult.Items) == 0 {
95
+ t.Fatalf("expected at least one league event/match item")
96
+ }
97
+
98
+ calendarResult, err := service.Calendar(ctx, "Mirwais Nika")
99
+ if err != nil {
100
+ t.Fatalf("LeagueService.Calendar error: %v", err)
101
+ }
102
+ if calendarResult.Kind != EntityCalendarDay {
103
+ t.Fatalf("expected calendar kind %q, got %q", EntityCalendarDay, calendarResult.Kind)
104
+ }
105
+ if len(calendarResult.Items) < 3 {
106
+ t.Fatalf("expected normalized calendar day items, got %d", len(calendarResult.Items))
107
+ }
108
+
109
+ athletesResult, err := service.Athletes(ctx, "Mirwais Nika", LeagueListOptions{Limit: 5})
110
+ if err != nil {
111
+ t.Fatalf("LeagueService.Athletes error: %v", err)
112
+ }
113
+ if athletesResult.Kind != EntityPlayer {
114
+ t.Fatalf("expected athletes kind %q, got %q", EntityPlayer, athletesResult.Kind)
115
+ }
116
+ if len(athletesResult.Items) == 0 {
117
+ t.Fatalf("expected athlete results from fallback roster traversal")
118
+ }
119
+
120
+ seasonsResult, err := service.Seasons(ctx, "Mirwais Nika")
121
+ if err != nil {
122
+ t.Fatalf("LeagueService.Seasons error: %v", err)
123
+ }
124
+ if seasonsResult.Kind != EntitySeason {
125
+ t.Fatalf("expected seasons kind %q, got %q", EntitySeason, seasonsResult.Kind)
126
+ }
127
+ if len(seasonsResult.Items) == 0 {
128
+ t.Fatalf("expected season refs in seasons output")
129
+ }
130
+
131
+ seasonShow, err := service.SeasonShow(ctx, "Mirwais Nika", SeasonLookupOptions{SeasonQuery: "2025"})
132
+ if err != nil {
133
+ t.Fatalf("LeagueService.SeasonShow error: %v", err)
134
+ }
135
+ if seasonShow.Kind != EntitySeason {
136
+ t.Fatalf("expected season show kind %q, got %q", EntitySeason, seasonShow.Kind)
137
+ }
138
+ season, ok := seasonShow.Data.(Season)
139
+ if !ok {
140
+ t.Fatalf("expected season show data type Season, got %T", seasonShow.Data)
141
+ }
142
+ if season.ID != "2025" || season.Year != 2025 {
143
+ t.Fatalf("expected selected season 2025 payload, got %+v", season)
144
+ }
145
+
146
+ seasonTypes, err := service.SeasonTypes(ctx, "Mirwais Nika", SeasonLookupOptions{SeasonQuery: "2025"})
147
+ if err != nil {
148
+ t.Fatalf("LeagueService.SeasonTypes error: %v", err)
149
+ }
150
+ if seasonTypes.Kind != EntitySeasonType || len(seasonTypes.Items) == 0 {
151
+ t.Fatalf("expected season types list, got %+v", seasonTypes)
152
+ }
153
+
154
+ seasonGroups, err := service.SeasonGroups(ctx, "Mirwais Nika", SeasonLookupOptions{SeasonQuery: "2025", TypeQuery: "1"})
155
+ if err != nil {
156
+ t.Fatalf("LeagueService.SeasonGroups error: %v", err)
157
+ }
158
+ if seasonGroups.Kind != EntitySeasonGroup || len(seasonGroups.Items) == 0 {
159
+ t.Fatalf("expected season groups list, got %+v", seasonGroups)
160
+ }
161
+ firstGroup, ok := seasonGroups.Items[0].(SeasonGroup)
162
+ if !ok {
163
+ t.Fatalf("expected season group item type SeasonGroup, got %T", seasonGroups.Items[0])
164
+ }
165
+ if strings.TrimSpace(firstGroup.StandingsRef) == "" {
166
+ t.Fatalf("expected season group standings ref, got %+v", firstGroup)
167
+ }
168
+
169
+ standingsResult, err := service.Standings(ctx, "Mirwais Nika")
170
+ if err != nil {
171
+ t.Fatalf("LeagueService.Standings error: %v", err)
172
+ }
173
+ if standingsResult.Kind != EntityStandingsGroup {
174
+ t.Fatalf("expected standings kind %q, got %q", EntityStandingsGroup, standingsResult.Kind)
175
+ }
176
+ if len(standingsResult.Items) == 0 {
177
+ t.Fatalf("expected standings groups from ref-chain traversal")
178
+ }
179
+ groupItem, ok := standingsResult.Items[0].(StandingsGroup)
180
+ if !ok {
181
+ t.Fatalf("expected standings item type StandingsGroup, got %T", standingsResult.Items[0])
182
+ }
183
+ if len(groupItem.Entries) == 0 {
184
+ t.Fatalf("expected standings entries after traversal, got %+v", groupItem)
185
+ }
186
+ if strings.TrimSpace(groupItem.Entries[0].Name) == "" {
187
+ t.Fatalf("expected team name hydration in standings entries, got %+v", groupItem.Entries[0])
188
+ }
189
+ }
190
+
191
+ func newPhase12LeagueTestService(t *testing.T) *LeagueService {
192
+ t.Helper()
193
+
194
+ listFixture := []byte(`{"count":1,"pageIndex":1,"pageSize":25,"pageCount":1,"items":[{"$ref":"http://core.espnuk.org/v2/sports/cricket/leagues/19138"}]}`)
195
+ eventsPageFixture := []byte(`{"count":1,"pageIndex":1,"pageSize":25,"pageCount":1,"items":[{"$ref":"http://core.espnuk.org/v2/sports/cricket/leagues/19138/events/1529474"}]}`)
196
+ calendarFixture := mustReadFixtureBytes(t, "leagues-seasons-standings/calendar.json")
197
+ onDaysFixture := mustReadFixtureBytes(t, "leagues-seasons-standings/calendar-ondays.json")
198
+ offDaysFixture := mustReadFixtureBytes(t, "leagues-seasons-standings/calendar-offdays.json")
199
+ seasonsFixture := mustReadFixtureBytes(t, "leagues-seasons-standings/seasons.json")
200
+ seasonFixture := mustReadFixtureBytes(t, "leagues-seasons-standings/season-2025.json")
201
+ seasonTypesFixture := mustReadFixtureBytes(t, "leagues-seasons-standings/season-types.json")
202
+ seasonTypeFixture := mustReadFixtureBytes(t, "leagues-seasons-standings/season-type-1.json")
203
+ seasonGroupsFixture := mustReadFixtureBytes(t, "leagues-seasons-standings/season-groups.json")
204
+ seasonGroupFixture := mustReadFixtureBytes(t, "leagues-seasons-standings/season-group-1.json")
205
+ standingsRootFixture := mustReadFixtureBytes(t, "leagues-seasons-standings/standings-root.json")
206
+ standingsListFixture := mustReadFixtureBytes(t, "leagues-seasons-standings/standings.json")
207
+ standingsItemFixture := mustReadFixtureBytes(t, "leagues-seasons-standings/standings-item-1.json")
208
+ rosterFixture := mustReadFixtureBytes(t, "team-competitor/roster-789643.json")
209
+ playerFixture := mustReadFixtureBytes(t, "players/athlete-1361257.json")
210
+ teamFixture := mustReadFixtureBytes(t, "team-competitor/team-789643.json")
211
+ eventFixture := []byte(`{"$ref":"http://core.espnuk.org/v2/sports/cricket/leagues/19138/events/1529474","id":"1529474","shortDescription":"3rd Match","description":"3rd Match, Mirwais Nika","date":"2026-04-09T05:30:00Z","competitions":[{"$ref":"http://core.espnuk.org/v2/sports/cricket/leagues/19138/events/1529474/competitions/1529474","id":"1529474","description":"3rd Match","shortDescription":"3rd Match","competitors":[{"$ref":"http://core.espnuk.org/v2/sports/cricket/leagues/19138/events/1529474/competitions/1529474/competitors/789643","id":"789643","team":{"$ref":"http://core.espnuk.org/v2/sports/cricket/events/1529474/teams/789643"},"roster":{"$ref":"http://core.espnuk.org/v2/sports/cricket/leagues/19138/events/1529474/competitions/1529474/competitors/789643/roster"}}]}]}`)
212
+
213
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
214
+ base := "http://" + r.Host + "/v2/sports/cricket"
215
+ switch r.URL.Path {
216
+ case "/v2/sports/cricket/events":
217
+ _, _ = w.Write([]byte(`{"count":0,"pageIndex":1,"pageSize":25,"pageCount":0,"items":[]}`))
218
+ case "/v2/sports/cricket/leagues":
219
+ _, _ = w.Write(rewriteFixtureBaseURL(listFixture, base))
220
+ case "/v2/sports/cricket/leagues/19138":
221
+ _, _ = w.Write([]byte(`{"$ref":"` + base + `/leagues/19138","id":"19138","uid":"s:200~l:19138","name":"Mirwais Nika Provincial 3-Day","slug":"19138","events":{"$ref":"` + base + `/leagues/19138/events"},"seasons":{"$ref":"` + base + `/leagues/19138/seasons"}}`))
222
+ case "/v2/sports/cricket/leagues/19138/events":
223
+ _, _ = w.Write(rewriteFixtureBaseURL(eventsPageFixture, base))
224
+ case "/v2/sports/cricket/leagues/19138/events/1529474":
225
+ _, _ = w.Write(rewriteFixtureBaseURL(eventFixture, base))
226
+ case "/v2/sports/cricket/leagues/19138/events/1529474/competitions/1529474/competitors/789643/roster":
227
+ _, _ = w.Write(rewriteFixtureBaseURL(rosterFixture, base))
228
+ case "/v2/sports/cricket/leagues/19138/athletes":
229
+ _, _ = w.Write([]byte(`{"count":0,"pageIndex":1,"pageSize":25,"pageCount":0,"items":[]}`))
230
+ case "/v2/sports/cricket/leagues/19138/athletes/1361257":
231
+ _, _ = w.Write(rewriteFixtureBaseURL(playerFixture, base))
232
+ case "/v2/sports/cricket/athletes/1361257":
233
+ _, _ = w.Write(rewriteFixtureBaseURL(playerFixture, base))
234
+ case "/v2/sports/cricket/leagues/19138/calendar":
235
+ _, _ = w.Write(rewriteFixtureBaseURL(calendarFixture, base))
236
+ case "/v2/sports/cricket/leagues/19138/calendar/ondays":
237
+ _, _ = w.Write(rewriteFixtureBaseURL(onDaysFixture, base))
238
+ case "/v2/sports/cricket/leagues/19138/calendar/offdays":
239
+ _, _ = w.Write(rewriteFixtureBaseURL(offDaysFixture, base))
240
+ case "/v2/sports/cricket/leagues/19138/seasons":
241
+ _, _ = w.Write(rewriteFixtureBaseURL(seasonsFixture, base))
242
+ case "/v2/sports/cricket/leagues/19138/seasons/2025":
243
+ _, _ = w.Write(rewriteFixtureBaseURL(seasonFixture, base))
244
+ case "/v2/sports/cricket/leagues/1479935/seasons/2025":
245
+ _, _ = w.Write(rewriteFixtureBaseURL(seasonFixture, base))
246
+ case "/v2/sports/cricket/leagues/19138/seasons/2025/types":
247
+ _, _ = w.Write(rewriteFixtureBaseURL(seasonTypesFixture, base))
248
+ case "/v2/sports/cricket/leagues/1479935/seasons/2025/types":
249
+ _, _ = w.Write(rewriteFixtureBaseURL(seasonTypesFixture, base))
250
+ case "/v2/sports/cricket/leagues/19138/seasons/2025/types/1":
251
+ _, _ = w.Write(rewriteFixtureBaseURL(seasonTypeFixture, base))
252
+ case "/v2/sports/cricket/leagues/1479935/seasons/2025/types/1":
253
+ _, _ = w.Write(rewriteFixtureBaseURL(seasonTypeFixture, base))
254
+ case "/v2/sports/cricket/leagues/19138/seasons/2025/types/1/groups":
255
+ _, _ = w.Write(rewriteFixtureBaseURL(seasonGroupsFixture, base))
256
+ case "/v2/sports/cricket/leagues/1479935/seasons/2025/types/1/groups":
257
+ _, _ = w.Write(rewriteFixtureBaseURL(seasonGroupsFixture, base))
258
+ case "/v2/sports/cricket/leagues/1479935/seasons/2025/types/1/groups/1":
259
+ _, _ = w.Write(rewriteFixtureBaseURL(seasonGroupFixture, base))
260
+ case "/v2/sports/cricket/leagues/19138/standings":
261
+ _, _ = w.Write(rewriteFixtureBaseURL(standingsRootFixture, base))
262
+ case "/v2/sports/cricket/leagues/1529471/seasons/2026/types/1/groups/1/standings":
263
+ _, _ = w.Write(rewriteFixtureBaseURL(standingsListFixture, base))
264
+ case "/v2/sports/cricket/leagues/1529471/seasons/2026/types/1/groups/1/standings/1":
265
+ _, _ = w.Write(rewriteFixtureBaseURL(standingsItemFixture, base))
266
+ case "/v2/sports/cricket/teams/789643":
267
+ _, _ = w.Write(rewriteFixtureBaseURL(teamFixture, base))
268
+ case "/v2/sports/cricket/teams/789649":
269
+ _, _ = w.Write([]byte(`{"$ref":"` + base + `/teams/789649","id":"789649","displayName":"Speen Ghar Region","shortDisplayName":"SGR","abbreviation":"SGR"}`))
270
+ case "/v2/sports/cricket/teams/789651":
271
+ _, _ = w.Write([]byte(`{"$ref":"` + base + `/teams/789651","id":"789651","displayName":"Amo Region","shortDisplayName":"AMO","abbreviation":"AMO"}`))
272
+ default:
273
+ http.NotFound(w, r)
274
+ }
275
+ }))
276
+ t.Cleanup(server.Close)
277
+
278
+ index, err := OpenEntityIndex(filepath.Join(t.TempDir(), "resolver-index.json"))
279
+ if err != nil {
280
+ t.Fatalf("OpenEntityIndex error: %v", err)
281
+ }
282
+ if err := index.Upsert(IndexedEntity{
283
+ Kind: EntityLeague,
284
+ ID: "19138",
285
+ Ref: "/leagues/19138",
286
+ Name: "Mirwais Nika Provincial 3-Day",
287
+ ShortName: "19138",
288
+ Aliases: []string{"Mirwais Nika", "19138"},
289
+ UpdatedAt: time.Now().UTC(),
290
+ }); err != nil {
291
+ t.Fatalf("index upsert error: %v", err)
292
+ }
293
+ index.SetLastEventsSeedAt(time.Now().UTC())
294
+
295
+ client, err := NewClient(Config{BaseURL: server.URL + "/v2/sports/cricket"})
296
+ if err != nil {
297
+ t.Fatalf("NewClient error: %v", err)
298
+ }
299
+ resolver, err := NewResolver(ResolverConfig{
300
+ Client: client,
301
+ Index: index,
302
+ EventSeedTTL: 24 * time.Hour,
303
+ Now: func() time.Time { return time.Now().UTC() },
304
+ })
305
+ if err != nil {
306
+ t.Fatalf("NewResolver error: %v", err)
307
+ }
308
+ t.Cleanup(func() {
309
+ _ = resolver.Close()
310
+ })
311
+
312
+ service, err := NewLeagueService(LeagueServiceConfig{
313
+ Client: client,
314
+ Resolver: resolver,
315
+ })
316
+ if err != nil {
317
+ t.Fatalf("NewLeagueService error: %v", err)
318
+ }
319
+ t.Cleanup(func() {
320
+ _ = service.Close()
321
+ })
322
+
323
+ return service
324
+ }
@@ -0,0 +1,169 @@
1
+ package cricinfo
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "strings"
7
+ "testing"
8
+ "time"
9
+ )
10
+
11
+ func TestLiveLeagueSeasonStandingsRoutes(t *testing.T) {
12
+ t.Parallel()
13
+ requireLiveMatrix(t)
14
+
15
+ client, err := NewClient(Config{
16
+ Timeout: 12 * time.Second,
17
+ MaxRetries: 3,
18
+ })
19
+ if err != nil {
20
+ t.Fatalf("NewClient error: %v", err)
21
+ }
22
+
23
+ routes := []struct {
24
+ name string
25
+ ref string
26
+ keys []string
27
+ }{
28
+ {name: "leagues", ref: "/leagues", keys: []string{"items", "count"}},
29
+ {name: "league", ref: "/leagues/19138", keys: []string{"id", "name", "events", "seasons"}},
30
+ {name: "calendar", ref: "/leagues/19138/calendar", keys: []string{"items", "count"}},
31
+ {name: "calendar-ondays", ref: "/leagues/19138/calendar/ondays", keys: []string{"eventDate", "sections"}},
32
+ {name: "seasons", ref: "/leagues/19138/seasons", keys: []string{"items", "count"}},
33
+ {name: "season", ref: "/leagues/19138/seasons/2025", keys: []string{"year", "types"}},
34
+ {name: "season-types", ref: "/leagues/19138/seasons/2025/types", keys: []string{"items", "count"}},
35
+ {name: "season-type", ref: "/leagues/19138/seasons/2025/types/1", keys: []string{"id", "groups", "hasGroups"}},
36
+ {name: "season-groups", ref: "/leagues/19138/seasons/2025/types/1/groups", keys: []string{"items", "count"}},
37
+ {name: "standings", ref: "/leagues/19138/standings", keys: []string{"$ref", "items", "standings", "entries"}},
38
+ }
39
+
40
+ for _, tc := range routes {
41
+ tc := tc
42
+ t.Run(tc.name, func(t *testing.T) {
43
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
44
+ defer cancel()
45
+
46
+ resolved, err := client.ResolveRefChain(ctx, tc.ref)
47
+ if err != nil {
48
+ if isLive503(err) {
49
+ t.Skipf("skipping %s after transient 503: %v", tc.name, err)
50
+ }
51
+ t.Fatalf("ResolveRefChain(%q) error: %v", tc.ref, err)
52
+ }
53
+
54
+ var payload map[string]any
55
+ if err := json.Unmarshal(resolved.Body, &payload); err != nil {
56
+ t.Fatalf("unmarshal %s payload: %v", tc.name, err)
57
+ }
58
+ requireAnyKey(t, payload, tc.keys...)
59
+ })
60
+ }
61
+ }
62
+
63
+ func TestLiveLeagueServiceSeasonAndStandingsTraversal(t *testing.T) {
64
+ t.Parallel()
65
+ requireLiveMatrix(t)
66
+
67
+ service, err := NewLeagueService(LeagueServiceConfig{})
68
+ if err != nil {
69
+ t.Fatalf("NewLeagueService error: %v", err)
70
+ }
71
+ defer func() {
72
+ _ = service.Close()
73
+ }()
74
+
75
+ ctx, cancel := context.WithTimeout(context.Background(), 75*time.Second)
76
+ defer cancel()
77
+
78
+ listResult, err := service.List(ctx, LeagueListOptions{Limit: 5})
79
+ if err != nil {
80
+ t.Fatalf("LeagueService.List error: %v", err)
81
+ }
82
+ if listResult.Status == ResultStatusError {
83
+ if listResult.Error != nil && listResult.Error.StatusCode == 503 {
84
+ t.Skipf("skipping league list after persistent 503: %s", listResult.Message)
85
+ }
86
+ t.Fatalf("unexpected list error result: %+v", listResult)
87
+ }
88
+ if len(listResult.Items) == 0 {
89
+ if hasLive503Warning(listResult.Warnings) {
90
+ t.Skipf("skipping league list after transient 503 warnings: %v", listResult.Warnings)
91
+ }
92
+ t.Fatalf("expected list items from live /leagues traversal")
93
+ }
94
+
95
+ showResult, err := service.Show(ctx, "19138")
96
+ if err != nil {
97
+ t.Fatalf("LeagueService.Show error: %v", err)
98
+ }
99
+ if showResult.Status == ResultStatusError {
100
+ if showResult.Error != nil && showResult.Error.StatusCode == 503 {
101
+ t.Skipf("skipping league show after persistent 503: %s", showResult.Message)
102
+ }
103
+ t.Fatalf("unexpected show error result: %+v", showResult)
104
+ }
105
+
106
+ calendarResult, err := service.Calendar(ctx, "19138")
107
+ if err != nil {
108
+ t.Fatalf("LeagueService.Calendar error: %v", err)
109
+ }
110
+ if calendarResult.Status == ResultStatusError {
111
+ if calendarResult.Error != nil && calendarResult.Error.StatusCode == 503 {
112
+ t.Skipf("skipping calendar traversal after persistent 503: %s", calendarResult.Message)
113
+ }
114
+ t.Fatalf("unexpected calendar error result: %+v", calendarResult)
115
+ }
116
+ if calendarResult.Status == ResultStatusEmpty && !strings.Contains(strings.ToLower(strings.Join(calendarResult.Warnings, " ")), "503") {
117
+ t.Fatalf("expected calendar traversal to return day entries")
118
+ }
119
+
120
+ seasonsResult, err := service.Seasons(ctx, "19138")
121
+ if err != nil {
122
+ t.Fatalf("LeagueService.Seasons error: %v", err)
123
+ }
124
+ if seasonsResult.Status == ResultStatusError {
125
+ if seasonsResult.Error != nil && seasonsResult.Error.StatusCode == 503 {
126
+ t.Skipf("skipping seasons traversal after persistent 503: %s", seasonsResult.Message)
127
+ }
128
+ t.Fatalf("unexpected seasons error result: %+v", seasonsResult)
129
+ }
130
+ if len(seasonsResult.Items) == 0 && hasLive503Warning(seasonsResult.Warnings) {
131
+ t.Skipf("skipping seasons traversal after transient 503 warnings: %v", seasonsResult.Warnings)
132
+ }
133
+
134
+ typesResult, err := service.SeasonTypes(ctx, "19138", SeasonLookupOptions{SeasonQuery: "2025"})
135
+ if err != nil {
136
+ t.Fatalf("LeagueService.SeasonTypes error: %v", err)
137
+ }
138
+ if typesResult.Status == ResultStatusError {
139
+ if typesResult.Error != nil && typesResult.Error.StatusCode == 503 {
140
+ t.Skipf("skipping season types traversal after persistent 503: %s", typesResult.Message)
141
+ }
142
+ t.Fatalf("unexpected season types error result: %+v", typesResult)
143
+ }
144
+
145
+ groupsResult, err := service.SeasonGroups(ctx, "19138", SeasonLookupOptions{SeasonQuery: "2025", TypeQuery: "1"})
146
+ if err != nil {
147
+ t.Fatalf("LeagueService.SeasonGroups error: %v", err)
148
+ }
149
+ if groupsResult.Status == ResultStatusError {
150
+ if groupsResult.Error != nil && groupsResult.Error.StatusCode == 503 {
151
+ t.Skipf("skipping season groups traversal after persistent 503: %s", groupsResult.Message)
152
+ }
153
+ t.Fatalf("unexpected season groups error result: %+v", groupsResult)
154
+ }
155
+
156
+ standingsResult, err := service.Standings(ctx, "19138")
157
+ if err != nil {
158
+ t.Fatalf("LeagueService.Standings error: %v", err)
159
+ }
160
+ if standingsResult.Status == ResultStatusError {
161
+ if standingsResult.Error != nil && standingsResult.Error.StatusCode == 503 {
162
+ t.Skipf("skipping standings traversal after persistent 503: %s", standingsResult.Message)
163
+ }
164
+ t.Fatalf("unexpected standings error result: %+v", standingsResult)
165
+ }
166
+ if len(standingsResult.Items) == 0 && hasLive503Warning(standingsResult.Warnings) {
167
+ t.Skipf("skipping standings traversal after transient 503 warnings: %v", standingsResult.Warnings)
168
+ }
169
+ }