From db32caacd8f6fc4a9d7f71afc40c8da509d18218 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Sun, 23 Nov 2025 12:45:40 +0000 Subject: [PATCH] Add GetRikishiAPI Signed-off-by: Matheus Pimenta --- client.go | 1 + get_rikishi.go | 36 +++ get_rikishi_test.go | 285 ++++++++++++++++++++ tests/integration/get_rikishi_test.go | 34 +++ tests/integration/list_rank_changes_test.go | 2 + 5 files changed, 358 insertions(+) create mode 100644 get_rikishi.go create mode 100644 get_rikishi_test.go create mode 100644 tests/integration/get_rikishi_test.go diff --git a/client.go b/client.go index 5c6e4d6..a6e4606 100644 --- a/client.go +++ b/client.go @@ -13,6 +13,7 @@ import ( // Client is a client for the Sumo API. type Client interface { SearchRikishisAPI + GetRikishiAPI ListRankChangesAPI ListShikonaChangesAPI ListMeasurementChangesAPI diff --git a/get_rikishi.go b/get_rikishi.go new file mode 100644 index 0000000..254477c --- /dev/null +++ b/get_rikishi.go @@ -0,0 +1,36 @@ +package sumoapi + +import ( + "context" + "fmt" + "net/url" +) + +// GetRikishiAPI defines the methods available for retrieving a single Rikishi. +type GetRikishiAPI interface { + // GetRikishi calls the GET /api/rikishi/{rikishiID} endpoint. + GetRikishi(ctx context.Context, req GetRikishiRequest) (*Rikishi, error) +} + +// GetRikishiRequest represents the request parameters for the GetRikishi method. +type GetRikishiRequest struct { + RikishiID int `json:"rikishiId" jsonschema:"The unique identifier of the Rikishi to retrieve. Example: 45 = Terunofuji"` + IncludeRanks bool `json:"includeRanks,omitempty" jsonschema:"Whether to include rank records over time in the Rikishi data."` + IncludeShikonas bool `json:"includeShikonas,omitempty" jsonschema:"Whether to include shikona (ring name) records over time in the Rikishi data."` + IncludeMeasurements bool `json:"includeMeasurements,omitempty" jsonschema:"Whether to include measurement records over time in the Rikishi data."` +} + +func (c *client) GetRikishi(ctx context.Context, req GetRikishiRequest) (*Rikishi, error) { + query := make(url.Values) + if req.IncludeMeasurements { + query.Set("measurements", "true") + } + if req.IncludeRanks { + query.Set("ranks", "true") + } + if req.IncludeShikonas { + query.Set("shikonas", "true") + } + path := fmt.Sprintf("/rikishi/%d", req.RikishiID) + return getObject[Rikishi](ctx, c, path, query) +} diff --git a/get_rikishi_test.go b/get_rikishi_test.go new file mode 100644 index 0000000..1108fe2 --- /dev/null +++ b/get_rikishi_test.go @@ -0,0 +1,285 @@ +package sumoapi_test + +import ( + "context" + "io" + "net/http" + "strings" + "testing" + + . "github.com/onsi/gomega" + + "github.com/sumo-mcp/sumoapi-go" +) + +func TestClient_GetRikishi(t *testing.T) { + t.Run("get rikishi by ID", func(t *testing.T) { + g := NewWithT(t) + + mockResp := `{ + "id": 45, + "shikonaEn": "Terunofuji", + "shikonaJp": "照ノ富士", + "currentRank": "Yokozuna", + "heya": "Isegahama" + }` + + 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/rikishi/45")) + g.Expect(req.URL.Query().Has("measurements")).To(BeFalse()) + g.Expect(req.URL.Query().Has("ranks")).To(BeFalse()) + g.Expect(req.URL.Query().Has("shikonas")).To(BeFalse()) + 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.GetRikishi(context.Background(), sumoapi.GetRikishiRequest{ + RikishiID: 45, + }) + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(resp).ToNot(BeNil()) + g.Expect(resp.ID).To(Equal(45)) + g.Expect(resp.ShikonaEnglish).To(Equal("Terunofuji")) + g.Expect(resp.ShikonaJapanese).To(Equal("照ノ富士")) + g.Expect(resp.CurrentRank).To(Equal("Yokozuna")) + g.Expect(resp.Heya).To(Equal("Isegahama")) + }) + + t.Run("get rikishi with include ranks", func(t *testing.T) { + g := NewWithT(t) + + mockResp := `{ + "id": 45, + "shikonaEn": "Terunofuji", + "rankHistory": [ + { + "id": "202501-45", + "bashoId": "202501", + "rikishiId": 45, + "rank": "Yokozuna 1 East" + } + ] + }` + + transport := &mockTransport{ + validateRequest: func(req *http.Request) error { + g.Expect(req.URL.Path).To(Equal("/api/rikishi/45")) + g.Expect(req.URL.Query().Get("ranks")).To(Equal("true")) + 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.GetRikishi(context.Background(), sumoapi.GetRikishiRequest{ + RikishiID: 45, + IncludeRanks: true, + }) + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(resp).ToNot(BeNil()) + g.Expect(resp.RankHistory).To(HaveLen(1)) + g.Expect(resp.RankHistory[0].RikishiID).To(Equal(45)) + }) + + t.Run("get rikishi with include shikonas", func(t *testing.T) { + g := NewWithT(t) + + mockResp := `{ + "id": 45, + "shikonaEn": "Terunofuji", + "shikonaHistory": [ + { + "id": "202501-45", + "bashoId": "202501", + "rikishiId": 45, + "shikonaEn": "Terunofuji", + "shikonaJp": "照ノ富士" + } + ] + }` + + transport := &mockTransport{ + validateRequest: func(req *http.Request) error { + g.Expect(req.URL.Path).To(Equal("/api/rikishi/45")) + g.Expect(req.URL.Query().Get("shikonas")).To(Equal("true")) + 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.GetRikishi(context.Background(), sumoapi.GetRikishiRequest{ + RikishiID: 45, + IncludeShikonas: true, + }) + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(resp).ToNot(BeNil()) + g.Expect(resp.ShikonaHistory).To(HaveLen(1)) + g.Expect(resp.ShikonaHistory[0].RikishiID).To(Equal(45)) + }) + + t.Run("get rikishi with include measurements", func(t *testing.T) { + g := NewWithT(t) + + mockResp := `{ + "id": 45, + "shikonaEn": "Terunofuji", + "measurementHistory": [ + { + "id": "202501-45", + "bashoId": "202501", + "rikishiId": 45, + "height": 193.0, + "weight": 180.0 + } + ] + }` + + transport := &mockTransport{ + validateRequest: func(req *http.Request) error { + g.Expect(req.URL.Path).To(Equal("/api/rikishi/45")) + g.Expect(req.URL.Query().Get("measurements")).To(Equal("true")) + 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.GetRikishi(context.Background(), sumoapi.GetRikishiRequest{ + RikishiID: 45, + IncludeMeasurements: true, + }) + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(resp).ToNot(BeNil()) + g.Expect(resp.MeasurementHistory).To(HaveLen(1)) + g.Expect(resp.MeasurementHistory[0].RikishiID).To(Equal(45)) + }) + + t.Run("get rikishi with all includes", func(t *testing.T) { + g := NewWithT(t) + + mockResp := `{ + "id": 45, + "shikonaEn": "Terunofuji", + "rankHistory": [], + "shikonaHistory": [], + "measurementHistory": [] + }` + + transport := &mockTransport{ + validateRequest: func(req *http.Request) error { + query := req.URL.Query() + g.Expect(query.Get("ranks")).To(Equal("true")) + g.Expect(query.Get("shikonas")).To(Equal("true")) + g.Expect(query.Get("measurements")).To(Equal("true")) + 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.GetRikishi(context.Background(), sumoapi.GetRikishiRequest{ + RikishiID: 45, + IncludeRanks: true, + IncludeShikonas: true, + IncludeMeasurements: true, + }) + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(resp).ToNot(BeNil()) + }) + + t.Run("context is propagated", func(t *testing.T) { + g := NewWithT(t) + + mockResp := `{ + "id": 45, + "shikonaEn": "Terunofuji" + }` + + 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.GetRikishi(ctx, sumoapi.GetRikishiRequest{ + RikishiID: 45, + }) + + 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.GetRikishi(context.Background(), sumoapi.GetRikishiRequest{ + RikishiID: 45, + }) + + 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.GetRikishi(context.Background(), sumoapi.GetRikishiRequest{ + RikishiID: 45, + }) + + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("error unmarshaling response body")) + g.Expect(resp).To(BeNil()) + }) +} diff --git a/tests/integration/get_rikishi_test.go b/tests/integration/get_rikishi_test.go new file mode 100644 index 0000000..3dacd59 --- /dev/null +++ b/tests/integration/get_rikishi_test.go @@ -0,0 +1,34 @@ +package integration_test + +import ( + "context" + "testing" + + . "github.com/onsi/gomega" + + "github.com/sumo-mcp/sumoapi-go" +) + +func TestIntegration_GetRikishi(t *testing.T) { + g := NewWithT(t) + + client := sumoapi.New() + + resp, err := client.GetRikishi(context.Background(), sumoapi.GetRikishiRequest{ + RikishiID: 45, + IncludeRanks: true, + IncludeShikonas: true, + IncludeMeasurements: true, + }) + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(resp).ToNot(BeNil()) + g.Expect(resp.ID).To(Equal(45)) + g.Expect(resp.ShikonaEnglish).To(Equal("Terunofuji Haruo")) + g.Expect(resp.ShikonaJapanese).To(Equal("照ノ富士 春雄")) + g.Expect(resp.Heya).To(Equal("Isegahama")) + + g.Expect(resp.RankHistory).ToNot(BeEmpty()) + g.Expect(resp.ShikonaHistory).ToNot(BeEmpty()) + g.Expect(resp.MeasurementHistory).ToNot(BeEmpty()) +} diff --git a/tests/integration/list_rank_changes_test.go b/tests/integration/list_rank_changes_test.go index 0e879af..58defd6 100644 --- a/tests/integration/list_rank_changes_test.go +++ b/tests/integration/list_rank_changes_test.go @@ -14,6 +14,8 @@ func TestIntegration_ListRankChanges(t *testing.T) { client := sumoapi.New() + // Here we test a specific Rikishi that had rank changes to make sure the API + // is returning exactly one change when filtering by RikishiID and BashoID. resp, err := client.ListRankChanges(context.Background(), sumoapi.ListRikishiChangesRequest{ RikishiID: 3081, BashoID: &sumoapi.BashoID{