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.
- package/AGENTS.md +63 -0
- package/CONTRIBUTORS.md +75 -0
- package/LICENSE +21 -0
- package/Makefile +131 -0
- package/README.md +130 -0
- package/bin/cricinfo.js +44 -0
- package/cmd/cricinfo/main.go +15 -0
- package/go.mod +10 -0
- package/go.sum +10 -0
- package/internal/app/app.go +11 -0
- package/internal/app/app_test.go +122 -0
- package/internal/buildinfo/buildinfo.go +16 -0
- package/internal/cli/analysis.go +262 -0
- package/internal/cli/analysis_test.go +175 -0
- package/internal/cli/competitions.go +154 -0
- package/internal/cli/competitions_test.go +165 -0
- package/internal/cli/leagues.go +297 -0
- package/internal/cli/leagues_test.go +194 -0
- package/internal/cli/matches.go +403 -0
- package/internal/cli/matches_test.go +413 -0
- package/internal/cli/players.go +263 -0
- package/internal/cli/players_test.go +384 -0
- package/internal/cli/root.go +141 -0
- package/internal/cli/search.go +119 -0
- package/internal/cli/teams.go +214 -0
- package/internal/cli/teams_test.go +192 -0
- package/internal/cricinfo/analysis.go +1401 -0
- package/internal/cricinfo/analysis_phase15_test.go +267 -0
- package/internal/cricinfo/client.go +471 -0
- package/internal/cricinfo/client_test.go +280 -0
- package/internal/cricinfo/cmd/fixture-refresh/main.go +145 -0
- package/internal/cricinfo/competitions.go +405 -0
- package/internal/cricinfo/competitions_phase13_test.go +234 -0
- package/internal/cricinfo/coverage_ledger.go +122 -0
- package/internal/cricinfo/coverage_ledger_test.go +253 -0
- package/internal/cricinfo/decode.go +115 -0
- package/internal/cricinfo/decode_test.go +100 -0
- package/internal/cricinfo/entity_index.go +618 -0
- package/internal/cricinfo/entity_index_test.go +175 -0
- package/internal/cricinfo/fixture_matrix.go +243 -0
- package/internal/cricinfo/fixture_matrix_test.go +49 -0
- package/internal/cricinfo/fixtures_test.go +264 -0
- package/internal/cricinfo/historical_hydration.go +1641 -0
- package/internal/cricinfo/historical_phase14_test.go +542 -0
- package/internal/cricinfo/leagues.go +1210 -0
- package/internal/cricinfo/leagues_phase12_test.go +324 -0
- package/internal/cricinfo/live_leagues_test.go +169 -0
- package/internal/cricinfo/live_matches_test.go +203 -0
- package/internal/cricinfo/live_matrix_test.go +118 -0
- package/internal/cricinfo/live_players_test.go +122 -0
- package/internal/cricinfo/live_search_test.go +86 -0
- package/internal/cricinfo/live_smoke_test.go +213 -0
- package/internal/cricinfo/live_teams_test.go +104 -0
- package/internal/cricinfo/matches.go +1508 -0
- package/internal/cricinfo/matches_phase7_test.go +207 -0
- package/internal/cricinfo/matches_phase9_test.go +253 -0
- package/internal/cricinfo/normalize_entities.go +1727 -0
- package/internal/cricinfo/normalize_leagues.go +346 -0
- package/internal/cricinfo/players.go +1332 -0
- package/internal/cricinfo/players_phase10_test.go +174 -0
- package/internal/cricinfo/players_phase11_test.go +373 -0
- package/internal/cricinfo/render_contract.go +1088 -0
- package/internal/cricinfo/render_phase4_test.go +633 -0
- package/internal/cricinfo/renderer.go +1689 -0
- package/internal/cricinfo/resolver.go +813 -0
- package/internal/cricinfo/resolver_test.go +244 -0
- package/internal/cricinfo/teams.go +603 -0
- package/internal/cricinfo/teams_phase8_test.go +231 -0
- package/internal/cricinfo/testdata/fixtures/README.md +43 -0
- package/internal/cricinfo/testdata/fixtures/aux-competition-metadata/broadcasts.json +11 -0
- package/internal/cricinfo/testdata/fixtures/aux-competition-metadata/officials.json +150 -0
- package/internal/cricinfo/testdata/fixtures/details-plays/detail-110.json +157 -0
- package/internal/cricinfo/testdata/fixtures/details-plays/detail-52545007.json +145 -0
- package/internal/cricinfo/testdata/fixtures/details-plays/detail-52559021.json +143 -0
- package/internal/cricinfo/testdata/fixtures/details-plays/plays.json +15 -0
- package/internal/cricinfo/testdata/fixtures/endpoint-matrix.tsv +19 -0
- package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/fow-1.json +12 -0
- package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/fow.json +42 -0
- package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/innings-1-2.json +38 -0
- package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/partnership-1.json +31 -0
- package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/partnerships.json +42 -0
- package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/calendar-offdays.json +20 -0
- package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/calendar-ondays.json +21 -0
- package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/calendar.json +14 -0
- package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-2025.json +13 -0
- package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-group-1.json +13 -0
- package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-groups.json +11 -0
- package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-type-1.json +13 -0
- package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-types.json +11 -0
- package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/seasons.json +30 -0
- package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/standings-item-1.json +72 -0
- package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/standings-root.json +3 -0
- package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/standings.json +15 -0
- package/internal/cricinfo/testdata/fixtures/matches-competitions/competition.json +460 -0
- package/internal/cricinfo/testdata/fixtures/matches-competitions/event-1529474.json +86 -0
- package/internal/cricinfo/testdata/fixtures/matches-competitions/matchcards-1527966.json +368 -0
- package/internal/cricinfo/testdata/fixtures/matches-competitions/situation-1529474.json +10 -0
- package/internal/cricinfo/testdata/fixtures/players/athlete-1361257-statistics.json +126 -0
- package/internal/cricinfo/testdata/fixtures/players/athlete-1361257.json +113 -0
- package/internal/cricinfo/testdata/fixtures/players/roster-1361257-linescores-1-1-statistics-0.json +208 -0
- package/internal/cricinfo/testdata/fixtures/players/roster-1361257-linescores-1-2-statistics-0.json +252 -0
- package/internal/cricinfo/testdata/fixtures/players/roster-1361257-linescores.json +74 -0
- package/internal/cricinfo/testdata/fixtures/players/roster-1361257-statistics-0.json +1008 -0
- package/internal/cricinfo/testdata/fixtures/root-discovery/events.json +72 -0
- package/internal/cricinfo/testdata/fixtures/root-discovery/root.json +28 -0
- package/internal/cricinfo/testdata/fixtures/team-competitor/competitor-789643.json +40 -0
- package/internal/cricinfo/testdata/fixtures/team-competitor/leaders-789643.json +353 -0
- package/internal/cricinfo/testdata/fixtures/team-competitor/records-789643.json +91 -0
- package/internal/cricinfo/testdata/fixtures/team-competitor/roster-1147772-object.json +231 -0
- package/internal/cricinfo/testdata/fixtures/team-competitor/roster-1147772.json +235 -0
- package/internal/cricinfo/testdata/fixtures/team-competitor/roster-789643.json +322 -0
- package/internal/cricinfo/testdata/fixtures/team-competitor/scores-789643.json +19 -0
- package/internal/cricinfo/testdata/fixtures/team-competitor/statistics-789643.json +629 -0
- package/internal/cricinfo/testdata/fixtures/team-competitor/team-789643-athletes.json +7 -0
- package/internal/cricinfo/testdata/fixtures/team-competitor/team-789643.json +67 -0
- package/internal/cricinfo/testdata/golden/match-empty.golden +1 -0
- package/internal/cricinfo/testdata/golden/match-list.golden +2 -0
- package/internal/cricinfo/testdata/golden/match-partial.golden +3 -0
- package/internal/cricinfo/types.go +54 -0
- package/package.json +51 -0
- package/scripts/postinstall.js +153 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
package cricinfo
|
|
2
|
+
|
|
3
|
+
// TemplateCoverageEntry maps a researched endpoint template to public command coverage.
|
|
4
|
+
type TemplateCoverageEntry struct {
|
|
5
|
+
Template string
|
|
6
|
+
CommandFamily string
|
|
7
|
+
Command string
|
|
8
|
+
View string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// FieldPathCoverageEntry maps a field-path family to public command coverage.
|
|
12
|
+
type FieldPathCoverageEntry struct {
|
|
13
|
+
Family string
|
|
14
|
+
CommandFamily string
|
|
15
|
+
Command string
|
|
16
|
+
View string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
var templateCoverageLedger = map[string]TemplateCoverageEntry{
|
|
20
|
+
"/": {Template: "/", CommandFamily: "leagues", Command: "leagues list", View: "root discovery seed"},
|
|
21
|
+
"/athletes": {Template: "/athletes", CommandFamily: "search", Command: "search players", View: "global athlete discovery seed"},
|
|
22
|
+
"/athletes/{id}": {Template: "/athletes/{id}", CommandFamily: "players", Command: "players profile", View: "player profile"},
|
|
23
|
+
"/athletes/{id}/news": {Template: "/athletes/{id}/news", CommandFamily: "players", Command: "players news", View: "player news list"},
|
|
24
|
+
"/athletes/{id}/statistics": {Template: "/athletes/{id}/statistics", CommandFamily: "players", Command: "players stats", View: "player statistics categories"},
|
|
25
|
+
"/events": {Template: "/events", CommandFamily: "matches", Command: "matches list", View: "event discovery"},
|
|
26
|
+
"/events/{id}": {Template: "/events/{id}", CommandFamily: "matches", Command: "matches show", View: "event competition expansion"},
|
|
27
|
+
"/events/{id}/competitions/{id}": {Template: "/events/{id}/competitions/{id}", CommandFamily: "competitions", Command: "competitions show", View: "competition summary"},
|
|
28
|
+
"/events/{id}/teams/{id}": {Template: "/events/{id}/teams/{id}", CommandFamily: "teams", Command: "teams show", View: "event-team identity subview"},
|
|
29
|
+
"/leagues": {Template: "/leagues", CommandFamily: "leagues", Command: "leagues list", View: "league discovery"},
|
|
30
|
+
"/leagues/{id}": {Template: "/leagues/{id}", CommandFamily: "leagues", Command: "leagues show", View: "league summary"},
|
|
31
|
+
"/leagues/{id}/athletes/{id}": {Template: "/leagues/{id}/athletes/{id}", CommandFamily: "leagues", Command: "leagues athletes", View: "league athlete profile"},
|
|
32
|
+
"/leagues/{id}/athletes/{n}": {Template: "/leagues/{id}/athletes/{n}", CommandFamily: "leagues", Command: "leagues athletes", View: "league athlete index page"},
|
|
33
|
+
"/leagues/{id}/calendar": {Template: "/leagues/{id}/calendar", CommandFamily: "leagues", Command: "leagues calendar", View: "calendar root"},
|
|
34
|
+
"/leagues/{id}/calendar/offdays": {Template: "/leagues/{id}/calendar/offdays", CommandFamily: "leagues", Command: "leagues calendar", View: "calendar offdays section"},
|
|
35
|
+
"/leagues/{id}/calendar/ondays": {Template: "/leagues/{id}/calendar/ondays", CommandFamily: "leagues", Command: "leagues calendar", View: "calendar ondays section"},
|
|
36
|
+
"/leagues/{id}/events": {Template: "/leagues/{id}/events", CommandFamily: "leagues", Command: "leagues events", View: "league event list"},
|
|
37
|
+
"/leagues/{id}/events/{id}": {Template: "/leagues/{id}/events/{id}", CommandFamily: "leagues", Command: "leagues events", View: "event expansion"},
|
|
38
|
+
"/leagues/{id}/events/{id}/competitions/{id}": {Template: "/leagues/{id}/events/{id}/competitions/{id}", CommandFamily: "competitions", Command: "competitions show", View: "competition summary"},
|
|
39
|
+
"/leagues/{id}/events/{id}/competitions/{id}/broadcasts": {Template: "/leagues/{id}/events/{id}/competitions/{id}/broadcasts", CommandFamily: "competitions", Command: "competitions broadcasts", View: "competition broadcasts"},
|
|
40
|
+
"/leagues/{id}/events/{id}/competitions/{id}/details": {Template: "/leagues/{id}/events/{id}/competitions/{id}/details", CommandFamily: "matches", Command: "matches details", View: "delivery ref page"},
|
|
41
|
+
"/leagues/{id}/events/{id}/competitions/{id}/details/{id}": {Template: "/leagues/{id}/events/{id}/competitions/{id}/details/{id}", CommandFamily: "matches", Command: "matches details", View: "delivery event detail"},
|
|
42
|
+
"/leagues/{id}/events/{id}/competitions/{id}/details/{n}": {Template: "/leagues/{id}/events/{id}/competitions/{id}/details/{n}", CommandFamily: "matches", Command: "matches details", View: "delivery event detail"},
|
|
43
|
+
"/leagues/{id}/events/{id}/competitions/{id}/matchcards": {Template: "/leagues/{id}/events/{id}/competitions/{id}/matchcards", CommandFamily: "matches", Command: "matches scorecard", View: "batting/bowling/partnership cards"},
|
|
44
|
+
"/leagues/{id}/events/{id}/competitions/{id}/odds": {Template: "/leagues/{id}/events/{id}/competitions/{id}/odds", CommandFamily: "competitions", Command: "competitions odds", View: "competition odds"},
|
|
45
|
+
"/leagues/{id}/events/{id}/competitions/{id}/officials": {Template: "/leagues/{id}/events/{id}/competitions/{id}/officials", CommandFamily: "competitions", Command: "competitions officials", View: "competition officials"},
|
|
46
|
+
"/leagues/{id}/events/{id}/competitions/{id}/plays": {Template: "/leagues/{id}/events/{id}/competitions/{id}/plays", CommandFamily: "matches", Command: "matches plays", View: "play-derived delivery events"},
|
|
47
|
+
"/leagues/{id}/events/{id}/competitions/{id}/situation": {Template: "/leagues/{id}/events/{id}/competitions/{id}/situation", CommandFamily: "matches", Command: "matches situation", View: "match situation"},
|
|
48
|
+
"/leagues/{id}/events/{id}/competitions/{id}/situation/odds": {Template: "/leagues/{id}/events/{id}/competitions/{id}/situation/odds", CommandFamily: "matches", Command: "matches situation", View: "situation odds subview"},
|
|
49
|
+
"/leagues/{id}/events/{id}/competitions/{id}/status": {Template: "/leagues/{id}/events/{id}/competitions/{id}/status", CommandFamily: "matches", Command: "matches status", View: "match status"},
|
|
50
|
+
"/leagues/{id}/events/{id}/competitions/{id}/tickets": {Template: "/leagues/{id}/events/{id}/competitions/{id}/tickets", CommandFamily: "competitions", Command: "competitions tickets", View: "competition tickets"},
|
|
51
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}", CommandFamily: "teams", Command: "teams show --match <match>", View: "competitor summary"},
|
|
52
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/leaders": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/leaders", CommandFamily: "teams", Command: "teams leaders --match <match>", View: "team leaders"},
|
|
53
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores", CommandFamily: "matches", Command: "matches innings", View: "team innings list"},
|
|
54
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}", CommandFamily: "matches", Command: "matches innings", View: "innings pointer"},
|
|
55
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}/{n}": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}/{n}", CommandFamily: "matches", Command: "matches deliveries", View: "period innings detail"},
|
|
56
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}/{n}/fow": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}/{n}/fow", CommandFamily: "matches", Command: "matches fow", View: "fall-of-wicket list"},
|
|
57
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}/{n}/fow/{n}": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}/{n}/fow/{n}", CommandFamily: "matches", Command: "matches fow", View: "fall-of-wicket detail"},
|
|
58
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}/{n}/leaders": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}/{n}/leaders", CommandFamily: "matches", Command: "matches deliveries", View: "period leaders subview"},
|
|
59
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}/{n}/partnerships": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}/{n}/partnerships", CommandFamily: "matches", Command: "matches partnerships", View: "partnership list"},
|
|
60
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}/{n}/partnerships/{n}": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}/{n}/partnerships/{n}", CommandFamily: "matches", Command: "matches partnerships", View: "partnership detail"},
|
|
61
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}/{n}/statistics/{n}": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/linescores/{n}/{n}/statistics/{n}", CommandFamily: "matches", Command: "matches deliveries", View: "period statistics timelines"},
|
|
62
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/records": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/records", CommandFamily: "teams", Command: "teams records --match <match>", View: "team records categories"},
|
|
63
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/roster": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/roster", CommandFamily: "teams", Command: "teams roster --match <match>", View: "match roster entries"},
|
|
64
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/roster/{id}/linescores": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/roster/{id}/linescores", CommandFamily: "players", Command: "players innings --match <match>", View: "player innings list"},
|
|
65
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/roster/{id}/linescores/{n}/{n}/statistics/{n}": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/roster/{id}/linescores/{n}/{n}/statistics/{n}", CommandFamily: "players", Command: "players innings --match <match>", View: "player innings period statistics"},
|
|
66
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/roster/{id}/statistics/{n}": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/roster/{id}/statistics/{n}", CommandFamily: "players", Command: "players match-stats --match <match>", View: "player match statistics"},
|
|
67
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/scores": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/scores", CommandFamily: "teams", Command: "teams scores --match <match>", View: "team score"},
|
|
68
|
+
"/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/statistics": {Template: "/leagues/{id}/events/{id}/competitions/{id}/competitors/{id}/statistics", CommandFamily: "teams", Command: "teams statistics --match <match>", View: "team statistics categories"},
|
|
69
|
+
"/leagues/{id}/seasons": {Template: "/leagues/{id}/seasons", CommandFamily: "leagues", Command: "leagues seasons", View: "season list"},
|
|
70
|
+
"/leagues/{id}/seasons/{id}": {Template: "/leagues/{id}/seasons/{id}", CommandFamily: "seasons", Command: "seasons show", View: "season detail"},
|
|
71
|
+
"/leagues/{id}/seasons/{id}/types": {Template: "/leagues/{id}/seasons/{id}/types", CommandFamily: "seasons", Command: "seasons types", View: "season type list"},
|
|
72
|
+
"/leagues/{id}/seasons/{id}/types/{n}": {Template: "/leagues/{id}/seasons/{id}/types/{n}", CommandFamily: "seasons", Command: "seasons types", View: "season type detail"},
|
|
73
|
+
"/leagues/{id}/seasons/{id}/types/{n}/groups": {Template: "/leagues/{id}/seasons/{id}/types/{n}/groups", CommandFamily: "seasons", Command: "seasons groups", View: "season group list"},
|
|
74
|
+
"/leagues/{id}/standings": {Template: "/leagues/{id}/standings", CommandFamily: "standings", Command: "standings show", View: "standings groups"},
|
|
75
|
+
"/teams/{id}": {Template: "/teams/{id}", CommandFamily: "teams", Command: "teams show", View: "global team profile"},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
var fieldPathFamilyCoverageLedger = map[string]FieldPathCoverageEntry{
|
|
79
|
+
"athlete": {Family: "athlete", CommandFamily: "players", Command: "players profile", View: "player identity"},
|
|
80
|
+
"athletesInvolved": {Family: "athletesInvolved", CommandFamily: "matches", Command: "matches details", View: "delivery athlete involvement"},
|
|
81
|
+
"batsman": {Family: "batsman", CommandFamily: "matches", Command: "matches details", View: "delivery batting context"},
|
|
82
|
+
"bowler": {Family: "bowler", CommandFamily: "matches", Command: "matches details", View: "delivery bowling context"},
|
|
83
|
+
"broadcasts": {Family: "broadcasts", CommandFamily: "competitions", Command: "competitions broadcasts", View: "competition broadcasts"},
|
|
84
|
+
"competitions": {Family: "competitions", CommandFamily: "competitions", Command: "competitions metadata", View: "competition metadata root"},
|
|
85
|
+
"competitors": {Family: "competitors", CommandFamily: "teams", Command: "teams show --match <match>", View: "match competitor view"},
|
|
86
|
+
"details": {Family: "details", CommandFamily: "matches", Command: "matches details", View: "delivery detail refs"},
|
|
87
|
+
"dismissal": {Family: "dismissal", CommandFamily: "players", Command: "players dismissals --match <match>", View: "dismissal metadata"},
|
|
88
|
+
"entries": {Family: "entries", CommandFamily: "teams", Command: "teams roster --match <match>", View: "roster entries"},
|
|
89
|
+
"fow": {Family: "fow", CommandFamily: "matches", Command: "matches fow", View: "fall-of-wicket timeline"},
|
|
90
|
+
"innings": {Family: "innings", CommandFamily: "matches", Command: "matches innings", View: "innings summary"},
|
|
91
|
+
"items": {Family: "items", CommandFamily: "matches", Command: "matches list", View: "page envelope items"},
|
|
92
|
+
"leagues": {Family: "leagues", CommandFamily: "leagues", Command: "leagues show", View: "league hierarchy"},
|
|
93
|
+
"matchcards": {Family: "matchcards", CommandFamily: "matches", Command: "matches scorecard", View: "scorecard cards"},
|
|
94
|
+
"odds": {Family: "odds", CommandFamily: "competitions", Command: "competitions odds", View: "competition odds"},
|
|
95
|
+
"officials": {Family: "officials", CommandFamily: "competitions", Command: "competitions officials", View: "competition officials"},
|
|
96
|
+
"over": {Family: "over", CommandFamily: "matches", Command: "matches deliveries", View: "over timeline"},
|
|
97
|
+
"partnerships": {Family: "partnerships", CommandFamily: "matches", Command: "matches partnerships", View: "partnership timeline"},
|
|
98
|
+
"seasons": {Family: "seasons", CommandFamily: "seasons", Command: "seasons groups", View: "season hierarchy"},
|
|
99
|
+
"situation": {Family: "situation", CommandFamily: "matches", Command: "matches situation", View: "situation snapshot"},
|
|
100
|
+
"splits": {Family: "splits", CommandFamily: "players", Command: "players stats", View: "statistics splits and categories"},
|
|
101
|
+
"status": {Family: "status", CommandFamily: "matches", Command: "matches status", View: "status summary"},
|
|
102
|
+
"teams": {Family: "teams", CommandFamily: "teams", Command: "teams show", View: "team identity"},
|
|
103
|
+
"tickets": {Family: "tickets", CommandFamily: "competitions", Command: "competitions tickets", View: "competition tickets"},
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// TemplateCoverageLedger returns a copy of the researched-template coverage ledger.
|
|
107
|
+
func TemplateCoverageLedger() map[string]TemplateCoverageEntry {
|
|
108
|
+
out := make(map[string]TemplateCoverageEntry, len(templateCoverageLedger))
|
|
109
|
+
for template, entry := range templateCoverageLedger {
|
|
110
|
+
out[template] = entry
|
|
111
|
+
}
|
|
112
|
+
return out
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// FieldPathFamilyCoverageLedger returns a copy of the field-family coverage ledger.
|
|
116
|
+
func FieldPathFamilyCoverageLedger() map[string]FieldPathCoverageEntry {
|
|
117
|
+
out := make(map[string]FieldPathCoverageEntry, len(fieldPathFamilyCoverageLedger))
|
|
118
|
+
for family, entry := range fieldPathFamilyCoverageLedger {
|
|
119
|
+
out[family] = entry
|
|
120
|
+
}
|
|
121
|
+
return out
|
|
122
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
package cricinfo
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bufio"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"sort"
|
|
8
|
+
"strings"
|
|
9
|
+
"testing"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
func TestTemplateCoverageLedgerIncludesAllResearchedTemplates(t *testing.T) {
|
|
13
|
+
t.Parallel()
|
|
14
|
+
|
|
15
|
+
ledger := TemplateCoverageLedger()
|
|
16
|
+
if len(ledger) == 0 {
|
|
17
|
+
t.Fatalf("template coverage ledger is empty")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
templates := researchedTemplatesFromTSV(t)
|
|
21
|
+
if len(templates) == 0 {
|
|
22
|
+
t.Fatalf("expected researched templates from TSV")
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
missing := make([]string, 0)
|
|
26
|
+
for _, template := range templates {
|
|
27
|
+
entry, ok := ledger[template]
|
|
28
|
+
if !ok {
|
|
29
|
+
missing = append(missing, template)
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
32
|
+
if strings.TrimSpace(entry.Template) == "" || entry.Template != template {
|
|
33
|
+
t.Fatalf("template ledger entry mismatch for %q: %+v", template, entry)
|
|
34
|
+
}
|
|
35
|
+
if strings.TrimSpace(entry.CommandFamily) == "" {
|
|
36
|
+
t.Fatalf("template %q has empty command family", template)
|
|
37
|
+
}
|
|
38
|
+
if strings.TrimSpace(entry.Command) == "" {
|
|
39
|
+
t.Fatalf("template %q has empty command mapping", template)
|
|
40
|
+
}
|
|
41
|
+
if strings.TrimSpace(entry.View) == "" {
|
|
42
|
+
t.Fatalf("template %q has empty result view mapping", template)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
extras := make([]string, 0)
|
|
47
|
+
expected := make(map[string]struct{}, len(templates))
|
|
48
|
+
for _, template := range templates {
|
|
49
|
+
expected[template] = struct{}{}
|
|
50
|
+
}
|
|
51
|
+
for template := range ledger {
|
|
52
|
+
if _, ok := expected[template]; !ok {
|
|
53
|
+
extras = append(extras, template)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
sort.Strings(missing)
|
|
58
|
+
sort.Strings(extras)
|
|
59
|
+
if len(missing) > 0 || len(extras) > 0 {
|
|
60
|
+
t.Fatalf("template coverage drift: missing=%v extras=%v", missing, extras)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func TestFieldPathFamilyCoverageLedgerKnownFamiliesMapped(t *testing.T) {
|
|
65
|
+
t.Parallel()
|
|
66
|
+
|
|
67
|
+
ledger := FieldPathFamilyCoverageLedger()
|
|
68
|
+
if len(ledger) == 0 {
|
|
69
|
+
t.Fatalf("field-path family coverage ledger is empty")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
catalogFamilies := researchedFieldPathFamilies(t)
|
|
73
|
+
if len(catalogFamilies) == 0 {
|
|
74
|
+
t.Fatalf("expected field-path families from catalog")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
expectedFamilies := []string{
|
|
78
|
+
"athlete",
|
|
79
|
+
"athletesInvolved",
|
|
80
|
+
"batsman",
|
|
81
|
+
"bowler",
|
|
82
|
+
"broadcasts",
|
|
83
|
+
"competitions",
|
|
84
|
+
"competitors",
|
|
85
|
+
"details",
|
|
86
|
+
"dismissal",
|
|
87
|
+
"entries",
|
|
88
|
+
"fow",
|
|
89
|
+
"innings",
|
|
90
|
+
"items",
|
|
91
|
+
"leagues",
|
|
92
|
+
"matchcards",
|
|
93
|
+
"odds",
|
|
94
|
+
"officials",
|
|
95
|
+
"over",
|
|
96
|
+
"partnerships",
|
|
97
|
+
"seasons",
|
|
98
|
+
"situation",
|
|
99
|
+
"splits",
|
|
100
|
+
"status",
|
|
101
|
+
"teams",
|
|
102
|
+
"tickets",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
missingLedger := make([]string, 0)
|
|
106
|
+
missingCatalog := make([]string, 0)
|
|
107
|
+
for _, family := range expectedFamilies {
|
|
108
|
+
entry, ok := ledger[family]
|
|
109
|
+
if !ok {
|
|
110
|
+
missingLedger = append(missingLedger, family)
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
if strings.TrimSpace(entry.Family) == "" || entry.Family != family {
|
|
114
|
+
t.Fatalf("field-path ledger entry mismatch for %q: %+v", family, entry)
|
|
115
|
+
}
|
|
116
|
+
if strings.TrimSpace(entry.CommandFamily) == "" {
|
|
117
|
+
t.Fatalf("field-path family %q has empty command family", family)
|
|
118
|
+
}
|
|
119
|
+
if strings.TrimSpace(entry.Command) == "" {
|
|
120
|
+
t.Fatalf("field-path family %q has empty command mapping", family)
|
|
121
|
+
}
|
|
122
|
+
if strings.TrimSpace(entry.View) == "" {
|
|
123
|
+
t.Fatalf("field-path family %q has empty result view mapping", family)
|
|
124
|
+
}
|
|
125
|
+
if _, ok := catalogFamilies[family]; !ok {
|
|
126
|
+
missingCatalog = append(missingCatalog, family)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
sort.Strings(missingLedger)
|
|
131
|
+
sort.Strings(missingCatalog)
|
|
132
|
+
if len(missingLedger) > 0 || len(missingCatalog) > 0 {
|
|
133
|
+
t.Fatalf("field-path family coverage drift: missing ledger=%v missing catalog=%v", missingLedger, missingCatalog)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
func researchedTemplatesFromTSV(t *testing.T) []string {
|
|
138
|
+
t.Helper()
|
|
139
|
+
|
|
140
|
+
path := filepath.Join(repoRoot(t), "gg", "agent-outputs", "cricinfo-working-templates.tsv")
|
|
141
|
+
file, err := os.Open(path)
|
|
142
|
+
if err != nil {
|
|
143
|
+
t.Fatalf("open working templates TSV %q: %v", path, err)
|
|
144
|
+
}
|
|
145
|
+
defer file.Close()
|
|
146
|
+
|
|
147
|
+
seen := map[string]struct{}{}
|
|
148
|
+
templates := make([]string, 0, 64)
|
|
149
|
+
scanner := bufio.NewScanner(file)
|
|
150
|
+
for scanner.Scan() {
|
|
151
|
+
line := strings.TrimSpace(scanner.Text())
|
|
152
|
+
if line == "" {
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
parts := strings.SplitN(line, "\t", 3)
|
|
156
|
+
if len(parts) < 2 {
|
|
157
|
+
t.Fatalf("unexpected TSV row %q", line)
|
|
158
|
+
}
|
|
159
|
+
template := strings.TrimSpace(parts[1])
|
|
160
|
+
if template == "" {
|
|
161
|
+
continue
|
|
162
|
+
}
|
|
163
|
+
if _, ok := seen[template]; ok {
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
seen[template] = struct{}{}
|
|
167
|
+
templates = append(templates, template)
|
|
168
|
+
}
|
|
169
|
+
if err := scanner.Err(); err != nil {
|
|
170
|
+
t.Fatalf("scan working templates TSV %q: %v", path, err)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
sort.Strings(templates)
|
|
174
|
+
return templates
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
func researchedFieldPathFamilies(t *testing.T) map[string]struct{} {
|
|
178
|
+
t.Helper()
|
|
179
|
+
|
|
180
|
+
path := filepath.Join(repoRoot(t), "gg", "agent-outputs", "cricinfo-field-path-catalog.txt")
|
|
181
|
+
file, err := os.Open(path)
|
|
182
|
+
if err != nil {
|
|
183
|
+
t.Fatalf("open field-path catalog %q: %v", path, err)
|
|
184
|
+
}
|
|
185
|
+
defer file.Close()
|
|
186
|
+
|
|
187
|
+
families := map[string]struct{}{}
|
|
188
|
+
scanner := bufio.NewScanner(file)
|
|
189
|
+
for scanner.Scan() {
|
|
190
|
+
path := strings.TrimSpace(scanner.Text())
|
|
191
|
+
if path == "" {
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
family := firstFieldPathFamily(path)
|
|
195
|
+
if family == "" {
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
families[family] = struct{}{}
|
|
199
|
+
}
|
|
200
|
+
if err := scanner.Err(); err != nil {
|
|
201
|
+
t.Fatalf("scan field-path catalog %q: %v", path, err)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return families
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
func firstFieldPathFamily(path string) string {
|
|
208
|
+
parts := strings.Split(path, ".")
|
|
209
|
+
for _, part := range parts {
|
|
210
|
+
token := strings.TrimSpace(part)
|
|
211
|
+
if token == "" || token == "$ref" {
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
if isNumericToken(token) {
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
217
|
+
return token
|
|
218
|
+
}
|
|
219
|
+
return ""
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
func isNumericToken(value string) bool {
|
|
223
|
+
value = strings.TrimSpace(value)
|
|
224
|
+
if value == "" {
|
|
225
|
+
return false
|
|
226
|
+
}
|
|
227
|
+
for _, r := range value {
|
|
228
|
+
if r < '0' || r > '9' {
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return true
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
func repoRoot(t *testing.T) string {
|
|
236
|
+
t.Helper()
|
|
237
|
+
|
|
238
|
+
dir, err := os.Getwd()
|
|
239
|
+
if err != nil {
|
|
240
|
+
t.Fatalf("getwd: %v", err)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
for {
|
|
244
|
+
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
|
|
245
|
+
return dir
|
|
246
|
+
}
|
|
247
|
+
parent := filepath.Dir(dir)
|
|
248
|
+
if parent == dir {
|
|
249
|
+
t.Fatalf("unable to locate repository root from %q", dir)
|
|
250
|
+
}
|
|
251
|
+
dir = parent
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
package cricinfo
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"fmt"
|
|
6
|
+
"sort"
|
|
7
|
+
"strings"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// DecodePage decodes a paginated envelope payload.
|
|
11
|
+
func DecodePage[T any](data []byte) (*Page[T], error) {
|
|
12
|
+
var page Page[T]
|
|
13
|
+
if err := json.Unmarshal(data, &page); err != nil {
|
|
14
|
+
return nil, fmt.Errorf("decode paginated payload: %w", err)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if page.Items == nil {
|
|
18
|
+
page.Items = []T{}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return &page, nil
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// DecodeObjectCollection decodes array-shaped or object-shaped collection fields.
|
|
25
|
+
func DecodeObjectCollection[T any](data []byte, field string) ([]T, error) {
|
|
26
|
+
field = strings.TrimSpace(field)
|
|
27
|
+
if field == "" {
|
|
28
|
+
return nil, fmt.Errorf("collection field is required")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
var envelope map[string]json.RawMessage
|
|
32
|
+
if err := json.Unmarshal(data, &envelope); err != nil {
|
|
33
|
+
return nil, fmt.Errorf("decode envelope: %w", err)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
raw, ok := envelope[field]
|
|
37
|
+
if !ok {
|
|
38
|
+
return nil, fmt.Errorf("collection field %q not found", field)
|
|
39
|
+
}
|
|
40
|
+
trimmed := strings.TrimSpace(string(raw))
|
|
41
|
+
if trimmed == "" || trimmed == "null" {
|
|
42
|
+
return []T{}, nil
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if strings.HasPrefix(trimmed, "[") {
|
|
46
|
+
var items []T
|
|
47
|
+
if err := json.Unmarshal(raw, &items); err != nil {
|
|
48
|
+
return nil, fmt.Errorf("decode array collection field %q: %w", field, err)
|
|
49
|
+
}
|
|
50
|
+
return items, nil
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if strings.HasPrefix(trimmed, "{") {
|
|
54
|
+
var keyed map[string]json.RawMessage
|
|
55
|
+
if err := json.Unmarshal(raw, &keyed); err != nil {
|
|
56
|
+
return nil, fmt.Errorf("decode object collection field %q: %w", field, err)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
keys := make([]string, 0, len(keyed))
|
|
60
|
+
for k := range keyed {
|
|
61
|
+
keys = append(keys, k)
|
|
62
|
+
}
|
|
63
|
+
sort.Strings(keys)
|
|
64
|
+
|
|
65
|
+
items := make([]T, 0, len(keys))
|
|
66
|
+
for _, key := range keys {
|
|
67
|
+
var item T
|
|
68
|
+
if err := json.Unmarshal(keyed[key], &item); err != nil {
|
|
69
|
+
return nil, fmt.Errorf("decode object collection field %q key %q: %w", field, key, err)
|
|
70
|
+
}
|
|
71
|
+
items = append(items, item)
|
|
72
|
+
}
|
|
73
|
+
return items, nil
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return nil, fmt.Errorf("collection field %q is neither array nor object", field)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// DecodeStatsObject decodes a single-object stats payload.
|
|
80
|
+
func DecodeStatsObject(data []byte) (*StatsObject, error) {
|
|
81
|
+
var stats StatsObject
|
|
82
|
+
if err := json.Unmarshal(data, &stats); err != nil {
|
|
83
|
+
return nil, fmt.Errorf("decode stats payload: %w", err)
|
|
84
|
+
}
|
|
85
|
+
return &stats, nil
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ExtractOptionalRef gets a nested {"$ref":"..."} field when present.
|
|
89
|
+
func ExtractOptionalRef(data []byte, field string) (*Ref, bool, error) {
|
|
90
|
+
field = strings.TrimSpace(field)
|
|
91
|
+
if field == "" {
|
|
92
|
+
return nil, false, fmt.Errorf("field is required")
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
var envelope map[string]json.RawMessage
|
|
96
|
+
if err := json.Unmarshal(data, &envelope); err != nil {
|
|
97
|
+
return nil, false, fmt.Errorf("decode envelope: %w", err)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
raw, ok := envelope[field]
|
|
101
|
+
if !ok || string(raw) == "null" {
|
|
102
|
+
return nil, false, nil
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
var ref Ref
|
|
106
|
+
if err := json.Unmarshal(raw, &ref); err != nil {
|
|
107
|
+
return nil, false, fmt.Errorf("decode ref field %q: %w", field, err)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if strings.TrimSpace(ref.URL) == "" {
|
|
111
|
+
return nil, false, nil
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return &ref, true, nil
|
|
115
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
package cricinfo
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"testing"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
type testItem struct {
|
|
8
|
+
ID int `json:"id"`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
func TestDecodePage(t *testing.T) {
|
|
12
|
+
t.Parallel()
|
|
13
|
+
|
|
14
|
+
page, err := DecodePage[Ref]([]byte(`{"count":1,"items":[{"$ref":"http://example.com/a"}],"pageCount":1,"pageIndex":1,"pageSize":20}`))
|
|
15
|
+
if err != nil {
|
|
16
|
+
t.Fatalf("DecodePage error: %v", err)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if page.Count != 1 || page.PageSize != 20 {
|
|
20
|
+
t.Fatalf("unexpected page metadata: %+v", page)
|
|
21
|
+
}
|
|
22
|
+
if len(page.Items) != 1 || page.Items[0].URL != "http://example.com/a" {
|
|
23
|
+
t.Fatalf("unexpected page items: %+v", page.Items)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func TestDecodeObjectCollectionArray(t *testing.T) {
|
|
28
|
+
t.Parallel()
|
|
29
|
+
|
|
30
|
+
items, err := DecodeObjectCollection[testItem]([]byte(`{"entries":[{"id":1},{"id":2}]}`), "entries")
|
|
31
|
+
if err != nil {
|
|
32
|
+
t.Fatalf("DecodeObjectCollection error: %v", err)
|
|
33
|
+
}
|
|
34
|
+
if len(items) != 2 || items[0].ID != 1 || items[1].ID != 2 {
|
|
35
|
+
t.Fatalf("unexpected array items: %+v", items)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func TestDecodeObjectCollectionObject(t *testing.T) {
|
|
40
|
+
t.Parallel()
|
|
41
|
+
|
|
42
|
+
items, err := DecodeObjectCollection[testItem]([]byte(`{"entries":{"b":{"id":2},"a":{"id":1}}}`), "entries")
|
|
43
|
+
if err != nil {
|
|
44
|
+
t.Fatalf("DecodeObjectCollection error: %v", err)
|
|
45
|
+
}
|
|
46
|
+
if len(items) != 2 || items[0].ID != 1 || items[1].ID != 2 {
|
|
47
|
+
t.Fatalf("unexpected object items order/content: %+v", items)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func TestDecodeObjectCollectionNull(t *testing.T) {
|
|
52
|
+
t.Parallel()
|
|
53
|
+
|
|
54
|
+
items, err := DecodeObjectCollection[testItem]([]byte(`{"entries":null}`), "entries")
|
|
55
|
+
if err != nil {
|
|
56
|
+
t.Fatalf("DecodeObjectCollection error: %v", err)
|
|
57
|
+
}
|
|
58
|
+
if len(items) != 0 {
|
|
59
|
+
t.Fatalf("expected empty slice, got %+v", items)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func TestDecodeStatsObject(t *testing.T) {
|
|
64
|
+
t.Parallel()
|
|
65
|
+
|
|
66
|
+
stats, err := DecodeStatsObject([]byte(`{"$ref":"http://example.com/stats","athlete":{"$ref":"http://example.com/athletes/1"},"splits":{"categories":[]}}`))
|
|
67
|
+
if err != nil {
|
|
68
|
+
t.Fatalf("DecodeStatsObject error: %v", err)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if stats.Ref != "http://example.com/stats" {
|
|
72
|
+
t.Fatalf("unexpected stats ref: %q", stats.Ref)
|
|
73
|
+
}
|
|
74
|
+
if stats.Athlete == nil || stats.Athlete.URL == "" {
|
|
75
|
+
t.Fatalf("expected athlete ref in stats object")
|
|
76
|
+
}
|
|
77
|
+
if len(stats.Splits) == 0 {
|
|
78
|
+
t.Fatalf("expected splits payload")
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func TestExtractOptionalRef(t *testing.T) {
|
|
83
|
+
t.Parallel()
|
|
84
|
+
|
|
85
|
+
ref, ok, err := ExtractOptionalRef([]byte(`{"status":{"$ref":"http://example.com/status"}}`), "status")
|
|
86
|
+
if err != nil {
|
|
87
|
+
t.Fatalf("ExtractOptionalRef error: %v", err)
|
|
88
|
+
}
|
|
89
|
+
if !ok || ref == nil || ref.URL != "http://example.com/status" {
|
|
90
|
+
t.Fatalf("unexpected optional ref output: ok=%v ref=%+v", ok, ref)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
nilRef, nilOk, nilErr := ExtractOptionalRef([]byte(`{"status":null}`), "status")
|
|
94
|
+
if nilErr != nil {
|
|
95
|
+
t.Fatalf("ExtractOptionalRef null error: %v", nilErr)
|
|
96
|
+
}
|
|
97
|
+
if nilOk || nilRef != nil {
|
|
98
|
+
t.Fatalf("expected null optional ref, got ok=%v ref=%+v", nilOk, nilRef)
|
|
99
|
+
}
|
|
100
|
+
}
|