Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions basho.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,28 @@ import (
"encoding/json"
"fmt"
"strconv"
"time"
)

// Basho represents a sumo tournament.
type Basho struct {
ID BashoID `json:"date" jsonschema:"The unique identifier for the basho (sumo tournament), in the format YYYYMM."`
StartDate *time.Time `json:"startDate,omitempty" jsonschema:"The starting date of the basho (sumo tournament)."`
EndDate *time.Time `json:"endDate,omitempty" jsonschema:"The ending date of the basho (sumo tournament)."`
Yusho []BashoPrize `json:"yusho,omitempty" jsonschema:"A list of yusho (tournament championship) prizes awarded to rikishi (sumo wrestlers) in the basho (sumo tournament)."`
SpecialPrizes []BashoPrize `json:"specialPrizes,omitempty" jsonschema:"A list of special prizes awarded to rikishi (sumo wrestlers) in the basho (sumo tournament)."`
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)."`
}

// BashoPrize represents a prize awarded to a rikishi in a basho.
// It can be a yusho or a special prize.
type BashoPrize struct {
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)."`
RikishiID int `json:"rikishiId" jsonschema:"The unique identifier of the rikishi (sumo wrestler) who received the prize."`
ShikonaEnglish string `json:"shikonaEn,omitempty" jsonschema:"The shikona (ring name) of the rikishi (sumo wrestler) in English."`
ShikonaJapanese string `json:"shikonaJp,omitempty" jsonschema:"The shikona (ring name) of the rikishi (sumo wrestler) in Japanese."`
}

// BashoID represents the unique identifier for a basho.
type BashoID struct {
Year int
Expand Down
2 changes: 2 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ type Client interface {
GetRikishiStatsAPI
ListRikishiMatchesAPI
ListRikishiMatchesAgainstOpponentAPI
GetBashoAPI
GetBashoWithTorikumiAPI
ListKimariteAPI
ListKimariteMatchesAPI
ListMeasurementChangesAPI
Expand Down
22 changes: 22 additions & 0 deletions get_basho.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package sumoapi

import (
"context"
"fmt"
)

// GetBashoAPI defines the methods available for retrieving a basho.
type GetBashoAPI interface {
// GetBasho calls the GET /api/basho/{bashoID} endpoint.
GetBasho(ctx context.Context, req GetBashoRequest) (*Basho, error)
}

// GetBashoRequest represents the request parameters for the GetBasho method.
type GetBashoRequest struct {
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."`
}

func (c *client) GetBasho(ctx context.Context, req GetBashoRequest) (*Basho, error) {
path := fmt.Sprintf("/basho/%s", req.BashoID.String())
return getObject[Basho](ctx, c, path, nil)
}
219 changes: 219 additions & 0 deletions get_basho_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package sumoapi_test

import (
"context"
"io"
"net/http"
"strings"
"testing"

. "github.com/onsi/gomega"

"github.com/sumo-mcp/sumoapi-go"
)

func TestClient_GetBasho(t *testing.T) {
t.Run("get basho by ID", func(t *testing.T) {
g := NewWithT(t)

mockResp := `{
"date": "202501",
"startDate": "2025-01-12T00:00:00Z",
"endDate": "2025-01-26T00:00:00Z",
"yusho": [
{
"type": "Makuuchi",
"rikishiId": 45,
"shikonaEn": "Terunofuji",
"shikonaJp": "照ノ富士"
}
],
"specialPrizes": [
{
"type": "Shukun-sho",
"rikishiId": 123,
"shikonaEn": "Takakeisho",
"shikonaJp": "貴景勝"
}
]
}`

transport := &mockTransport{
validateRequest: func(req *http.Request) error {
g.Expect(req.Method).To(Equal(http.MethodGet))
g.Expect(req.URL.Scheme).To(Equal("https"))
g.Expect(req.URL.Host).To(Equal("sumo-api.com"))
g.Expect(req.URL.Path).To(Equal("/api/basho/202501"))
g.Expect(req.URL.Query()).To(BeEmpty())
return nil
},
response: &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader(mockResp)),
},
}

client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
resp, err := client.GetBasho(context.Background(), sumoapi.GetBashoRequest{
BashoID: sumoapi.BashoID{Year: 2025, Month: 1},
})

g.Expect(err).ToNot(HaveOccurred())
g.Expect(resp).ToNot(BeNil())
g.Expect(resp.ID).To(Equal(sumoapi.BashoID{Year: 2025, Month: 1}))
g.Expect(resp.StartDate).ToNot(BeNil())
g.Expect(resp.EndDate).ToNot(BeNil())
g.Expect(resp.Yusho).To(HaveLen(1))
g.Expect(resp.Yusho[0].Type).To(Equal("Makuuchi"))
g.Expect(resp.Yusho[0].RikishiID).To(Equal(45))
g.Expect(resp.Yusho[0].ShikonaEnglish).To(Equal("Terunofuji"))
g.Expect(resp.SpecialPrizes).To(HaveLen(1))
g.Expect(resp.SpecialPrizes[0].Type).To(Equal("Shukun-sho"))
g.Expect(resp.SpecialPrizes[0].RikishiID).To(Equal(123))
})

t.Run("get basho with minimal data", func(t *testing.T) {
g := NewWithT(t)

mockResp := `{
"date": "199903"
}`

transport := &mockTransport{
validateRequest: func(req *http.Request) error {
g.Expect(req.URL.Path).To(Equal("/api/basho/199903"))
return nil
},
response: &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader(mockResp)),
},
}

client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
resp, err := client.GetBasho(context.Background(), sumoapi.GetBashoRequest{
BashoID: sumoapi.BashoID{Year: 1999, Month: 3},
})

g.Expect(err).ToNot(HaveOccurred())
g.Expect(resp).ToNot(BeNil())
g.Expect(resp.ID).To(Equal(sumoapi.BashoID{Year: 1999, Month: 3}))
g.Expect(resp.StartDate).To(BeNil())
g.Expect(resp.EndDate).To(BeNil())
g.Expect(resp.Yusho).To(BeEmpty())
g.Expect(resp.SpecialPrizes).To(BeEmpty())
g.Expect(resp.Torikumi).To(BeEmpty())
})

