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,175 @@
1
+ package cricinfo
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+ "time"
7
+ )
8
+
9
+ func TestEntityIndexSearchExactAndFuzzy(t *testing.T) {
10
+ t.Parallel()
11
+
12
+ idx, err := OpenEntityIndex(filepath.Join(t.TempDir(), "index.json"))
13
+ if err != nil {
14
+ t.Fatalf("OpenEntityIndex error: %v", err)
15
+ }
16
+
17
+ now := time.Now().UTC()
18
+ if err := idx.Upsert(IndexedEntity{
19
+ Kind: EntityPlayer,
20
+ ID: "1361257",
21
+ Ref: "http://core.espnuk.org/v2/sports/cricket/athletes/1361257",
22
+ Name: "Fazal Haq Shaheen",
23
+ ShortName: "Fazal",
24
+ Aliases: []string{"Fazal Haq", "Shaheen"},
25
+ UpdatedAt: now,
26
+ }); err != nil {
27
+ t.Fatalf("Upsert player 1 error: %v", err)
28
+ }
29
+
30
+ if err := idx.Upsert(IndexedEntity{
31
+ Kind: EntityPlayer,
32
+ ID: "999",
33
+ Name: "John Doe",
34
+ Aliases: []string{"Johnny"},
35
+ UpdatedAt: now.Add(-time.Minute),
36
+ }); err != nil {
37
+ t.Fatalf("Upsert player 2 error: %v", err)
38
+ }
39
+
40
+ exact := idx.Search(EntityPlayer, "1361257", 5, SearchContext{})
41
+ if len(exact) == 0 || exact[0].ID != "1361257" {
42
+ t.Fatalf("expected exact id match for 1361257, got %+v", exact)
43
+ }
44
+
45
+ fuzzy := idx.Search(EntityPlayer, "faz sha", 5, SearchContext{})
46
+ if len(fuzzy) == 0 || fuzzy[0].ID != "1361257" {
47
+ t.Fatalf("expected fuzzy alias match for 'faz sha', got %+v", fuzzy)
48
+ }
49
+ }
50
+
51
+ func TestEntityIndexSearchContextBoost(t *testing.T) {
52
+ t.Parallel()
53
+
54
+ idx, err := OpenEntityIndex(filepath.Join(t.TempDir(), "index.json"))
55
+ if err != nil {
56
+ t.Fatalf("OpenEntityIndex error: %v", err)
57
+ }
58
+
59
+ if err := idx.Upsert(IndexedEntity{
60
+ Kind: EntityMatch,
61
+ ID: "1529474",
62
+ Name: "3rd Match",
63
+ LeagueID: "19138",
64
+ MatchID: "1529474",
65
+ UpdatedAt: time.Now().UTC(),
66
+ }); err != nil {
67
+ t.Fatalf("Upsert preferred match error: %v", err)
68
+ }
69
+
70
+ if err := idx.Upsert(IndexedEntity{
71
+ Kind: EntityMatch,
72
+ ID: "1529999",
73
+ Name: "3rd Match",
74
+ LeagueID: "11132",
75
+ MatchID: "1529999",
76
+ UpdatedAt: time.Now().UTC(),
77
+ }); err != nil {
78
+ t.Fatalf("Upsert non-preferred match error: %v", err)
79
+ }
80
+
81
+ results := idx.Search(EntityMatch, "3rd match", 5, SearchContext{PreferredLeagueID: "19138"})
82
+ if len(results) == 0 {
83
+ t.Fatalf("expected search results")
84
+ }
85
+ if results[0].ID != "1529474" {
86
+ t.Fatalf("expected context-preferred match first, got %+v", results)
87
+ }
88
+ }
89
+
90
+ func TestEntityIndexSearchPrefersExactMatchContext(t *testing.T) {
91
+ t.Parallel()
92
+
93
+ idx, err := OpenEntityIndex(filepath.Join(t.TempDir(), "index.json"))
94
+ if err != nil {
95
+ t.Fatalf("OpenEntityIndex error: %v", err)
96
+ }
97
+
98
+ now := time.Now().UTC()
99
+ entities := []IndexedEntity{
100
+ {
101
+ Kind: EntityPlayer,
102
+ ID: "253802",
103
+ Name: "Virat Kohli",
104
+ LeagueID: "11132",
105
+ MatchID: "1527689",
106
+ UpdatedAt: now,
107
+ },
108
+ {
109
+ Kind: EntityPlayer,
110
+ ID: "999999",
111
+ Name: "Virat Kohli",
112
+ LeagueID: "19138",
113
+ MatchID: "1529474",
114
+ UpdatedAt: now.Add(time.Minute),
115
+ },
116
+ }
117
+ if err := idx.UpsertMany(entities); err != nil {
118
+ t.Fatalf("UpsertMany error: %v", err)
119
+ }
120
+
121
+ results := idx.Search(EntityPlayer, "virat kohli", 5, SearchContext{
122
+ PreferredLeagueID: "11132",
123
+ PreferredMatchID: "1527689",
124
+ })
125
+ if len(results) == 0 {
126
+ t.Fatalf("expected contextual player search results")
127
+ }
128
+ if results[0].ID != "253802" {
129
+ t.Fatalf("expected preferred match-context player first, got %+v", results)
130
+ }
131
+ for _, result := range results {
132
+ if result.MatchID != "1527689" {
133
+ t.Fatalf("expected results to be narrowed to preferred match context, got %+v", results)
134
+ }
135
+ }
136
+ }
137
+
138
+ func TestEntityIndexSearchDoesNotMatchInitialForFullNameQuery(t *testing.T) {
139
+ t.Parallel()
140
+
141
+ idx, err := OpenEntityIndex(filepath.Join(t.TempDir(), "index.json"))
142
+ if err != nil {
143
+ t.Fatalf("OpenEntityIndex error: %v", err)
144
+ }
145
+
146
+ now := time.Now().UTC()
147
+ if err := idx.UpsertMany([]IndexedEntity{
148
+ {
149
+ Kind: EntityPlayer,
150
+ ID: "253802",
151
+ Name: "Virat Kohli",
152
+ ShortName: "Kohli",
153
+ Aliases: []string{"Virat Kohli", "V Kohli"},
154
+ UpdatedAt: now,
155
+ },
156
+ {
157
+ Kind: EntityPlayer,
158
+ ID: "1408688",
159
+ Name: "Vaibhav Sooryavanshi",
160
+ ShortName: "Sooryavanshi",
161
+ Aliases: []string{"Vaibhav Sooryavanshi", "V Sooryavanshi"},
162
+ UpdatedAt: now.Add(time.Minute),
163
+ },
164
+ }); err != nil {
165
+ t.Fatalf("UpsertMany error: %v", err)
166
+ }
167
+
168
+ results := idx.Search(EntityPlayer, "Virat Kohli", 5, SearchContext{})
169
+ if len(results) == 0 {
170
+ t.Fatalf("expected player search results")
171
+ }
172
+ if results[0].ID != "253802" {
173
+ t.Fatalf("expected exact full-name match first, got %+v", results)
174
+ }
175
+ }
@@ -0,0 +1,243 @@
1
+ package cricinfo
2
+
3
+ import (
4
+ "fmt"
5
+ "sort"
6
+ "strings"
7
+ )
8
+
9
+ // FixtureFamily groups fixtures and live probes by API resource family.
10
+ type FixtureFamily string
11
+
12
+ const (
13
+ FixtureFamilyRootDiscovery FixtureFamily = "root-discovery"
14
+ FixtureFamilyMatchesCompetition FixtureFamily = "matches-competitions"
15
+ FixtureFamilyDetailsPlays FixtureFamily = "details-plays"
16
+ FixtureFamilyTeamCompetitor FixtureFamily = "team-competitor"
17
+ FixtureFamilyInningsDepth FixtureFamily = "innings-fow-partnerships"
18
+ FixtureFamilyPlayers FixtureFamily = "players"
19
+ FixtureFamilyLeagueSeason FixtureFamily = "leagues-seasons-standings"
20
+ FixtureFamilyAuxCompetitionMeta FixtureFamily = "aux-competition-metadata"
21
+ )
22
+
23
+ // FixtureSpec defines a curated fixture/live endpoint pairing.
24
+ type FixtureSpec struct {
25
+ Family FixtureFamily
26
+ Name string
27
+ Ref string
28
+ FixturePath string
29
+ LiveProbe bool
30
+ }
31
+
32
+ var fixtureSpecs = []FixtureSpec{
33
+ {
34
+ Family: FixtureFamilyRootDiscovery,
35
+ Name: "root",
36
+ Ref: "/v2/sports/cricket",
37
+ FixturePath: "root-discovery/root.json",
38
+ LiveProbe: true,
39
+ },
40
+ {
41
+ Family: FixtureFamilyRootDiscovery,
42
+ Name: "events-page",
43
+ Ref: "/events",
44
+ FixturePath: "root-discovery/events.json",
45
+ LiveProbe: false,
46
+ },
47
+ {
48
+ Family: FixtureFamilyMatchesCompetition,
49
+ Name: "competition",
50
+ Ref: "/leagues/19138/events/1529474/competitions/1529474",
51
+ FixturePath: "matches-competitions/competition.json",
52
+ LiveProbe: true,
53
+ },
54
+ {
55
+ Family: FixtureFamilyMatchesCompetition,
56
+ Name: "matchcards",
57
+ Ref: "/leagues/11132/events/1527966/competitions/1527966/matchcards",
58
+ FixturePath: "matches-competitions/matchcards-1527966.json",
59
+ LiveProbe: false,
60
+ },
61
+ {
62
+ Family: FixtureFamilyMatchesCompetition,
63
+ Name: "situation",
64
+ Ref: "/leagues/19138/events/1529474/competitions/1529474/situation",
65
+ FixturePath: "matches-competitions/situation-1529474.json",
66
+ LiveProbe: false,
67
+ },
68
+ {
69
+ Family: FixtureFamilyDetailsPlays,
70
+ Name: "plays-page",
71
+ Ref: "/leagues/19138/events/1529474/competitions/1529474/plays?limit=1",
72
+ FixturePath: "details-plays/plays.json",
73
+ LiveProbe: true,
74
+ },
75
+ {
76
+ Family: FixtureFamilyDetailsPlays,
77
+ Name: "detail-item",
78
+ Ref: "/leagues/19138/events/1529474/competitions/1529474/details/110",
79
+ FixturePath: "details-plays/detail-110.json",
80
+ LiveProbe: false,
81
+ },
82
+ {
83
+ Family: FixtureFamilyTeamCompetitor,
84
+ Name: "competitor",
85
+ Ref: "/leagues/19138/events/1529474/competitions/1529474/competitors/789643",
86
+ FixturePath: "team-competitor/competitor-789643.json",
87
+ LiveProbe: true,
88
+ },
89
+ {
90
+ Family: FixtureFamilyTeamCompetitor,
91
+ Name: "competitor-roster",
92
+ Ref: "/leagues/11132/events/1475396/competitions/1475396/competitors/1147772/roster",
93
+ FixturePath: "team-competitor/roster-1147772.json",
94
+ LiveProbe: false,
95
+ },
96
+ {
97
+ Family: FixtureFamilyInningsDepth,
98
+ Name: "innings-period",
99
+ Ref: "/leagues/1098952/events/1475396/competitions/1475396/competitors/1147772/linescores/1/2",
100
+ FixturePath: "innings-fow-partnerships/innings-1-2.json",
101
+ LiveProbe: true,
102
+ },
103
+ {
104
+ Family: FixtureFamilyInningsDepth,
105
+ Name: "fow",
106
+ Ref: "/leagues/1098952/events/1475396/competitions/1475396/competitors/1147772/linescores/1/2/fow",
107
+ FixturePath: "innings-fow-partnerships/fow.json",
108
+ LiveProbe: false,
109
+ },
110
+ {
111
+ Family: FixtureFamilyInningsDepth,
112
+ Name: "partnerships",
113
+ Ref: "/leagues/1098952/events/1475396/competitions/1475396/competitors/1147772/linescores/1/2/partnerships",
114
+ FixturePath: "innings-fow-partnerships/partnerships.json",
115
+ LiveProbe: false,
116
+ },
117
+ {
118
+ Family: FixtureFamilyPlayers,
119
+ Name: "athlete-profile",
120
+ Ref: "/athletes/1361257",
121
+ FixturePath: "players/athlete-1361257.json",
122
+ LiveProbe: true,
123
+ },
124
+ {
125
+ Family: FixtureFamilyPlayers,
126
+ Name: "athlete-statistics",
127
+ Ref: "/athletes/1361257/statistics",
128
+ FixturePath: "players/athlete-1361257-statistics.json",
129
+ LiveProbe: false,
130
+ },
131
+ {
132
+ Family: FixtureFamilyLeagueSeason,
133
+ Name: "standings",
134
+ Ref: "/leagues/19138/standings",
135
+ FixturePath: "leagues-seasons-standings/standings.json",
136
+ LiveProbe: true,
137
+ },
138
+ {
139
+ Family: FixtureFamilyLeagueSeason,
140
+ Name: "seasons",
141
+ Ref: "/leagues/19138/seasons",
142
+ FixturePath: "leagues-seasons-standings/seasons.json",
143
+ LiveProbe: false,
144
+ },
145
+ {
146
+ Family: FixtureFamilyAuxCompetitionMeta,
147
+ Name: "officials",
148
+ Ref: "/leagues/11132/events/1527944/competitions/1527944/officials",
149
+ FixturePath: "aux-competition-metadata/officials.json",
150
+ LiveProbe: true,
151
+ },
152
+ {
153
+ Family: FixtureFamilyAuxCompetitionMeta,
154
+ Name: "broadcasts",
155
+ Ref: "/leagues/11132/events/1527944/competitions/1527944/broadcasts",
156
+ FixturePath: "aux-competition-metadata/broadcasts.json",
157
+ LiveProbe: false,
158
+ },
159
+ }
160
+
161
+ // FixtureMatrix returns a copy of the curated fixture endpoint matrix.
162
+ func FixtureMatrix() []FixtureSpec {
163
+ out := make([]FixtureSpec, len(fixtureSpecs))
164
+ copy(out, fixtureSpecs)
165
+ return out
166
+ }
167
+
168
+ // FixtureFamilies returns sorted family names represented in the matrix.
169
+ func FixtureFamilies() []FixtureFamily {
170
+ seen := map[FixtureFamily]struct{}{}
171
+ for _, spec := range fixtureSpecs {
172
+ seen[spec.Family] = struct{}{}
173
+ }
174
+
175
+ families := make([]FixtureFamily, 0, len(seen))
176
+ for family := range seen {
177
+ families = append(families, family)
178
+ }
179
+ sort.Slice(families, func(i, j int) bool { return families[i] < families[j] })
180
+
181
+ return families
182
+ }
183
+
184
+ // ParseFixtureFamilies parses a comma-separated family filter.
185
+ func ParseFixtureFamilies(raw string) (map[FixtureFamily]struct{}, error) {
186
+ raw = strings.TrimSpace(raw)
187
+ if raw == "" {
188
+ return nil, nil
189
+ }
190
+
191
+ allowed := map[FixtureFamily]struct{}{}
192
+ for _, family := range FixtureFamilies() {
193
+ allowed[family] = struct{}{}
194
+ }
195
+
196
+ selected := map[FixtureFamily]struct{}{}
197
+ parts := strings.Split(raw, ",")
198
+ for _, part := range parts {
199
+ family := FixtureFamily(strings.TrimSpace(part))
200
+ if family == "" {
201
+ continue
202
+ }
203
+ if _, ok := allowed[family]; !ok {
204
+ return nil, fmt.Errorf("unknown fixture family %q", family)
205
+ }
206
+ selected[family] = struct{}{}
207
+ }
208
+
209
+ if len(selected) == 0 {
210
+ return nil, nil
211
+ }
212
+
213
+ return selected, nil
214
+ }
215
+
216
+ // FilterFixtureMatrixByFamily filters the matrix by selected families.
217
+ func FilterFixtureMatrixByFamily(matrix []FixtureSpec, selected map[FixtureFamily]struct{}) []FixtureSpec {
218
+ if len(selected) == 0 {
219
+ out := make([]FixtureSpec, len(matrix))
220
+ copy(out, matrix)
221
+ return out
222
+ }
223
+
224
+ out := make([]FixtureSpec, 0, len(matrix))
225
+ for _, spec := range matrix {
226
+ if _, ok := selected[spec.Family]; ok {
227
+ out = append(out, spec)
228
+ }
229
+ }
230
+
231
+ return out
232
+ }
233
+
234
+ // LiveProbeMatrix returns one or more live probe entries from the matrix.
235
+ func LiveProbeMatrix(matrix []FixtureSpec) []FixtureSpec {
236
+ out := make([]FixtureSpec, 0, len(matrix))
237
+ for _, spec := range matrix {
238
+ if spec.LiveProbe {
239
+ out = append(out, spec)
240
+ }
241
+ }
242
+ return out
243
+ }
@@ -0,0 +1,49 @@
1
+ package cricinfo
2
+
3
+ import "testing"
4
+
5
+ func TestFixtureMatrixCoversAllMajorFamilies(t *testing.T) {
6
+ t.Parallel()
7
+
8
+ required := []FixtureFamily{
9
+ FixtureFamilyRootDiscovery,
10
+ FixtureFamilyMatchesCompetition,
11
+ FixtureFamilyDetailsPlays,
12
+ FixtureFamilyTeamCompetitor,
13
+ FixtureFamilyInningsDepth,
14
+ FixtureFamilyPlayers,
15
+ FixtureFamilyLeagueSeason,
16
+ FixtureFamilyAuxCompetitionMeta,
17
+ }
18
+
19
+ matrix := FixtureMatrix()
20
+ found := map[FixtureFamily]int{}
21
+ for _, spec := range matrix {
22
+ found[spec.Family]++
23
+ if spec.Name == "" || spec.Ref == "" || spec.FixturePath == "" {
24
+ t.Fatalf("invalid fixture spec %+v", spec)
25
+ }
26
+ }
27
+
28
+ for _, family := range required {
29
+ if found[family] == 0 {
30
+ t.Fatalf("missing fixture coverage for family %q", family)
31
+ }
32
+ }
33
+ }
34
+
35
+ func TestParseFixtureFamilies(t *testing.T) {
36
+ t.Parallel()
37
+
38
+ selected, err := ParseFixtureFamilies("players,details-plays")
39
+ if err != nil {
40
+ t.Fatalf("ParseFixtureFamilies error: %v", err)
41
+ }
42
+ if len(selected) != 2 {
43
+ t.Fatalf("expected 2 selected families, got %d", len(selected))
44
+ }
45
+
46
+ if _, err := ParseFixtureFamilies("players,unknown"); err == nil {
47
+ t.Fatalf("expected parse error for unknown family")
48
+ }
49
+ }