Skip to content

Commit 22c51bc

Browse files
authored
Merge pull request #10 from sumo-mcp/workspace
Add GetBashoAPI and GetBashoWithTorikumiAPI
2 parents 29cf74a + 150d6db commit 22c51bc

8 files changed

Lines changed: 662 additions & 0 deletions

basho.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,28 @@ import (
44
"encoding/json"
55
"fmt"
66
"strconv"
7+
"time"
78
)
89

10+
// Basho represents a sumo tournament.
11+
type Basho struct {
12+
ID BashoID `json:"date" jsonschema:"The unique identifier for the basho (sumo tournament), in the format YYYYMM."`
13+
StartDate *time.Time `json:"startDate,omitempty" jsonschema:"The starting date of the basho (sumo tournament)."`
14+
EndDate *time.Time `json:"endDate,omitempty" jsonschema:"The ending date of the basho (sumo tournament)."`
15+
Yusho []BashoPrize `json:"yusho,omitempty" jsonschema:"A list of yusho (tournament championship) prizes awarded to rikishi (sumo wrestlers) in the basho (sumo tournament)."`
16+
SpecialPrizes []BashoPrize `json:"specialPrizes,omitempty" jsonschema:"A list of special prizes awarded to rikishi (sumo wrestlers) in the basho (sumo tournament)."`
17+
Torikumi []Match `json:"torikumi,omitempty" jsonschema:"A torikumi (bout schedule) that took or will take place for a specific day of a specific division of the basho (sumo tournament)."`
18+
}
19+
20+
// BashoPrize represents a prize awarded to a rikishi in a basho.
21+
// It can be a yusho or a special prize.
22+
type BashoPrize struct {
23+
Type string `json:"type" jsonschema:"The type of prize. When the prize is a yusho (tournament championship), the value is the name of the tournament division. When the prize is a special prize, the value is one of 'Shukun-sho' (outstanding performance), 'Kanto-sho' (fighting spirit), or 'Gino-sho' (technique)."`
24+
RikishiID int `json:"rikishiId" jsonschema:"The unique identifier of the rikishi (sumo wrestler) who received the prize."`
25+
ShikonaEnglish string `json:"shikonaEn,omitempty" jsonschema:"The shikona (ring name) of the rikishi (sumo wrestler) in English."`
26+
ShikonaJapanese string `json:"shikonaJp,omitempty" jsonschema:"The shikona (ring name) of the rikishi (sumo wrestler) in Japanese."`
27+
}
28+
929
// BashoID represents the unique identifier for a basho.
1030
type BashoID struct {
1131
Year int

client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ type Client interface {
1717
GetRikishiStatsAPI
1818
ListRikishiMatchesAPI
1919
ListRikishiMatchesAgainstOpponentAPI
20+
GetBashoAPI
21+
GetBashoWithTorikumiAPI
2022
ListKimariteAPI
2123
ListKimariteMatchesAPI
2224
ListMeasurementChangesAPI

get_basho.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package sumoapi
2+
3+
import (
4+
"context"
5+
"fmt"
6+
)
7+
8+
// GetBashoAPI defines the methods available for retrieving a basho.
9+
type GetBashoAPI interface {
10+
// GetBasho calls the GET /api/basho/{bashoID} endpoint.
11+
GetBasho(ctx context.Context, req GetBashoRequest) (*Basho, error)
12+
}
13+
14+
// GetBashoRequest represents the request parameters for the GetBasho method.
15+
type GetBashoRequest struct {
16+
BashoID BashoID `json:"bashoId" jsonschema:"The unique identifier of the basho (sumo tournament) to retrieve. Format: YYYYMM, e.g., 202401 for the January 2024 basho."`
17+
}
18+
19+
func (c *client) GetBasho(ctx context.Context, req GetBashoRequest) (*Basho, error) {
20+
path := fmt.Sprintf("/basho/%s", req.BashoID.String())
21+
return getObject[Basho](ctx, c, path, nil)
22+
}

get_basho_test.go

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
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_GetBasho(t *testing.T) {
16+
t.Run("get basho by ID", func(t *testing.T) {
17+
g := NewWithT(t)
18+
19+
mockResp := `{
20+
"date": "202501",
21+
"startDate": "2025-01-12T00:00:00Z",
22+
"endDate": "2025-01-26T00:00:00Z",
23+
"yusho": [
24+
{
25+
"type": "Makuuchi",
26+
"rikishiId": 45,
27+
"shikonaEn": "Terunofuji",
28+
"shikonaJp": "照ノ富士"
29+
}
30+
],
31+
"specialPrizes": [
32+
{
33+
"type": "Shukun-sho",
34+
"rikishiId": 123,
35+
"shikonaEn": "Takakeisho",
36+
"shikonaJp": "貴景勝"
37+
}
38+
]
39+
}`
40+
41+
transport := &mockTransport{
42+
validateRequest: func(req *http.Request) error {
43+
g.Expect(req.Method).To(Equal(http.MethodGet))
44+
g.Expect(req.URL.Scheme).To(Equal("https"))
45+
g.Expect(req.URL.Host).To(Equal("sumo-api.com"))
46+
g.Expect(req.URL.Path).To(Equal("/api/basho/202501"))
47+
g.Expect(req.URL.Query()).To(BeEmpty())
48+
return nil
49+
},
50+
response: &http.Response{
51+
StatusCode: http.StatusOK,
52+
Body: io.NopCloser(strings.NewReader(mockResp)),
53+
},
54+
}
55+
56+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
57+
resp, err := client.GetBasho(context.Background(), sumoapi.GetBashoRequest{
58+
BashoID: sumoapi.BashoID{Year: 2025, Month: 1},
59+
})
60+
61+
g.Expect(err).ToNot(HaveOccurred())
62+
g.Expect(resp).ToNot(BeNil())
63+
g.Expect(resp.ID).To(Equal(sumoapi.BashoID{Year: 2025, Month: 1}))
64+
g.Expect(resp.StartDate).ToNot(BeNil())
65+
g.Expect(resp.EndDate).ToNot(BeNil())
66+
g.Expect(resp.Yusho).To(HaveLen(1))
67+
g.Expect(resp.Yusho[0].Type).To(Equal("Makuuchi"))
68+
g.Expect(resp.Yusho[0].RikishiID).To(Equal(45))
69+
g.Expect(resp.Yusho[0].ShikonaEnglish).To(Equal("Terunofuji"))
70+
g.Expect(resp.SpecialPrizes).To(HaveLen(1))
71+
g.Expect(resp.SpecialPrizes[0].Type).To(Equal("Shukun-sho"))
72+
g.Expect(resp.SpecialPrizes[0].RikishiID).To(Equal(123))
73+
})
74+
75+
t.Run("get basho with minimal data", func(t *testing.T) {
76+
g := NewWithT(t)
77+
78+
mockResp := `{
79+
"date": "199903"
80+
}`
81+
82+
transport := &mockTransport{
83+
validateRequest: func(req *http.Request) error {
84+
g.Expect(req.URL.Path).To(Equal("/api/basho/199903"))
85+
return nil
86+
},
87+
response: &http.Response{
88+
StatusCode: http.StatusOK,
89+
Body: io.NopCloser(strings.NewReader(mockResp)),
90+
},
91+
}
92+
93+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
94+
resp, err := client.GetBasho(context.Background(), sumoapi.GetBashoRequest{
95+
BashoID: sumoapi.BashoID{Year: 1999, Month: 3},
96+
})
97+
98+
g.Expect(err).ToNot(HaveOccurred())
99+
g.Expect(resp).ToNot(BeNil())
100+
g.Expect(resp.ID).To(Equal(sumoapi.BashoID{Year: 1999, Month: 3}))
101+
g.Expect(resp.StartDate).To(BeNil())
102+
g.Expect(resp.EndDate).To(BeNil())
103+
g.Expect(resp.Yusho).To(BeEmpty())
104+
g.Expect(resp.SpecialPrizes).To(BeEmpty())
105+
g.Expect(resp.Torikumi).To(BeEmpty())
106+
})
107+
108+
t.Run("get basho with torikumi", func(t *testing.T) {
109+
g := NewWithT(t)
110+
111+
mockResp := `{
112+
"date": "202501",
113+
"torikumi": [
114+
{
115+
"bashoId": "202501",
116+
"division": "Makuuchi",
117+
"day": 1,
118+
"matchNo": 1,
119+
"eastId": 45,
120+
"eastShikona": "Terunofuji",
121+
"eastRank": "Yokozuna 1 East",
122+
"westId": 123,
123+
"westShikona": "Takakeisho",
124+
"westRank": "Ozeki 1 West",
125+
"kimarite": "Yorikiri",
126+
"winnerId": 45,
127+
"winnerEn": "Terunofuji"
128+
}
129+
]
130+
}`
131+
132+
transport := &mockTransport{
133+
response: &http.Response{
134+
StatusCode: http.StatusOK,
135+
Body: io.NopCloser(strings.NewReader(mockResp)),
136+
},
137+
}
138+
139+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
140+
resp, err := client.GetBasho(context.Background(), sumoapi.GetBashoRequest{
141+
BashoID: sumoapi.BashoID{Year: 2025, Month: 1},
142+
})
143+
144+
g.Expect(err).ToNot(HaveOccurred())
145+
g.Expect(resp).ToNot(BeNil())
146+
g.Expect(resp.Torikumi).To(HaveLen(1))
147+
g.Expect(resp.Torikumi[0].BashoID).To(Equal(sumoapi.BashoID{Year: 2025, Month: 1}))
148+
g.Expect(resp.Torikumi[0].Division).To(Equal("Makuuchi"))
149+
g.Expect(resp.Torikumi[0].Day).To(Equal(1))
150+
})
151+
152+
t.Run("context is propagated", func(t *testing.T) {
153+
g := NewWithT(t)
154+
155+
mockResp := `{
156+
"date": "202501"
157+
}`
158+
159+
type testKey struct{}
160+
ctx := context.WithValue(context.Background(), testKey{}, "test-value")
161+
162+
transport := &mockTransport{
163+
validateRequest: func(req *http.Request) error {
164+
g.Expect(req.Context().Value(testKey{})).To(Equal("test-value"))
165+
return nil
166+
},
167+
response: &http.Response{
168+
StatusCode: http.StatusOK,
169+
Body: io.NopCloser(strings.NewReader(mockResp)),
170+
},
171+
}
172+
173+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
174+
_, err := client.GetBasho(ctx, sumoapi.GetBashoRequest{
175+
BashoID: sumoapi.BashoID{Year: 2025, Month: 1},
176+
})
177+
178+
g.Expect(err).ToNot(HaveOccurred())
179+
})
180+
181+
t.Run("http request error", func(t *testing.T) {
182+
g := NewWithT(t)
183+
184+
transport := &mockTransport{
185+
validateRequest: func(req *http.Request) error {
186+
return http.ErrAbortHandler
187+
},
188+
}
189+
190+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
191+
resp, err := client.GetBasho(context.Background(), sumoapi.GetBashoRequest{
192+
BashoID: sumoapi.BashoID{Year: 2025, Month: 1},
193+
})
194+
195+
g.Expect(err).To(HaveOccurred())
196+
g.Expect(err.Error()).To(ContainSubstring("error making http request"))
197+
g.Expect(resp).To(BeNil())
198+
})
199+
200+
t.Run("invalid JSON response", func(t *testing.T) {
201+
g := NewWithT(t)
202+
203+
transport := &mockTransport{
204+
response: &http.Response{
205+
StatusCode: http.StatusOK,
206+
Body: io.NopCloser(strings.NewReader("not valid json")),
207+
},
208+
}
209+
210+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
211+
resp, err := client.GetBasho(context.Background(), sumoapi.GetBashoRequest{
212+
BashoID: sumoapi.BashoID{Year: 2025, Month: 1},
213+
})
214+
215+
g.Expect(err).To(HaveOccurred())
216+
g.Expect(err.Error()).To(ContainSubstring("error unmarshaling response body"))
217+
g.Expect(resp).To(BeNil())
218+
})
219+
}

get_basho_with_torikumi.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package sumoapi
2+
3+
import (
4+
"context"
5+
"fmt"
6+
)
7+
8+
// GetBashoWithTorikumiAPI defines the methods available for retrieving a basho.
9+
type GetBashoWithTorikumiAPI interface {
10+
// GetBashoWithTorikumi calls the GET /api/basho/{bashoID}/torikumi/{division}/{day} endpoint.
11+
//
12+
// Documented bugs:
13+
// - There's a skew in match numbering: the number of the match in the match ID starts at 0, while the matchNo field starts at 1.
14+
GetBashoWithTorikumi(ctx context.Context, req GetBashoWithTorikumiRequest) (*Basho, error)
15+
}
16+
17+
// GetBashoWithTorikumiRequest represents the request parameters for the GetBashoWithTorikumi method.
18+
type GetBashoWithTorikumiRequest struct {
19+
BashoID BashoID `json:"bashoId" jsonschema:"The unique identifier of the basho (sumo tournament) to retrieve. Format: YYYYMM, e.g., 202401 for the January 2024 basho."`
20+
Division string `json:"division" jsonschema:"The basho (sumo tournament) division to retrieve matches for. Valid values are Makuuchi, Juryo, Makushita, Sandanme, Jonidan, Jonokuchi."`
21+
Day int `json:"day" jsonschema:"The day of the basho (sumo tournament) to retrieve matches for. Values from 1 to 15 represent days, and 16 and above represent individual playoff matches."`
22+
}
23+
24+
func (c *client) GetBashoWithTorikumi(ctx context.Context, req GetBashoWithTorikumiRequest) (*Basho, error) {
25+
path := fmt.Sprintf("/basho/%s/torikumi/%s/%d", req.BashoID.String(), req.Division, req.Day)
26+
return getObject[Basho](ctx, c, path, nil)
27+
}

0 commit comments

Comments
 (0)