From bc158ec30307e728f8ee11df4eba6c6cb5692abc Mon Sep 17 00:00:00 2001 From: Oleg Balunenko Date: Fri, 5 Jun 2026 05:06:19 +0400 Subject: [PATCH 1/3] feat: Update routes client to use athlete ID path parameter The GetRoutesByAthleteId method now explicitly takes the athlete ID as a direct argument, reflecting an API change where the ID is expected as a path parameter. This update ensures the client aligns with the latest API specification for this endpoint. Also includes a minor adjustment to parameter setting in CreateActivity for consistency. --- client/activities.go | 2 +- client/routes.go | 5 +- docs/swagger.json | 10 +++- examples/client_example_test.go | 46 ++++++++++++------- .../get_routes_by_athlete_id_parameters.go | 24 ++++++++++ 5 files changed, 67 insertions(+), 20 deletions(-) diff --git a/client/activities.go b/client/activities.go index 48912ef..6a65b08 100644 --- a/client/activities.go +++ b/client/activities.go @@ -88,7 +88,7 @@ func (a activitiesService) CreateActivity(ctx context.Context, name string, acti } if sportType != "" { - params.SetType(&sportType) + params.SetSportType(sportType) } params.SetStartDateLocal(strfmt.DateTime(startDateLocal)) diff --git a/client/routes.go b/client/routes.go index 67a0a01..d17e4b8 100644 --- a/client/routes.go +++ b/client/routes.go @@ -19,7 +19,7 @@ type RoutesAPI interface { // GetRouteById returns a route with given id GetRouteById(ctx context.Context, id int64) (models.Route, error) // GetRoutesByAthleteId returns a list of routes for the given athlete - GetRoutesByAthleteId(ctx context.Context, opts ...GetRoutesByAthleteIdOpts) ([]models.Route, error) + GetRoutesByAthleteId(ctx context.Context, id int64, opts ...GetRoutesByAthleteIdOpts) ([]models.Route, error) } type GetRoutesByAthleteIdOpts struct { @@ -84,11 +84,12 @@ func (r routesService) GetRouteById(ctx context.Context, id int64) (models.Route return convertToModelsRoute(route.GetPayload()), nil } -func (r routesService) GetRoutesByAthleteId(ctx context.Context, opts ...GetRoutesByAthleteIdOpts) ([]models.Route, error) { +func (r routesService) GetRoutesByAthleteId(ctx context.Context, id int64, opts ...GetRoutesByAthleteIdOpts) ([]models.Route, error) { params := routes.NewGetRoutesByAthleteIDParams() params.SetDefaults() params.SetContext(ctx) + params.SetID(id) for _, opt := range opts { if opt.Page != nil { diff --git a/docs/swagger.json b/docs/swagger.json index de8b4b5..3bb131f 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1461,6 +1461,14 @@ "summary": "List Athlete Routes", "operationId": "getRoutesByAthleteId", "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "The identifier of the athlete.", + "name": "id", + "in": "path", + "required": true + }, { "type": "integer", "description": "Page number. Defaults to 1.", @@ -5407,4 +5415,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/examples/client_example_test.go b/examples/client_example_test.go index 8028d33..5e2cd69 100644 --- a/examples/client_example_test.go +++ b/examples/client_example_test.go @@ -1,7 +1,6 @@ package examples import ( - "context" "encoding/json" "testing" @@ -29,15 +28,21 @@ func getToken(t testing.TB) string { return token } -func TestGetLoggedInAthlete(t *testing.T) { +func makeClient(t testing.TB) *client.APIClient { + t.Helper() + token := getToken(t) apiClient, err := client.NewAPIClient(token) require.NoError(t, err) - ctx := context.Background() + return apiClient +} - athlete, err := apiClient.Athletes.GetLoggedInAthlete(ctx) +func TestGetLoggedInAthlete(t *testing.T) { + apiClient := makeClient(t) + + athlete, err := apiClient.Athletes.GetLoggedInAthlete(t.Context()) require.NoError(t, err) // Indent athlete @@ -46,14 +51,9 @@ func TestGetLoggedInAthlete(t *testing.T) { // Test Activites API func TestGetLoggedInAthleteActivities(t *testing.T) { - token := getToken(t) + apiClient := makeClient(t) - apiClient, err := client.NewAPIClient(token) - require.NoError(t, err) - - ctx := context.Background() - - activities, err := apiClient.Activities.GetLoggedInAthleteActivities(ctx) + activities, err := apiClient.Activities.GetLoggedInAthleteActivities(t.Context()) require.NoError(t, err) // Indent activities @@ -62,12 +62,9 @@ func TestGetLoggedInAthleteActivities(t *testing.T) { // Test Gear API func TestGetLoggedInAthleteGear(t *testing.T) { - token := getToken(t) + apiClient := makeClient(t) - apiClient, err := client.NewAPIClient(token) - require.NoError(t, err) - - ctx := context.Background() + ctx := t.Context() athlete, err := apiClient.Athletes.GetLoggedInAthlete(ctx) require.NoError(t, err) @@ -80,3 +77,20 @@ func TestGetLoggedInAthleteGear(t *testing.T) { // Indent gear printJSON(t, gear) } + +func TestGetRoutesByAthleteID(t *testing.T) { + apiClient := makeClient(t) + + ctx := t.Context() + + athlete, err := apiClient.Athletes.GetLoggedInAthlete(ctx) + require.NoError(t, err) + + id := athlete.ID + require.NotZero(t, id) + + routes, err := apiClient.Routes.GetRoutesByAthleteId(ctx, id) + require.NoError(t, err) + + printJSON(t, routes) +} diff --git a/internal/gen/strava-api-go/client/routes/get_routes_by_athlete_id_parameters.go b/internal/gen/strava-api-go/client/routes/get_routes_by_athlete_id_parameters.go index 23157ed..f5d8d8d 100644 --- a/internal/gen/strava-api-go/client/routes/get_routes_by_athlete_id_parameters.go +++ b/internal/gen/strava-api-go/client/routes/get_routes_by_athlete_id_parameters.go @@ -59,6 +59,14 @@ GetRoutesByAthleteIDParams contains all the parameters to send to the API endpoi */ type GetRoutesByAthleteIDParams struct { + /* ID. + + The identifier of the athlete. + + Format: int64 + */ + ID int64 + /* Page. Page number. Defaults to 1. @@ -137,6 +145,17 @@ func (o *GetRoutesByAthleteIDParams) SetHTTPClient(client *http.Client) { o.HTTPClient = client } +// WithID adds the id to the get routes by athlete Id params +func (o *GetRoutesByAthleteIDParams) WithID(id int64) *GetRoutesByAthleteIDParams { + o.SetID(id) + return o +} + +// SetID adds the id to the get routes by athlete Id params +func (o *GetRoutesByAthleteIDParams) SetID(id int64) { + o.ID = id +} + // WithPage adds the page to the get routes by athlete Id params func (o *GetRoutesByAthleteIDParams) WithPage(page *int64) *GetRoutesByAthleteIDParams { o.SetPage(page) @@ -167,6 +186,11 @@ func (o *GetRoutesByAthleteIDParams) WriteToRequest(r runtime.ClientRequest, reg } var res []error + // path param id + if err := r.SetPathParam("id", swag.FormatInt64(o.ID)); err != nil { + return err + } + if o.Page != nil { // query param page From f586c30614827ccaf6ee84916238378d65b5d5bc Mon Sep 17 00:00:00 2001 From: Oleg Balunenko Date: Fri, 5 Jun 2026 05:18:24 +0400 Subject: [PATCH 2/3] feat: Enable GPX and TCX route file export The Strava API endpoints for exporting routes as GPX and TCX files return raw file data. This change enables the client to correctly retrieve these files by: - Updating the OpenAPI specification to properly define the file response types. - Registering byte stream consumers for `application/gpx+xml`, `application/tcx+xml`, and other XML/text MIME types. - Modifying the client methods to return the file content as `[]byte` and providing options to explicitly set the `Accept` header. This ensures that users can successfully download route export files --- client/client.go | 18 +++++- client/routes.go | 25 ++++---- docs/swagger.json | 21 ++++++- examples/client_example_test.go | 31 ++++++++++ .../routes/get_route_as_g_p_x_responses.go | 20 +++++- .../routes/get_route_as_t_c_x_responses.go | 20 +++++- .../client/routes/routes_client.go | 62 ++++++++++++++++--- 7 files changed, 168 insertions(+), 29 deletions(-) diff --git a/client/client.go b/client/client.go index ec09726..36989d6 100644 --- a/client/client.go +++ b/client/client.go @@ -3,6 +3,7 @@ package client import ( + openapiruntime "github.com/go-openapi/runtime" httptransport "github.com/go-openapi/runtime/client" apiclient "github.com/obalunenko/strava-api/internal/gen/strava-api-go/client" @@ -24,7 +25,8 @@ type APIClient struct { // NewAPIClient creates a new APIClient. Requires a Strava API token. func NewAPIClient(token string) (*APIClient, error) { - client := apiclient.Default + client := apiclient.NewHTTPClient(nil) + registerRouteExportConsumers(client.Transport) // Set the bearer token for auth. auth := httptransport.BearerToken(token) @@ -41,3 +43,17 @@ func NewAPIClient(token string) (*APIClient, error) { Uploads: newUploadsApiService(client, auth), }, nil } + +func registerRouteExportConsumers(transport openapiruntime.ClientTransport) { + runtimeTransport, ok := transport.(*httptransport.Runtime) + if !ok { + return + } + + consumer := openapiruntime.ByteStreamConsumer() + runtimeTransport.Consumers["application/gpx+xml"] = consumer + runtimeTransport.Consumers["application/tcx+xml"] = consumer + runtimeTransport.Consumers["application/vnd.garmin.tcx+xml"] = consumer + runtimeTransport.Consumers[openapiruntime.XMLMime] = consumer + runtimeTransport.Consumers[openapiruntime.TextMime] = consumer +} diff --git a/client/routes.go b/client/routes.go index d17e4b8..2e5367d 100644 --- a/client/routes.go +++ b/client/routes.go @@ -1,6 +1,7 @@ package client import ( + "bytes" "context" "github.com/go-openapi/runtime" @@ -13,9 +14,9 @@ import ( // RoutesAPI is an interface for interacting with routes endpoints of Strava API type RoutesAPI interface { // GetRouteAsGPX returns a GPX file of the route - GetRouteAsGPX(ctx context.Context, id int64) error + GetRouteAsGPX(ctx context.Context, id int64) ([]byte, error) // GetRouteAsTCX returns a TCX file of the route - GetRouteAsTCX(ctx context.Context, id int64) error + GetRouteAsTCX(ctx context.Context, id int64) ([]byte, error) // GetRouteById returns a route with given id GetRouteById(ctx context.Context, id int64) (models.Route, error) // GetRoutesByAthleteId returns a list of routes for the given athlete @@ -39,34 +40,34 @@ type routesService struct { auth runtime.ClientAuthInfoWriter } -func (r routesService) GetRouteAsGPX(ctx context.Context, id int64) error { +func (r routesService) GetRouteAsGPX(ctx context.Context, id int64) ([]byte, error) { params := routes.NewGetRouteAsGPXParams() params.SetDefaults() params.SetContext(ctx) params.SetID(id) - _, err := r.client.Routes.GetRouteAsGPX(params, r.auth) - if err != nil { - return err + var buf bytes.Buffer + if _, err := r.client.Routes.GetRouteAsGPX(params, r.auth, &buf, routes.WithAcceptApplicationGpxXML); err != nil { + return nil, err } - return nil + return buf.Bytes(), nil } -func (r routesService) GetRouteAsTCX(ctx context.Context, id int64) error { +func (r routesService) GetRouteAsTCX(ctx context.Context, id int64) ([]byte, error) { params := routes.NewGetRouteAsTCXParams() params.SetDefaults() params.SetContext(ctx) params.SetID(id) - _, err := r.client.Routes.GetRouteAsTCX(params, r.auth) - if err != nil { - return err + var buf bytes.Buffer + if _, err := r.client.Routes.GetRouteAsTCX(params, r.auth, &buf, routes.WithAcceptApplicationVndGarminTcxXML); err != nil { + return nil, err } - return nil + return buf.Bytes(), nil } func (r routesService) GetRouteById(ctx context.Context, id int64) (models.Route, error) { diff --git a/docs/swagger.json b/docs/swagger.json index 3bb131f..27c3fc6 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1883,6 +1883,11 @@ ], "summary": "Export Route GPX", "operationId": "getRouteAsGPX", + "produces": [ + "application/gpx+xml", + "application/xml", + "text/xml" + ], "parameters": [ { "type": "integer", @@ -1895,7 +1900,10 @@ ], "responses": { "200": { - "description": "A GPX file with the route." + "description": "A GPX file with the route.", + "schema": { + "type": "file" + } }, "default": { "description": "Unexpected error.", @@ -1914,6 +1922,12 @@ ], "summary": "Export Route TCX", "operationId": "getRouteAsTCX", + "produces": [ + "application/tcx+xml", + "application/vnd.garmin.tcx+xml", + "application/xml", + "text/xml" + ], "parameters": [ { "type": "integer", @@ -1926,7 +1940,10 @@ ], "responses": { "200": { - "description": "A TCX file with the route." + "description": "A TCX file with the route.", + "schema": { + "type": "file" + } }, "default": { "description": "Unexpected error.", diff --git a/examples/client_example_test.go b/examples/client_example_test.go index 5e2cd69..3a18c93 100644 --- a/examples/client_example_test.go +++ b/examples/client_example_test.go @@ -94,3 +94,34 @@ func TestGetRoutesByAthleteID(t *testing.T) { printJSON(t, routes) } + +func TestExportFirstRouteAsGPXAndTCX(t *testing.T) { + apiClient := makeClient(t) + + ctx := t.Context() + + athlete, err := apiClient.Athletes.GetLoggedInAthlete(ctx) + require.NoError(t, err) + + id := athlete.ID + require.NotZero(t, id) + + routes, err := apiClient.Routes.GetRoutesByAthleteId(ctx, id) + require.NoError(t, err) + if len(routes) == 0 { + t.Skip("authenticated athlete has no routes") + } + + routeID := routes[0].ID + require.NotZero(t, routeID) + + gpx, err := apiClient.Routes.GetRouteAsGPX(ctx, routeID) + require.NoError(t, err) + require.NotEmpty(t, gpx) + + tcx, err := apiClient.Routes.GetRouteAsTCX(ctx, routeID) + require.NoError(t, err) + require.NotEmpty(t, tcx) + + t.Logf("exported route %d: GPX=%d bytes TCX=%d bytes", routeID, len(gpx), len(tcx)) +} diff --git a/internal/gen/strava-api-go/client/routes/get_route_as_g_p_x_responses.go b/internal/gen/strava-api-go/client/routes/get_route_as_g_p_x_responses.go index 4b38e0d..80d07d3 100644 --- a/internal/gen/strava-api-go/client/routes/get_route_as_g_p_x_responses.go +++ b/internal/gen/strava-api-go/client/routes/get_route_as_g_p_x_responses.go @@ -17,13 +17,14 @@ import ( // GetRouteAsGPXReader is a Reader for the GetRouteAsGPX structure. type GetRouteAsGPXReader struct { formats strfmt.Registry + writer io.Writer } // ReadResponse reads a server response into the received o. func (o *GetRouteAsGPXReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { switch response.Code() { case 200: - result := NewGetRouteAsGPXOK() + result := NewGetRouteAsGPXOK(o.writer) if err := result.readResponse(response, consumer, o.formats); err != nil { return nil, err } @@ -41,8 +42,11 @@ func (o *GetRouteAsGPXReader) ReadResponse(response runtime.ClientResponse, cons } // NewGetRouteAsGPXOK creates a GetRouteAsGPXOK with default headers values -func NewGetRouteAsGPXOK() *GetRouteAsGPXOK { - return &GetRouteAsGPXOK{} +func NewGetRouteAsGPXOK(writer io.Writer) *GetRouteAsGPXOK { + return &GetRouteAsGPXOK{ + + Payload: writer, + } } /* @@ -51,6 +55,7 @@ GetRouteAsGPXOK describes a response with status code 200, with default header v A GPX file with the route. */ type GetRouteAsGPXOK struct { + Payload io.Writer } // IsSuccess returns true when this get route as g p x o k response has a 2xx status code @@ -91,8 +96,17 @@ func (o *GetRouteAsGPXOK) String() string { return fmt.Sprintf("[GET /routes/{id}/export_gpx][%d] getRouteAsGPXOK", 200) } +func (o *GetRouteAsGPXOK) GetPayload() io.Writer { + return o.Payload +} + func (o *GetRouteAsGPXOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + return nil } diff --git a/internal/gen/strava-api-go/client/routes/get_route_as_t_c_x_responses.go b/internal/gen/strava-api-go/client/routes/get_route_as_t_c_x_responses.go index 7f5ed31..39b5605 100644 --- a/internal/gen/strava-api-go/client/routes/get_route_as_t_c_x_responses.go +++ b/internal/gen/strava-api-go/client/routes/get_route_as_t_c_x_responses.go @@ -17,13 +17,14 @@ import ( // GetRouteAsTCXReader is a Reader for the GetRouteAsTCX structure. type GetRouteAsTCXReader struct { formats strfmt.Registry + writer io.Writer } // ReadResponse reads a server response into the received o. func (o *GetRouteAsTCXReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { switch response.Code() { case 200: - result := NewGetRouteAsTCXOK() + result := NewGetRouteAsTCXOK(o.writer) if err := result.readResponse(response, consumer, o.formats); err != nil { return nil, err } @@ -41,8 +42,11 @@ func (o *GetRouteAsTCXReader) ReadResponse(response runtime.ClientResponse, cons } // NewGetRouteAsTCXOK creates a GetRouteAsTCXOK with default headers values -func NewGetRouteAsTCXOK() *GetRouteAsTCXOK { - return &GetRouteAsTCXOK{} +func NewGetRouteAsTCXOK(writer io.Writer) *GetRouteAsTCXOK { + return &GetRouteAsTCXOK{ + + Payload: writer, + } } /* @@ -51,6 +55,7 @@ GetRouteAsTCXOK describes a response with status code 200, with default header v A TCX file with the route. */ type GetRouteAsTCXOK struct { + Payload io.Writer } // IsSuccess returns true when this get route as t c x o k response has a 2xx status code @@ -91,8 +96,17 @@ func (o *GetRouteAsTCXOK) String() string { return fmt.Sprintf("[GET /routes/{id}/export_tcx][%d] getRouteAsTCXOK", 200) } +func (o *GetRouteAsTCXOK) GetPayload() io.Writer { + return o.Payload +} + func (o *GetRouteAsTCXOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + return nil } diff --git a/internal/gen/strava-api-go/client/routes/routes_client.go b/internal/gen/strava-api-go/client/routes/routes_client.go index df9b23e..17dd1bd 100644 --- a/internal/gen/strava-api-go/client/routes/routes_client.go +++ b/internal/gen/strava-api-go/client/routes/routes_client.go @@ -3,6 +3,8 @@ package routes import ( + "io" + "github.com/go-openapi/runtime" httptransport "github.com/go-openapi/runtime/client" "github.com/go-openapi/strfmt" @@ -49,11 +51,55 @@ type Client struct { // ClientOption may be used to customize the behavior of Client methods. type ClientOption func(*runtime.ClientOperation) +// This client is generated with a few options you might find useful for your swagger spec. +// +// Feel free to add you own set of options. + +// WithAccept allows the client to force the Accept header +// to negotiate a specific Producer from the server. +// +// You may use this option to set arbitrary extensions to your MIME media type. +func WithAccept(mime string) ClientOption { + return func(r *runtime.ClientOperation) { + r.ProducesMediaTypes = []string{mime} + } +} + +// WithAcceptApplicationGpxXML sets the Accept header to "application/gpx+xml". +func WithAcceptApplicationGpxXML(r *runtime.ClientOperation) { + r.ProducesMediaTypes = []string{"application/gpx+xml"} +} + +// WithAcceptApplicationJSON sets the Accept header to "application/json". +func WithAcceptApplicationJSON(r *runtime.ClientOperation) { + r.ProducesMediaTypes = []string{"application/json"} +} + +// WithAcceptApplicationTcxXML sets the Accept header to "application/tcx+xml". +func WithAcceptApplicationTcxXML(r *runtime.ClientOperation) { + r.ProducesMediaTypes = []string{"application/tcx+xml"} +} + +// WithAcceptApplicationVndGarminTcxXML sets the Accept header to "application/vnd.garmin.tcx+xml". +func WithAcceptApplicationVndGarminTcxXML(r *runtime.ClientOperation) { + r.ProducesMediaTypes = []string{"application/vnd.garmin.tcx+xml"} +} + +// WithAcceptApplicationXML sets the Accept header to "application/xml". +func WithAcceptApplicationXML(r *runtime.ClientOperation) { + r.ProducesMediaTypes = []string{"application/xml"} +} + +// WithAcceptTextXML sets the Accept header to "text/xml". +func WithAcceptTextXML(r *runtime.ClientOperation) { + r.ProducesMediaTypes = []string{"text/xml"} +} + // ClientService is the interface for Client methods type ClientService interface { - GetRouteAsGPX(params *GetRouteAsGPXParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetRouteAsGPXOK, error) + GetRouteAsGPX(params *GetRouteAsGPXParams, authInfo runtime.ClientAuthInfoWriter, writer io.Writer, opts ...ClientOption) (*GetRouteAsGPXOK, error) - GetRouteAsTCX(params *GetRouteAsTCXParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetRouteAsTCXOK, error) + GetRouteAsTCX(params *GetRouteAsTCXParams, authInfo runtime.ClientAuthInfoWriter, writer io.Writer, opts ...ClientOption) (*GetRouteAsTCXOK, error) GetRouteByID(params *GetRouteByIDParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetRouteByIDOK, error) @@ -67,7 +113,7 @@ GetRouteAsGPX exports route g p x Returns a GPX file of the route. Requires read_all scope for private routes. */ -func (a *Client) GetRouteAsGPX(params *GetRouteAsGPXParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetRouteAsGPXOK, error) { +func (a *Client) GetRouteAsGPX(params *GetRouteAsGPXParams, authInfo runtime.ClientAuthInfoWriter, writer io.Writer, opts ...ClientOption) (*GetRouteAsGPXOK, error) { // NOTE: parameters are not validated before sending if params == nil { params = NewGetRouteAsGPXParams() @@ -76,11 +122,11 @@ func (a *Client) GetRouteAsGPX(params *GetRouteAsGPXParams, authInfo runtime.Cli ID: "getRouteAsGPX", Method: "GET", PathPattern: "/routes/{id}/export_gpx", - ProducesMediaTypes: []string{"application/json"}, + ProducesMediaTypes: []string{"application/gpx+xml", "application/xml", "text/xml"}, ConsumesMediaTypes: []string{"application/json"}, Schemes: []string{"https"}, Params: params, - Reader: &GetRouteAsGPXReader{formats: a.formats}, + Reader: &GetRouteAsGPXReader{formats: a.formats, writer: writer}, AuthInfo: authInfo, Context: params.Context, Client: params.HTTPClient, @@ -112,7 +158,7 @@ GetRouteAsTCX exports route t c x Returns a TCX file of the route. Requires read_all scope for private routes. */ -func (a *Client) GetRouteAsTCX(params *GetRouteAsTCXParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetRouteAsTCXOK, error) { +func (a *Client) GetRouteAsTCX(params *GetRouteAsTCXParams, authInfo runtime.ClientAuthInfoWriter, writer io.Writer, opts ...ClientOption) (*GetRouteAsTCXOK, error) { // NOTE: parameters are not validated before sending if params == nil { params = NewGetRouteAsTCXParams() @@ -121,11 +167,11 @@ func (a *Client) GetRouteAsTCX(params *GetRouteAsTCXParams, authInfo runtime.Cli ID: "getRouteAsTCX", Method: "GET", PathPattern: "/routes/{id}/export_tcx", - ProducesMediaTypes: []string{"application/json"}, + ProducesMediaTypes: []string{"application/tcx+xml", "application/vnd.garmin.tcx+xml", "application/xml", "text/xml"}, ConsumesMediaTypes: []string{"application/json"}, Schemes: []string{"https"}, Params: params, - Reader: &GetRouteAsTCXReader{formats: a.formats}, + Reader: &GetRouteAsTCXReader{formats: a.formats, writer: writer}, AuthInfo: authInfo, Context: params.Context, Client: params.HTTPClient, From b2518ad7ed62d7f8dbc4048f16029ac6605916ae Mon Sep 17 00:00:00 2001 From: Oleg Balunenko Date: Fri, 5 Jun 2026 05:22:30 +0400 Subject: [PATCH 3/3] chore: Refine Go development tooling and integration test setup The `go-tools-docker-compose.yml` file has been simplified to focus on core linting and code generation tasks, removing services for specific testing, coverage, and release operations that are now handled through other mechanisms. This streamlines the local development environment setup. Furthermore, integration tests in `examples/client_example_test.go` are explicitly tagged with `//go:build integration` and comprehensive execution instructions, including the `STRAVA_ACCESS_TOKEN` requirement, have been added to the `README.md`. A skip condition was also introduced to enhance test robustness when an authenticated athlete lacks specific data. A minor correction to a `.PHONY` declaration in the `Makefile` completes these tooling adjustments. --- Makefile | 2 +- README.md | 7 ++- .../go-tools-docker-compose.yml | 56 ------------------- examples/client_example_test.go | 6 +- 4 files changed, 12 insertions(+), 59 deletions(-) diff --git a/Makefile b/Makefile index 6e9a231..7be2c5c 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ fmt: ## Format code and sort imports. format-code: fmt imports -.PHONY: format-project +.PHONY: format-code ## Installs vendored tools. install-tools: diff --git a/README.md b/README.md index fdc9dd6..9637bab 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,12 @@ Go Strava API client generated from [API spec](https://developers.strava.com/swa [Usage examples](examples/client_example_test.go) +The examples are live integration tests and require a Strava access token: + +```shell +STRAVA_ACCESS_TOKEN=... go test -tags=integration ./examples +``` + ### Usage ```go @@ -46,4 +52,3 @@ func main() { } ``` - diff --git a/deployments/docker-compose/go-tools-docker-compose.yml b/deployments/docker-compose/go-tools-docker-compose.yml index 18d1fe5..73f201d 100755 --- a/deployments/docker-compose/go-tools-docker-compose.yml +++ b/deployments/docker-compose/go-tools-docker-compose.yml @@ -23,68 +23,12 @@ services: service: tools entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/style/fmt.sh' - run-tests: - extends: - service: tools - entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/tests/run.sh' - - run-tests-coverage: - extends: - service: tools - entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/tests/coverage.sh' - - prepare-cover-report: - extends: - service: tools - entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/tests/prepare-cover-report.sh' - - update-readme-coverage: - extends: - service: tools - entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/update-readme-coverage.sh' - lint-full: extends: service: tools entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/linting/run-linters.sh' - lint-pipeline: - extends: - service: tools - entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/linting/golangci-pipeline.sh' - - lint-sonar: - extends: - service: tools - entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/linting/golangci-sonar.sh' - go-generate: extends: service: tools entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/codegen/go-generate.sh' - - release-local-snapshot: - extends: - service: tools - entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/release/local-snapshot-release.sh' - environment: - APP_NAME: ${APP_NAME} - GOOS: ${GOOS} - GOARCH: ${GOARCH} - GITHUB_TOKEN: ${GITHUB_TOKEN:-} - - release-check-config: - extends: - service: tools - entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/release/check.sh' - environment: - APP_NAME: ${APP_NAME} - GITHUB_TOKEN: ${GITHUB_TOKEN:-} - - release: - extends: - service: tools - entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/release/release.sh' - environment: - APP_NAME: ${APP_NAME} - GITHUB_TOKEN: ${GITHUB_TOKEN:-} diff --git a/examples/client_example_test.go b/examples/client_example_test.go index 3a18c93..aa42df7 100644 --- a/examples/client_example_test.go +++ b/examples/client_example_test.go @@ -1,3 +1,5 @@ +//go:build integration + package examples import ( @@ -69,7 +71,9 @@ func TestGetLoggedInAthleteGear(t *testing.T) { athlete, err := apiClient.Athletes.GetLoggedInAthlete(ctx) require.NoError(t, err) - require.NotNil(t, athlete.Bikes) + if len(athlete.Bikes) == 0 { + t.Skip("authenticated athlete has no bikes") + } gear, err := apiClient.Gears.GetGearById(ctx, athlete.Bikes[0].ID) require.NoError(t, err)