Skip to content

Commit e175fa0

Browse files
authored
Merge pull request #7 from sumo-mcp/workspace
Add GetRikishiStatsAPI
2 parents 14ed01d + e5a2179 commit e175fa0

6 files changed

Lines changed: 308 additions & 14 deletions

File tree

client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
type Client interface {
1515
SearchRikishisAPI
1616
GetRikishiAPI
17+
GetRikishiStatsAPI
1718
ListRankChangesAPI
1819
ListShikonaChangesAPI
1920
ListMeasurementChangesAPI

client_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package sumoapi_test
2+
3+
import "net/http"
4+
5+
type mockTransport struct {
6+
validateRequest func(*http.Request) error
7+
response *http.Response
8+
}
9+
10+
func (m *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
11+
if m.validateRequest != nil {
12+
if err := m.validateRequest(req); err != nil {
13+
return nil, err
14+
}
15+
}
16+
return m.response, nil
17+
}

get_rikishi_stats.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package sumoapi
2+
3+
import (
4+
"context"
5+
"fmt"
6+
)
7+
8+
// GetRikishiStatsAPI defines the methods available for retrieving statistics for a single Rikishi.
9+
type GetRikishiStatsAPI interface {
10+
// GetRikishiStats calls the GET /api/rikishi/{rikishiID}/stats endpoint.
11+
GetRikishiStats(ctx context.Context, req GetRikishiStatsRequest) (*GetRikishiStatsResponse, error)
12+
}
13+
14+
// GetRikishiStatsRequest represents the request parameters for the GetRikishiStats method.
15+
type GetRikishiStatsRequest struct {
16+
RikishiID int `json:"rikishiId" jsonschema:"The unique identifier of the Rikishi to retrieve. Example: 45 = Terunofuji"`
17+
}
18+
19+
// GetRikishiStatsResponse represents the response from the GetRikishiStats method.
20+
type GetRikishiStatsResponse struct {
21+
Basho int `json:"basho,omitempty" jsonschema:"The number of official tournaments (basho) the Rikishi has participated in."`
22+
Yusho int `json:"yusho,omitempty" jsonschema:"The number of tournament championships (yusho) the Rikishi has won."`
23+
TotalMatches int `json:"totalMatches,omitempty" jsonschema:"The total number of matches the Rikishi has had."`
24+
TotalWins int `json:"totalWins,omitempty" jsonschema:"The total number of wins the Rikishi has achieved."`
25+
TotalLosses int `json:"totalLosses,omitempty" jsonschema:"The total number of losses the Rikishi has suffered."`
26+
TotalAbsences int `json:"totalAbsences,omitempty" jsonschema:"The total number of absences the Rikishi has had."`
27+
Sansho map[string]int `json:"sansho,omitempty" jsonschema:"A mapping of special prize names to the number of times the Rikishi has won each prize."`
28+
BashoByDivision map[string]int `json:"bashoByDivision,omitempty" jsonschema:"A mapping of division names to the number of basho the Rikishi has participated in each division."`
29+
YushoByDivision map[string]int `json:"yushoByDivision,omitempty" jsonschema:"A mapping of division names to the number of yusho the Rikishi has won in each division."`
30+
WinsByDivision map[string]int `json:"winsByDivision,omitempty" jsonschema:"A mapping of division names to the number of wins the Rikishi has had in each division."`
31+
LossByDivision map[string]int `json:"lossByDivision,omitempty" jsonschema:"A mapping of division names to the number of losses the Rikishi has had in each division."`
32+
AbsenceByDivision map[string]int `json:"absenceByDivision,omitempty" jsonschema:"A mapping of division names to the number of absences the Rikishi has had in each division."`
33+
TotalMatchesByDivision map[string]int `json:"totalByDivision,omitempty" jsonschema:"A mapping of division names to the total number of matches the Rikishi has had in each division."`
34+
}
35+
36+
func (c *client) GetRikishiStats(ctx context.Context, req GetRikishiStatsRequest) (*GetRikishiStatsResponse, error) {
37+
path := fmt.Sprintf("/rikishi/%d/stats", req.RikishiID)
38+
return getObject[GetRikishiStatsResponse](ctx, c, path, nil)
39+
}

get_rikishi_stats_test.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package sumoapi_test
2+
3+
import (
4+
"context"
5+
"io"
6+
"net/http"
7+
"strings"
8+
"testing"
9+
10+
. "github.com/onsi/gomega"
11+
12+
"github.com/sumo-mcp/sumoapi-go"
13+
)
14+
15+
func TestClient_GetRikishiStats(t *testing.T) {
16+
t.Run("get rikishi stats by ID", func(t *testing.T) {
17+
g := NewWithT(t)
18+
19+
mockResp := `{
20+
"basho": 81,
21+
"yusho": 13,
22+
"totalMatches": 798,
23+
"totalWins": 523,
24+
"totalLosses": 275,
25+
"totalAbsences": 231,
26+
"sansho": {
27+
"Gino-sho": 3,
28+
"Kanto-sho": 3,
29+
"Shukun-sho": 3
30+
},
31+
"bashoByDivision": {
32+
"Makuuchi": 52,
33+
"Juryo": 7
34+
},
35+
"yushoByDivision": {
36+
"Makuuchi": 10,
37+
"Juryo": 2
38+
},
39+
"winsByDivision": {
40+
"Makuuchi": 366,
41+
"Juryo": 61
42+
},
43+
"lossByDivision": {
44+
"Makuuchi": 207,
45+
"Juryo": 38
46+
},
47+
"absenceByDivision": {
48+
"Makuuchi": 197,
49+
"Juryo": 6
50+
},
51+
"totalByDivision": {
52+
"Makuuchi": 573,
53+
"Juryo": 99
54+
}
55+
}`
56+
57+
transport := &mockTransport{
58+
validateRequest: func(req *http.Request) error {
59+
g.Expect(req.Method).To(Equal(http.MethodGet))
60+
g.Expect(req.URL.Scheme).To(Equal("https"))
61+
g.Expect(req.URL.Host).To(Equal("sumo-api.com"))
62+
g.Expect(req.URL.Path).To(Equal("/api/rikishi/45/stats"))
63+
g.Expect(req.URL.RawQuery).To(BeEmpty())
64+
return nil
65+
},
66+
response: &http.Response{
67+
StatusCode: http.StatusOK,
68+
Body: io.NopCloser(strings.NewReader(mockResp)),
69+
},
70+
}
71+
72+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
73+
resp, err := client.GetRikishiStats(context.Background(), sumoapi.GetRikishiStatsRequest{
74+
RikishiID: 45,
75+
})
76+
77+
g.Expect(err).ToNot(HaveOccurred())
78+
g.Expect(resp).ToNot(BeNil())
79+
g.Expect(resp.Basho).To(Equal(81))
80+
g.Expect(resp.Yusho).To(Equal(13))
81+
g.Expect(resp.TotalMatches).To(Equal(798))
82+
g.Expect(resp.TotalWins).To(Equal(523))
83+
g.Expect(resp.TotalLosses).To(Equal(275))
84+
g.Expect(resp.TotalAbsences).To(Equal(231))
85+
86+
// Check sansho
87+
g.Expect(resp.Sansho).To(HaveLen(3))
88+
g.Expect(resp.Sansho["Gino-sho"]).To(Equal(3))
89+
g.Expect(resp.Sansho["Kanto-sho"]).To(Equal(3))
90+
g.Expect(resp.Sansho["Shukun-sho"]).To(Equal(3))
91+
92+
// Check division stats
93+
g.Expect(resp.BashoByDivision["Makuuchi"]).To(Equal(52))
94+
g.Expect(resp.YushoByDivision["Makuuchi"]).To(Equal(10))
95+
g.Expect(resp.WinsByDivision["Makuuchi"]).To(Equal(366))
96+
g.Expect(resp.LossByDivision["Makuuchi"]).To(Equal(207))
97+
g.Expect(resp.AbsenceByDivision["Makuuchi"]).To(Equal(197))
98+
g.Expect(resp.TotalMatchesByDivision["Makuuchi"]).To(Equal(573))
99+
})
100+
101+
t.Run("context is propagated", func(t *testing.T) {
102+
g := NewWithT(t)
103+
104+
mockResp := `{
105+
"basho": 10,
106+
"yusho": 1
107+
}`
108+
109+
type testKey struct{}
110+
ctx := context.WithValue(context.Background(), testKey{}, "test-value")
111+
112+
transport := &mockTransport{
113+
validateRequest: func(req *http.Request) error {
114+
g.Expect(req.Context().Value(testKey{})).To(Equal("test-value"))
115+
return nil
116+
},
117+
response: &http.Response{
118+
StatusCode: http.StatusOK,
119+
Body: io.NopCloser(strings.NewReader(mockResp)),
120+
},
121+
}
122+
123+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
124+
_, err := client.GetRikishiStats(ctx, sumoapi.GetRikishiStatsRequest{
125+
RikishiID: 45,
126+
})
127+
128+
g.Expect(err).ToNot(HaveOccurred())
129+
})
130+
131+
t.Run("http request error", func(t *testing.T) {
132+
g := NewWithT(t)
133+
134+
transport := &mockTransport{
135+
validateRequest: func(req *http.Request) error {
136+
return http.ErrAbortHandler
137+
},
138+
}
139+
140+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
141+
resp, err := client.GetRikishiStats(context.Background(), sumoapi.GetRikishiStatsRequest{
142+
RikishiID: 45,
143+
})
144+
145+
g.Expect(err).To(HaveOccurred())
146+
g.Expect(err.Error()).To(ContainSubstring("error making http request"))
147+
g.Expect(resp).To(BeNil())
148+
})
149+
150+
t.Run("invalid JSON response", func(t *testing.T) {
151+
g := NewWithT(t)
152+
153+
transport := &mockTransport{
154+
response: &http.Response{
155+
StatusCode: http.StatusOK,
156+
Body: io.NopCloser(strings.NewReader("not valid json")),
157+
},
158+
}
159+
160+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
161+
resp, err := client.GetRikishiStats(context.Background(), sumoapi.GetRikishiStatsRequest{
162+
RikishiID: 45,
163+
})
164+
165+
g.Expect(err).To(HaveOccurred())
166+
g.Expect(err.Error()).To(ContainSubstring("error unmarshaling response body"))
167+
g.Expect(resp).To(BeNil())
168+
})
169+
}

search_rikishis_test.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,6 @@ import (
1212
"github.com/sumo-mcp/sumoapi-go"
1313
)
1414

15-
type mockTransport struct {
16-
validateRequest func(*http.Request) error
17-
response *http.Response
18-
}
19-
20-
func (m *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
21-
if m.validateRequest != nil {
22-
if err := m.validateRequest(req); err != nil {
23-
return nil, err
24-
}
25-
}
26-
return m.response, nil
27-
}
28-
2915
func TestClient_SearchRikishis(t *testing.T) {
3016
t.Run("search by shikona", func(t *testing.T) {
3117
g := NewWithT(t)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package integration_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
. "github.com/onsi/gomega"
8+
9+
"github.com/sumo-mcp/sumoapi-go"
10+
)
11+
12+
func TestIntegration_GetRikishiStats(t *testing.T) {
13+
g := NewWithT(t)
14+
15+
client := sumoapi.New()
16+
17+
resp, err := client.GetRikishiStats(context.Background(), sumoapi.GetRikishiStatsRequest{
18+
RikishiID: 45, // Terunofuji
19+
})
20+
21+
g.Expect(err).ToNot(HaveOccurred())
22+
g.Expect(resp).ToNot(BeNil())
23+
24+
// Verify overall stats
25+
g.Expect(resp.Basho).To(Equal(81))
26+
g.Expect(resp.Yusho).To(Equal(13))
27+
g.Expect(resp.TotalMatches).To(Equal(798))
28+
g.Expect(resp.TotalWins).To(Equal(523))
29+
g.Expect(resp.TotalLosses).To(Equal(275))
30+
g.Expect(resp.TotalAbsences).To(Equal(231))
31+
32+
// Verify sansho (special prizes)
33+
g.Expect(resp.Sansho).To(HaveLen(3))
34+
g.Expect(resp.Sansho["Gino-sho"]).To(Equal(3))
35+
g.Expect(resp.Sansho["Kanto-sho"]).To(Equal(3))
36+
g.Expect(resp.Sansho["Shukun-sho"]).To(Equal(3))
37+
38+
// Verify basho by division
39+
g.Expect(resp.BashoByDivision["Jonokuchi"]).To(Equal(1))
40+
g.Expect(resp.BashoByDivision["Jonidan"]).To(Equal(2))
41+
g.Expect(resp.BashoByDivision["Sandanme"]).To(Equal(4))
42+
g.Expect(resp.BashoByDivision["Makushita"]).To(Equal(15))
43+
g.Expect(resp.BashoByDivision["Juryo"]).To(Equal(7))
44+
g.Expect(resp.BashoByDivision["Makuuchi"]).To(Equal(52))
45+
46+
// Verify yusho by division
47+
g.Expect(resp.YushoByDivision["Makushita"]).To(Equal(1))
48+
g.Expect(resp.YushoByDivision["Juryo"]).To(Equal(2))
49+
g.Expect(resp.YushoByDivision["Makuuchi"]).To(Equal(10))
50+
51+
// Verify wins by division
52+
g.Expect(resp.WinsByDivision["Jonokuchi"]).To(Equal(5))
53+
g.Expect(resp.WinsByDivision["Jonidan"]).To(Equal(13))
54+
g.Expect(resp.WinsByDivision["Sandanme"]).To(Equal(13))
55+
g.Expect(resp.WinsByDivision["Makushita"]).To(Equal(65))
56+
g.Expect(resp.WinsByDivision["Juryo"]).To(Equal(61))
57+
g.Expect(resp.WinsByDivision["Makuuchi"]).To(Equal(366))
58+
59+
// Verify losses by division
60+
g.Expect(resp.LossByDivision["Jonokuchi"]).To(Equal(2))
61+
g.Expect(resp.LossByDivision["Jonidan"]).To(Equal(1))
62+
g.Expect(resp.LossByDivision["Sandanme"]).To(Equal(1))
63+
g.Expect(resp.LossByDivision["Makushita"]).To(Equal(26))
64+
g.Expect(resp.LossByDivision["Juryo"]).To(Equal(38))
65+
g.Expect(resp.LossByDivision["Makuuchi"]).To(Equal(207))
66+
67+
// Verify absences by division
68+
g.Expect(resp.AbsenceByDivision["Jonokuchi"]).To(Equal(0))
69+
g.Expect(resp.AbsenceByDivision["Jonidan"]).To(Equal(0))
70+
g.Expect(resp.AbsenceByDivision["Sandanme"]).To(Equal(14))
71+
g.Expect(resp.AbsenceByDivision["Makushita"]).To(Equal(14))
72+
g.Expect(resp.AbsenceByDivision["Juryo"]).To(Equal(6))
73+
g.Expect(resp.AbsenceByDivision["Makuuchi"]).To(Equal(197))
74+
75+
// Verify total matches by division
76+
g.Expect(resp.TotalMatchesByDivision["Jonokuchi"]).To(Equal(7))
77+
g.Expect(resp.TotalMatchesByDivision["Jonidan"]).To(Equal(14))
78+
g.Expect(resp.TotalMatchesByDivision["Sandanme"]).To(Equal(14))
79+
g.Expect(resp.TotalMatchesByDivision["Makushita"]).To(Equal(91))
80+
g.Expect(resp.TotalMatchesByDivision["Juryo"]).To(Equal(99))
81+
g.Expect(resp.TotalMatchesByDivision["Makuuchi"]).To(Equal(573))
82+
}

0 commit comments

Comments
 (0)