Skip to content

Commit

Permalink
normalize encoding (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
bzimmer authored Oct 28, 2021
1 parent 7005bad commit 30b88aa
Show file tree
Hide file tree
Showing 21 changed files with 424 additions and 160 deletions.
6 changes: 3 additions & 3 deletions activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import (

const UserAgent = "github.com/bzimmer/activity"

type GPXer interface {
type GPXEncoder interface {
// GPX returns a GPX instance
GPX() (*gpx.GPX, error)
}

type GeoJSONer interface {
type GeoJSONEncoder interface {
// GeoJSON returns a GeoJSON instance
GeoJSON() (*geojson.Feature, error)
GeoJSON() (*geojson.FeatureCollection, error)
}
3 changes: 3 additions & 0 deletions cyclinganalytics/cyclinganalytics.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import (

const _baseURL = "https://www.cyclinganalytics.com/api"

// APIOption for configuring API requests
type APIOption func(url.Values) error

// Client for accessing Cycling Analytics' API
type Client struct {
config oauth2.Config
Expand Down
25 changes: 23 additions & 2 deletions cyclinganalytics/cyclinganalytics_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package cyclinganalytics_test

import (
"context"
"net/http"
"testing"
"time"

"github.com/bzimmer/activity/cyclinganalytics"
"github.com/bzimmer/httpwares"
"github.com/stretchr/testify/assert"
"golang.org/x/oauth2"
"golang.org/x/time/rate"

"github.com/bzimmer/activity/cyclinganalytics"
)

func newClient(status int, filename string) (*cyclinganalytics.Client, error) {
Expand All @@ -13,6 +20,20 @@ func newClient(status int, filename string) (*cyclinganalytics.Client, error) {
Status: status,
Filename: filename,
ContentType: "application/json"}),
cyclinganalytics.WithHTTPTracing(false),
cyclinganalytics.WithTokenCredentials("fooKey", "barToken", time.Time{}))
}

func TestWith(t *testing.T) {
t.Parallel()
a := assert.New(t)
client, err := cyclinganalytics.NewClient(
cyclinganalytics.WithConfig(oauth2.Config{}),
cyclinganalytics.WithHTTPTracing(true),
cyclinganalytics.WithHTTPClient(http.DefaultClient),
cyclinganalytics.WithToken(&oauth2.Token{}),
cyclinganalytics.WithAutoRefresh(context.Background()),
cyclinganalytics.WithRateLimiter(rate.NewLimiter(rate.Every(time.Second), 10)),
cyclinganalytics.WithClientCredentials("foo", "bar"))
a.NoError(err)
a.NotNil(client)
}
2 changes: 1 addition & 1 deletion cyclinganalytics/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/bzimmer/activity"
)

var _ activity.GPXer = (*Ride)(nil)
var _ activity.GPXEncoder = (*Ride)(nil)

func (r *Ride) GPX() (*gpx.GPX, error) {
var layout geom.Layout
Expand Down
4 changes: 2 additions & 2 deletions cyclinganalytics/encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/bzimmer/activity/cyclinganalytics"
)

func TestRideToGPX(t *testing.T) {
func TestRideGPX(t *testing.T) {
t.Parallel()
a := assert.New(t)
client, err := newClient(http.StatusOK, "ride.json")
Expand All @@ -19,7 +19,7 @@ func TestRideToGPX(t *testing.T) {
opts := cyclinganalytics.RideOptions{
Streams: []string{"latitude", "longitude", "elevation"},
}
ride, err := client.Rides.Ride(ctx, 175334338355, opts)
ride, err := client.Rides.Ride(ctx, 175334338355, cyclinganalytics.WithRideOptions(opts))
a.NoError(err)
a.NotNil(ride)
a.NotNil(ride.Streams)
Expand Down
1 change: 1 addition & 0 deletions cyclinganalytics/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestModel(t *testing.T) {
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
src := cyclinganalytics.Ride{
UTCDatetime: cyclinganalytics.Datetime{Time: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)},
}
Expand Down
42 changes: 24 additions & 18 deletions cyclinganalytics/rides.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,31 +35,37 @@ const (
meupload = "me/upload"
)

func (r *RideOptions) values() (*url.Values, error) {
v := &url.Values{}
if r.Streams != nil {
if err := validateStreams(r.Streams); err != nil {
return nil, err
func WithRideOptions(r RideOptions) APIOption {
return func(v url.Values) error {
if r.Streams != nil {
if err := validateStreams(r.Streams); err != nil {
return err
}
v.Set("streams", strings.Join(r.Streams, ","))
}
v.Set("streams", strings.Join(r.Streams, ","))
}
if r.Curves.AveragePower && r.Curves.EffectivePower {
v.Set("curves", "true")
} else {
v.Set("power_curve", fmt.Sprintf("%t", r.Curves.AveragePower))
v.Set("epower_curve", fmt.Sprintf("%t", r.Curves.EffectivePower))
if r.Curves.AveragePower && r.Curves.EffectivePower {
v.Set("curves", "true")
} else {
v.Set("power_curve", fmt.Sprintf("%t", r.Curves.AveragePower))
v.Set("epower_curve", fmt.Sprintf("%t", r.Curves.EffectivePower))
}
return nil
}
return v, nil
}

// Ride returns a single ride with available options
func (s *RidesService) Ride(ctx context.Context, rideID int64, opts RideOptions) (*Ride, error) {
func (s *RidesService) Ride(ctx context.Context, rideID int64, opts ...APIOption) (*Ride, error) {
uri := fmt.Sprintf("ride/%d", rideID)
params, err := opts.values()
if err != nil {
return nil, err
v := url.Values{}
for i := range opts {
f := opts[i]
if f != nil {
if err := f(v); err != nil {
return nil, err
}
}
}
req, err := s.client.newAPIRequest(ctx, http.MethodGet, uri, params, nil)
req, err := s.client.newAPIRequest(ctx, http.MethodGet, uri, &v, nil)
if err != nil {
return nil, err
}
Expand Down
139 changes: 107 additions & 32 deletions cyclinganalytics/rides_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,127 @@ package cyclinganalytics_test
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/bzimmer/activity"
"github.com/bzimmer/activity/cyclinganalytics"
)

func TestRides(t *testing.T) {
t.Parallel()
a := assert.New(t)
client, err := newClient(http.StatusOK, "me-rides.json")
a.NoError(err)
ctx := context.Background()
rides, err := client.Rides.Rides(ctx, cyclinganalytics.Me, activity.Pagination{})
a.NoError(err)
a.NotNil(rides)
a.Equal(2, len(rides))
}

func TestRide(t *testing.T) {
t.Parallel()
a := assert.New(t)
client, err := newClient(http.StatusOK, "ride.json")
a.NoError(err)
ctx := context.Background()
opts := cyclinganalytics.RideOptions{
Streams: []string{"latitude", "longitude", "elevation"},

tests := []struct {
name string
err string
rideID int64
}{
{
rideID: 22322,
name: "query recent feed",
},
{
rideID: 175334338355,
name: "query recent feed",
},
{
rideID: 882722,
name: "query recent feed",
err: "Something went horribly wrong",
},
}

for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
mux := http.NewServeMux()
mux.HandleFunc("/ride/22322", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "testdata/ride.json")
})
mux.HandleFunc("/ride/175334338355", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "testdata/ride.json")
})
mux.HandleFunc("/ride/882722", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
http.ServeFile(w, r, "testdata/error.json")
})

svr := httptest.NewServer(mux)
defer svr.Close()

opts := cyclinganalytics.WithRideOptions(cyclinganalytics.RideOptions{
Streams: []string{"latitude", "longitude", "elevation"},
})
client, err := cyclinganalytics.NewClient(
cyclinganalytics.WithBaseURL(svr.URL),
cyclinganalytics.WithHTTPTracing(false),
cyclinganalytics.WithTokenCredentials("fooKey", "barToken", time.Time{}))
a.NoError(err)
ride, err := client.Rides.Ride(context.Background(), tt.rideID, opts)
if tt.err != "" {
a.Error(err)
a.Nil(ride)
a.Contains(err.Error(), tt.err)
return
}
a.NoError(err)
a.NotNil(ride)
a.NotNil(ride.Streams)
a.Equal(27154, len(ride.Streams.Elevation))
gears := ride.Streams.Gears
a.NotNil(gears)
a.Equal(813, len(gears.Shifts))
})
}
ride, err := client.Rides.Ride(ctx, 175334338355, opts)
a.NoError(err)
a.NotNil(ride)
a.NotNil(ride.Streams)
a.Equal(27154, len(ride.Streams.Elevation))
gears := ride.Streams.Gears
a.NotNil(gears)
a.Equal(813, len(gears.Shifts))
}

func TestRideForbidden(t *testing.T) {
func TestRides(t *testing.T) {
t.Parallel()
a := assert.New(t)
client, err := newClient(http.StatusForbidden, "error.json")
a.NoError(err)
ctx := context.Background()
ride, err := client.Rides.Ride(ctx, 175334338355, cyclinganalytics.RideOptions{})
a.Error(err)
a.Nil(ride)

tests := []struct {
name string
user cyclinganalytics.UserID
}{
{
user: cyclinganalytics.Me,
name: "query rides for `me`",
},
{
user: cyclinganalytics.UserID(882782),
name: "query rides for user",
},
}

for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
mux := http.NewServeMux()
mux.HandleFunc("/me/rides", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "testdata/me-rides.json")
})
mux.HandleFunc("/882782/rides", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "testdata/me-rides.json")
})

svr := httptest.NewServer(mux)
defer svr.Close()

client, err := cyclinganalytics.NewClient(
cyclinganalytics.WithBaseURL(svr.URL),
cyclinganalytics.WithHTTPTracing(false),
cyclinganalytics.WithTokenCredentials("fooKey", "barToken", time.Time{}))
a.NoError(err)
rides, err := client.Rides.Rides(context.Background(), tt.user, activity.Pagination{})
a.NoError(err)
a.NotNil(rides)
a.Len(rides, 2)
})
}
}
50 changes: 50 additions & 0 deletions cyclinganalytics/upload_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package cyclinganalytics_test

import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/bzimmer/activity/cyclinganalytics"
"github.com/stretchr/testify/assert"
)

func TestUpload(t *testing.T) {
tests := []struct {
name, err string
}{
{
name: "uploader error",
err: "missing upload file",
},
}

for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
a := assert.New(t)
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
})

svr := httptest.NewServer(mux)
defer svr.Close()

client, err := cyclinganalytics.NewClient(
cyclinganalytics.WithBaseURL(svr.URL),
cyclinganalytics.WithTokenCredentials("fooKey", "barToken", time.Time{}))
a.NoError(err)
uploader := client.Uploader()
a.NotNil(uploader)
upload, err := uploader.Upload(context.Background(), nil)
if tt.err != "" {
a.Error(err)
a.Nil(upload)
a.Contains(err.Error(), tt.err)
return
}
})
}
}
Loading

0 comments on commit 30b88aa

Please sign in to comment.