t.Run("get basho with torikumi", func(t *testing.T) {
g := NewWithT(t)

mockResp := `{
"date": "202501",
"torikumi": [
{
"bashoId": "202501",
"division": "Makuuchi",
"day": 1,
"matchNo": 1,
"eastId": 45,
"eastShikona": "Terunofuji",
"eastRank": "Yokozuna 1 East",
"westId": 123,
"westShikona": "Takakeisho",
"westRank": "Ozeki 1 West",
"kimarite": "Yorikiri",
"winnerId": 45,
"winnerEn": "Terunofuji"
}
]
}`

transport := &mockTransport{
response: &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader(mockResp)),
},
}

client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
resp, err := client.GetBasho(context.Background(), sumoapi.GetBashoRequest{
BashoID: sumoapi.BashoID{Year: 2025, Month: 1},
})

g.Expect(err).ToNot(HaveOccurred())
g.Expect(resp).ToNot(BeNil())
g.Expect(resp.Torikumi).To(HaveLen(1))
g.Expect(resp.Torikumi[0].BashoID).To(Equal(sumoapi.BashoID{Year: 2025, Month: 1}))
g.Expect(resp.Torikumi[0].Division).To(Equal("Makuuchi"))
g.Expect(resp.Torikumi[0].Day).To(Equal(1))
})

t.Run("context is propagated", func(t *testing.T) {
g := NewWithT(t)

mockResp := `{
"date": "202501"
}`

type testKey struct{}
ctx := context.WithValue(context.Background(), testKey{}, "test-value")

transport := &mockTransport{
validateRequest: func(req *http.Request) error {
g.Expect(req.Context().Value(testKey{})).To(Equal("test-value"))
return nil
},
response: &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader(mockResp)),
},
}

client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
_, err := client.GetBasho(ctx, sumoapi.GetBashoRequest{
BashoID: sumoapi.BashoID{Year: 2025, Month: 1},
})

g.Expect(err).ToNot(HaveOccurred())
})

t.Run("http request error", func(t *testing.T) {
g := NewWithT(t)

transport := &mockTransport{
validateRequest: func(req *http.Request) error {
return http.ErrAbortHandler
},
}

client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
resp, err := client.GetBasho(context.Background(), sumoapi.GetBashoRequest{
BashoID: sumoapi.BashoID{Year: 2025, Month: 1},
})

g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(ContainSubstring("error making http request"))
g.Expect(resp).To(BeNil())
})

t.Run("invalid JSON response", func(t *testing.T) {
g := NewWithT(t)

transport := &mockTransport{
response: &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader("not valid json")),
},
}

client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
resp, err := client.GetBasho(context.Background(), sumoapi.GetBashoRequest{
BashoID: sumoapi.BashoID{Year: 2025, Month: 1},
})

g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(ContainSubstring("error unmarshaling response body"))
g.Expect(resp).To(BeNil())
})
}
27 changes: 27 additions & 0 deletions get_basho_with_torikumi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package sumoapi

import (
"context"
"fmt"
)

// GetBashoWithTorikumiAPI defines the methods available for retrieving a basho.
type GetBashoWithTorikumiAPI interface {
// GetBashoWithTorikumi calls the GET /api/basho/{bashoID}/torikumi/{division}/{day} endpoint.
//
// Documented bugs:
// - 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.
GetBashoWithTorikumi(ctx context.Context, req GetBashoWithTorikumiRequest) (*Basho, error)
}

// GetBashoWithTorikumiRequest represents the request parameters for the GetBashoWithTorikumi method.
type GetBashoWithTorikumiRequest struct {
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."`
Division string `json:"division" jsonschema:"The basho (sumo tournament) division to retrieve matches for. Valid values are Makuuchi, Juryo, Makushita, Sandanme, Jonidan, Jonokuchi."`
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."`
}

func (c *client) GetBashoWithTorikumi(ctx context.Context, req GetBashoWithTorikumiRequest) (*Basho, error) {
path := fmt.Sprintf("/basho/%s/torikumi/%s/%d", req.BashoID.String(), req.Division, req.Day)
return getObject[Basho](ctx, c, path, nil)
}
Loading