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,264 @@
1
+ package cricinfo
2
+
3
+ import (
4
+ "encoding/json"
5
+ "os"
6
+ "path/filepath"
7
+ "testing"
8
+ )
9
+
10
+ func TestCuratedFixturesExistAndAreValidJSON(t *testing.T) {
11
+ t.Parallel()
12
+
13
+ for _, spec := range FixtureMatrix() {
14
+ path := filepath.Join("testdata", "fixtures", spec.FixturePath)
15
+ data, err := os.ReadFile(path)
16
+ if err != nil {
17
+ t.Fatalf("read fixture %q: %v", path, err)
18
+ }
19
+ if len(data) == 0 {
20
+ t.Fatalf("fixture %q is empty", path)
21
+ }
22
+
23
+ var payload any
24
+ if err := json.Unmarshal(data, &payload); err != nil {
25
+ t.Fatalf("fixture %q invalid JSON: %v", path, err)
26
+ }
27
+ }
28
+ }
29
+
30
+ func TestCuratedFixturesCoverMajorFamilies(t *testing.T) {
31
+ t.Parallel()
32
+
33
+ assertFixtureFamilyKeys(t, FixtureFamilyRootDiscovery, "root")
34
+ assertFixtureFamilyKeys(t, FixtureFamilyMatchesCompetition, "competition")
35
+ assertFixtureFamilyKeys(t, FixtureFamilyDetailsPlays, "plays-page")
36
+ assertFixtureFamilyKeys(t, FixtureFamilyTeamCompetitor, "competitor")
37
+ assertFixtureFamilyKeys(t, FixtureFamilyInningsDepth, "innings-period")
38
+ assertFixtureFamilyKeys(t, FixtureFamilyPlayers, "athlete-profile")
39
+ assertFixtureFamilyKeys(t, FixtureFamilyLeagueSeason, "standings")
40
+ assertFixtureFamilyKeys(t, FixtureFamilyAuxCompetitionMeta, "officials")
41
+ }
42
+
43
+ func TestCuratedFixturesDecodeContracts(t *testing.T) {
44
+ t.Parallel()
45
+
46
+ eventsBody := mustReadFixtureByName(t, FixtureFamilyRootDiscovery, "events-page")
47
+ page, err := DecodePage[Ref](eventsBody)
48
+ if err != nil {
49
+ t.Fatalf("DecodePage events fixture error: %v", err)
50
+ }
51
+ if page.PageSize == 0 {
52
+ t.Fatalf("expected non-zero page size in events fixture")
53
+ }
54
+
55
+ playsBody := mustReadFixtureByName(t, FixtureFamilyDetailsPlays, "plays-page")
56
+ plays, err := DecodePage[Ref](playsBody)
57
+ if err != nil {
58
+ t.Fatalf("DecodePage plays fixture error: %v", err)
59
+ }
60
+ if len(plays.Items) == 0 {
61
+ t.Fatalf("expected at least one play item ref")
62
+ }
63
+
64
+ statsBody := mustReadFixtureByName(t, FixtureFamilyPlayers, "athlete-statistics")
65
+ stats, err := DecodeStatsObject(statsBody)
66
+ if err != nil {
67
+ t.Fatalf("DecodeStatsObject player statistics fixture error: %v", err)
68
+ }
69
+ if len(stats.Splits) == 0 {
70
+ t.Fatalf("expected non-empty splits in player statistics fixture")
71
+ }
72
+
73
+ rosterBody := mustReadFixtureByName(t, FixtureFamilyTeamCompetitor, "competitor-roster")
74
+ rosterEntries, err := DecodeObjectCollection[map[string]any](rosterBody, "entries")
75
+ if err != nil {
76
+ t.Fatalf("DecodeObjectCollection roster fixture error: %v", err)
77
+ }
78
+ if len(rosterEntries) == 0 {
79
+ t.Fatalf("expected roster entries in roster fixture")
80
+ }
81
+
82
+ rosterObjectBody, err := os.ReadFile(filepath.Join("testdata", "fixtures", "team-competitor", "roster-1147772-object.json"))
83
+ if err != nil {
84
+ t.Fatalf("read object-shaped roster fixture error: %v", err)
85
+ }
86
+ rosterObjectEntries, err := DecodeObjectCollection[map[string]any](rosterObjectBody, "entries")
87
+ if err != nil {
88
+ t.Fatalf("DecodeObjectCollection object-shaped roster fixture error: %v", err)
89
+ }
90
+ if len(rosterObjectEntries) == 0 {
91
+ t.Fatalf("expected roster entries in object-shaped roster fixture")
92
+ }
93
+
94
+ leadersBody, err := os.ReadFile(filepath.Join("testdata", "fixtures", "team-competitor", "leaders-789643.json"))
95
+ if err != nil {
96
+ t.Fatalf("read leaders fixture error: %v", err)
97
+ }
98
+ leaders, err := NormalizeTeamLeaders(leadersBody, Team{ID: "789643"}, TeamScopeMatch, "1529474")
99
+ if err != nil {
100
+ t.Fatalf("NormalizeTeamLeaders fixture error: %v", err)
101
+ }
102
+ if len(leaders.Categories) == 0 {
103
+ t.Fatalf("expected category-based leaders in leaders fixture")
104
+ }
105
+ if len(leaders.Categories[0].Leaders) == 0 {
106
+ t.Fatalf("expected at least one leader entry in leaders fixture")
107
+ }
108
+ }
109
+
110
+ func TestPhase9FixtureNormalizationForWicketSplitsAndPartnershipPayloads(t *testing.T) {
111
+ t.Parallel()
112
+
113
+ statsBody, err := os.ReadFile(filepath.Join("testdata", "fixtures", "team-competitor", "statistics-789643.json"))
114
+ if err != nil {
115
+ t.Fatalf("read statistics fixture error: %v", err)
116
+ }
117
+ overs, wickets, err := NormalizeInningsPeriodStatistics(statsBody)
118
+ if err != nil {
119
+ t.Fatalf("NormalizeInningsPeriodStatistics fixture error: %v", err)
120
+ }
121
+ if len(overs) == 0 {
122
+ t.Fatalf("expected over timeline entries from period statistics fixture")
123
+ }
124
+ if len(wickets) == 0 {
125
+ t.Fatalf("expected wicket timeline entries from period statistics fixture")
126
+ }
127
+ if wickets[0].DetailRef == "" {
128
+ t.Fatalf("expected wicket timeline detail ref from statistics fixture")
129
+ }
130
+
131
+ partnershipBody, err := os.ReadFile(filepath.Join("testdata", "fixtures", "innings-fow-partnerships", "partnership-1.json"))
132
+ if err != nil {
133
+ t.Fatalf("read partnership fixture error: %v", err)
134
+ }
135
+ partnership, err := NormalizePartnership(partnershipBody)
136
+ if err != nil {
137
+ t.Fatalf("NormalizePartnership fixture error: %v", err)
138
+ }
139
+ if partnership.WicketNumber == 0 || partnership.Runs == 0 {
140
+ t.Fatalf("expected detailed partnership fields, got %+v", partnership)
141
+ }
142
+ if len(partnership.Batsmen) == 0 {
143
+ t.Fatalf("expected partnership batsmen payload")
144
+ }
145
+
146
+ fowBody, err := os.ReadFile(filepath.Join("testdata", "fixtures", "innings-fow-partnerships", "fow-1.json"))
147
+ if err != nil {
148
+ t.Fatalf("read fow fixture error: %v", err)
149
+ }
150
+ fow, err := NormalizeFallOfWicket(fowBody)
151
+ if err != nil {
152
+ t.Fatalf("NormalizeFallOfWicket fixture error: %v", err)
153
+ }
154
+ if fow.WicketNumber == 0 || fow.WicketOver == 0 {
155
+ t.Fatalf("expected detailed fow fields, got %+v", fow)
156
+ }
157
+ if fow.AthleteRef == "" {
158
+ t.Fatalf("expected fow athlete ref in detailed payload")
159
+ }
160
+ }
161
+
162
+ func TestPhase10FixtureNormalizationForPlayerProfileAndStats(t *testing.T) {
163
+ t.Parallel()
164
+
165
+ profileBody, err := os.ReadFile(filepath.Join("testdata", "fixtures", "players", "athlete-1361257.json"))
166
+ if err != nil {
167
+ t.Fatalf("read player profile fixture error: %v", err)
168
+ }
169
+ player, err := NormalizePlayer(profileBody)
170
+ if err != nil {
171
+ t.Fatalf("NormalizePlayer fixture error: %v", err)
172
+ }
173
+ if player.Team == nil || len(player.Styles) == 0 {
174
+ t.Fatalf("expected normalized team and styles in player profile, got %+v", player)
175
+ }
176
+ if len(player.MajorTeams) == 0 {
177
+ t.Fatalf("expected normalized major teams in player profile")
178
+ }
179
+
180
+ statsBody, err := os.ReadFile(filepath.Join("testdata", "fixtures", "players", "athlete-1361257-statistics.json"))
181
+ if err != nil {
182
+ t.Fatalf("read player statistics fixture error: %v", err)
183
+ }
184
+ playerStats, err := NormalizePlayerStatistics(statsBody)
185
+ if err != nil {
186
+ t.Fatalf("NormalizePlayerStatistics fixture error: %v", err)
187
+ }
188
+ if len(playerStats.Categories) == 0 {
189
+ t.Fatalf("expected grouped categories in player statistics fixture")
190
+ }
191
+ if len(playerStats.Categories[0].Stats) == 0 {
192
+ t.Fatalf("expected grouped stats in player statistics fixture")
193
+ }
194
+ }
195
+
196
+ func assertFixtureFamilyKeys(t *testing.T, family FixtureFamily, name string) {
197
+ t.Helper()
198
+
199
+ body := mustReadFixtureByName(t, family, name)
200
+ var payload map[string]any
201
+ if err := json.Unmarshal(body, &payload); err != nil {
202
+ t.Fatalf("unmarshal fixture %q/%q: %v", family, name, err)
203
+ }
204
+ if len(payload) == 0 {
205
+ t.Fatalf("fixture %q/%q is empty object", family, name)
206
+ }
207
+
208
+ switch family {
209
+ case FixtureFamilyRootDiscovery:
210
+ requireAnyKey(t, payload, "events", "leagues", "items")
211
+ case FixtureFamilyMatchesCompetition:
212
+ requireAnyKey(t, payload, "competitors", "status", "date")
213
+ case FixtureFamilyDetailsPlays:
214
+ requireAnyKey(t, payload, "items", "count", "pageSize")
215
+ case FixtureFamilyTeamCompetitor:
216
+ requireAnyKey(t, payload, "id", "team", "linescores")
217
+ case FixtureFamilyInningsDepth:
218
+ requireAnyKey(t, payload, "period", "runs", "wickets")
219
+ case FixtureFamilyPlayers:
220
+ requireAnyKey(t, payload, "id", "displayName", "fullName")
221
+ case FixtureFamilyLeagueSeason:
222
+ requireAnyKey(t, payload, "children", "entries", "items", "seasons")
223
+ case FixtureFamilyAuxCompetitionMeta:
224
+ requireAnyKey(t, payload, "items", "entries", "count", "officials")
225
+ default:
226
+ t.Fatalf("unexpected family %q", family)
227
+ }
228
+ }
229
+
230
+ func requireAnyKey(t *testing.T, payload map[string]any, keys ...string) {
231
+ t.Helper()
232
+ for _, key := range keys {
233
+ if _, ok := payload[key]; ok {
234
+ return
235
+ }
236
+ }
237
+ t.Fatalf("expected any key in %v, got keys: %v", keys, mapKeys(payload))
238
+ }
239
+
240
+ func mustReadFixtureByName(t *testing.T, family FixtureFamily, name string) []byte {
241
+ t.Helper()
242
+
243
+ for _, spec := range FixtureMatrix() {
244
+ if spec.Family == family && spec.Name == name {
245
+ path := filepath.Join("testdata", "fixtures", spec.FixturePath)
246
+ data, err := os.ReadFile(path)
247
+ if err != nil {
248
+ t.Fatalf("read fixture %q: %v", path, err)
249
+ }
250
+ return data
251
+ }
252
+ }
253
+
254
+ t.Fatalf("fixture not found in matrix: family=%q name=%q", family, name)
255
+ return nil
256
+ }
257
+
258
+ func mapKeys(payload map[string]any) []string {
259
+ keys := make([]string, 0, len(payload))
260
+ for key := range payload {
261
+ keys = append(keys, key)
262
+ }
263
+ return keys
264
+ }