strava-ruby-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +15 -0
  5. data/.rubocop_todo.yml +47 -0
  6. data/.travis.yml +9 -0
  7. data/CHANGELOG.md +3 -0
  8. data/CONTRIBUTING.md +125 -0
  9. data/Dangerfile +2 -0
  10. data/Gemfile +15 -0
  11. data/LICENSE.md +22 -0
  12. data/README.md +231 -0
  13. data/RELEASING.md +61 -0
  14. data/Rakefile +17 -0
  15. data/bin/oauth-token.rb +28 -0
  16. data/lib/strava-ruby-client.rb +30 -0
  17. data/lib/strava/api/client.rb +38 -0
  18. data/lib/strava/api/config.rb +31 -0
  19. data/lib/strava/errors/fault.rb +13 -0
  20. data/lib/strava/logger.rb +13 -0
  21. data/lib/strava/models/activity.rb +57 -0
  22. data/lib/strava/models/athlete.rb +25 -0
  23. data/lib/strava/models/map.rb +9 -0
  24. data/lib/strava/models/model.rb +5 -0
  25. data/lib/strava/models/token.rb +12 -0
  26. data/lib/strava/oauth/client.rb +70 -0
  27. data/lib/strava/oauth/config.rb +33 -0
  28. data/lib/strava/version.rb +3 -0
  29. data/lib/strava/web/client.rb +31 -0
  30. data/lib/strava/web/config.rb +41 -0
  31. data/lib/strava/web/connection.rb +35 -0
  32. data/lib/strava/web/raise_error.rb +29 -0
  33. data/lib/strava/web/request.rb +38 -0
  34. data/spec/fixtures/strava/client_athlete.yml +59 -0
  35. data/spec/fixtures/strava/client_athlete_activities.yml +121 -0
  36. data/spec/fixtures/strava/oauth_token_authorization_code.yml +53 -0
  37. data/spec/fixtures/strava/oauth_token_invalid_client.yml +50 -0
  38. data/spec/fixtures/strava/oauth_token_invalid_code.yml +50 -0
  39. data/spec/fixtures/strava/oauth_token_refresh_token.yml +52 -0
  40. data/spec/spec_helper.rb +10 -0
  41. data/spec/strava/api/client_spec.rb +36 -0
  42. data/spec/strava/api/config_spec.rb +22 -0
  43. data/spec/strava/oauth/client_spec.rb +120 -0
  44. data/spec/strava/oauth/config_spec.rb +24 -0
  45. data/spec/strava/version_spec.rb +7 -0
  46. data/spec/strava/web/client_spec.rb +9 -0
  47. data/spec/strava/web/config_spec.rb +4 -0
  48. data/spec/support/shared/it_behaves_like_web_client.rb +131 -0
  49. data/spec/support/vcr.rb +12 -0
  50. data/strava-ruby-client.gemspec +21 -0
  51. metadata +150 -0
@@ -0,0 +1,50 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://www.strava.com/oauth/token
6
+ body:
7
+ encoding: UTF-8
8
+ string: client_id=12345&client_secret=client-secret&code=code&grant_type=authorization_code&token
9
+ headers:
10
+ Accept:
11
+ - application/json; charset=utf-8
12
+ User-Agent:
13
+ - Strava Ruby Client/0.1.0
14
+ Content-Type:
15
+ - application/x-www-form-urlencoded
16
+ Accept-Encoding:
17
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
18
+ response:
19
+ status:
20
+ code: 400
21
+ message: Bad Request
22
+ headers:
23
+ Date:
24
+ - Thu, 22 Nov 2018 20:26:56 GMT
25
+ Content-Type:
26
+ - application/json; charset=UTF-8
27
+ Transfer-Encoding:
28
+ - chunked
29
+ Connection:
30
+ - keep-alive
31
+ Cache-Control:
32
+ - no-cache
33
+ Via:
34
+ - 1.1 linkerd
35
+ Status:
36
+ - 400 Bad Request
37
+ X-Request-Id:
38
+ - d3406a4e-9099-400f-b709-aad0ea2768fe
39
+ X-Frame-Options:
40
+ - SAMEORIGIN,DENY
41
+ X-Content-Type-Options:
42
+ - nosniff
43
+ X-Xss-Protection:
44
+ - 1; mode=block
45
+ body:
46
+ encoding: ASCII-8BIT
47
+ string: '{"message":"Bad Request","errors":[{"resource":"RequestToken","field":"code","code":"invalid"}]}'
48
+ http_version:
49
+ recorded_at: Thu, 22 Nov 2018 20:26:56 GMT
50
+ recorded_with: VCR 4.0.0
@@ -0,0 +1,52 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://www.strava.com/oauth/token
6
+ body:
7
+ encoding: UTF-8
8
+ string: client_id=24523&client_secret=client-secret&grant_type=refresh_token&refresh_token=refresh-token
9
+ headers:
10
+ Accept:
11
+ - application/json; charset=utf-8
12
+ User-Agent:
13
+ - Strava Ruby Client/0.1.0
14
+ Content-Type:
15
+ - application/x-www-form-urlencoded
16
+ Accept-Encoding:
17
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Date:
24
+ - Thu, 22 Nov 2018 22:31:57 GMT
25
+ Content-Type:
26
+ - application/json; charset=UTF-8
27
+ Transfer-Encoding:
28
+ - chunked
29
+ Connection:
30
+ - keep-alive
31
+ Cache-Control:
32
+ - max-age=0, private, must-revalidate
33
+ Via:
34
+ - 1.1 linkerd
35
+ Status:
36
+ - 200 OK
37
+ X-Request-Id:
38
+ - df1c7766-41ac-44e5-b2a1-fc96486b2c7c
39
+ Etag:
40
+ - W/"0f3eb115bd7d2ef9718c83ece058e3f9"
41
+ X-Frame-Options:
42
+ - SAMEORIGIN,DENY
43
+ X-Content-Type-Options:
44
+ - nosniff
45
+ X-Xss-Protection:
46
+ - 1; mode=block
47
+ body:
48
+ encoding: ASCII-8BIT
49
+ string: '{"token_type":"Bearer","access_token":"new-access-token","expires_at":1542940199,"expires_in":14282,"refresh_token":"new-refresh-token"}'
50
+ http_version:
51
+ recorded_at: Thu, 22 Nov 2018 22:31:57 GMT
52
+ recorded_with: VCR 4.0.0
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..'))
2
+
3
+ require 'rubygems'
4
+ require 'rspec'
5
+
6
+ require 'strava-ruby-client'
7
+
8
+ Dir[File.join(File.dirname(__FILE__), 'support', '**/*.rb')].each do |file|
9
+ require file
10
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Strava::Api::Client do
4
+ before do
5
+ Strava::Api::Config.reset
6
+ end
7
+ it_behaves_like 'web client'
8
+ context 'with a token' do
9
+ let(:client) { Strava::Api::Client.new(access_token: ENV['STRAVA_ACCESS_TOKEN'] || 'access-token') }
10
+ describe '#athlete', vcr: { cassette_name: 'client_athlete' } do
11
+ let(:athlete) { client.athlete }
12
+ it 'returns athlete' do
13
+ expect(athlete).to be_a Strava::Models::Athlete
14
+ expect(athlete.id).to eq 26_462_176
15
+ expect(athlete.created_at).to eq Time.parse('2017-11-28 19:05:35 UTC')
16
+ expect(athlete.updated_at).to eq Time.parse('2018-11-19 01:44:15 UTC')
17
+ expect(athlete.firstname).to eq 'Daniel'
18
+ expect(athlete.lastname).to eq 'Block'
19
+ expect(athlete.city).to eq 'New York'
20
+ expect(athlete.email).to eq 'dblock@example.com'
21
+ end
22
+ end
23
+ describe '#athlete_activities', vcr: { cassette_name: 'client_athlete_activities' } do
24
+ let(:athlete_activities) { client.athlete_activities }
25
+ it 'returns athlete activities' do
26
+ expect(athlete_activities).to be_a Enumerable
27
+ expect(athlete_activities.count).to eq 30
28
+ activity = athlete_activities.first
29
+ expect(activity.id).to eq 1_972_463_847
30
+ expect(activity.athlete).to be_a Strava::Models::Athlete
31
+ expect(activity.map).to be_a Strava::Models::Map
32
+ expect(activity.start_date).to be_a Time
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Strava::Api::Config do
4
+ before do
5
+ Strava::Api.config.reset
6
+ end
7
+ describe '#defaults' do
8
+ it 'sets endpoint' do
9
+ expect(Strava::Api.config.endpoint).to eq 'https://www.strava.com/api/v3'
10
+ end
11
+ end
12
+ describe '#configure' do
13
+ before do
14
+ Strava::Api.configure do |config|
15
+ config.endpoint = 'updated'
16
+ end
17
+ end
18
+ it 'sets endpoint' do
19
+ expect(Strava::Api.config.endpoint).to eq 'updated'
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Strava::OAuth::Client do
4
+ before do
5
+ Strava::OAuth::Config.reset
6
+ end
7
+ it_behaves_like 'web client'
8
+ context 'with defaults' do
9
+ let(:client) { Strava::OAuth::Client.new }
10
+ describe '#initialize' do
11
+ it 'sets endpoint' do
12
+ expect(client.endpoint).to eq 'https://www.strava.com/oauth'
13
+ end
14
+ Strava::OAuth::Config::ATTRIBUTES.each do |key|
15
+ it "sets #{key}" do
16
+ expect(client.send(key)).to eq Strava::OAuth::Config.send(key)
17
+ end
18
+ end
19
+ context '#authorize_url' do
20
+ it 'requires client id' do
21
+ expect { client.authorize_url }.to raise_error ArgumentError, 'Missing Strava client id.'
22
+ end
23
+ end
24
+ end
25
+ end
26
+ context 'with custom settings' do
27
+ describe '#initialize' do
28
+ Strava::OAuth::Config::ATTRIBUTES.each do |key|
29
+ context key do
30
+ let(:client) { Strava::OAuth::Client.new(key => 'custom') }
31
+ it "sets #{key}" do
32
+ expect(client.send(key)).to_not eq Strava::OAuth::Config.send(key)
33
+ expect(client.send(key)).to eq 'custom'
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ context 'global config' do
40
+ after do
41
+ Strava::OAuth::Client.config.reset
42
+ end
43
+ let(:client) { Strava::OAuth::Client.new }
44
+ context 'client id and secret' do
45
+ before do
46
+ Strava::OAuth::Client.configure do |config|
47
+ config.client_id = 'custom client id'
48
+ config.client_secret = 'custom client secret'
49
+ end
50
+ end
51
+ describe '#initialize' do
52
+ it 'sets client id and secret' do
53
+ expect(client.client_id).to eq 'custom client id'
54
+ expect(client.client_secret).to eq 'custom client secret'
55
+ end
56
+ end
57
+ end
58
+ end
59
+ context 'with a client id and secret' do
60
+ let(:client) { Strava::OAuth::Client.new(client_id: '12345', client_secret: 'client-secret') }
61
+ context '#authorize_url' do
62
+ it 'returns url' do
63
+ expect(client.authorize_url).to eq 'https://www.strava.com/oauth/authorize?approval_prompt=auto&client_id=12345&redirect_uri=http%3A%2F%2Flocalhost&response_type=code&scope=read'
64
+ end
65
+ it 'returns url with custom options' do
66
+ expect(
67
+ client.authorize_url(
68
+ redirect_uri: 'https://example.com/oauth',
69
+ approval_prompt: 'force',
70
+ response_type: 'code',
71
+ scope: 'activity:read_all',
72
+ state: 'magic'
73
+ )
74
+ ).to eq 'https://www.strava.com/oauth/authorize?approval_prompt=force&client_id=12345&redirect_uri=https%3A%2F%2Fexample.com%2Foauth&response_type=code&scope=activity%3Aread_all&state=magic'
75
+ end
76
+ end
77
+ context '#oauth_token' do
78
+ it 'errors with an invalid client id', vcr: { cassette_name: 'oauth_token_invalid_client' } do
79
+ expect { client.oauth_token(code: 'code') }.to raise_error Strava::Errors::Fault do |e|
80
+ expect(e.message).to eq 'Bad Request'
81
+ expect(e.errors).to eq([{ 'code' => 'invalid', 'field' => 'client_id', 'resource' => 'Application' }])
82
+ end
83
+ end
84
+ it 'errors with an invalid code', vcr: { cassette_name: 'oauth_token_invalid_code' } do
85
+ expect { client.oauth_token(code: 'code') }.to raise_error Faraday::ClientError do |e|
86
+ expect(e.message).to eq 'Bad Request'
87
+ expect(e.errors).to eq([{ 'code' => 'invalid', 'field' => 'code', 'resource' => 'RequestToken' }])
88
+ end
89
+ end
90
+ it 'performs the initial token exchange', vcr: { cassette_name: 'oauth_token_authorization_code' } do
91
+ token = client.oauth_token(code: 'deadbeef585a6edb1f00309d3e1e0fec74973fb0')
92
+ expect(token).to be_a Strava::Models::Token
93
+ expect(token.access_token).to eq 'deadbeefd7ab44704fb2a146bd98c7a349a2b43d'
94
+ expect(token.refresh_token).to eq 'deadbeef9d64b225d0c50db4854a8d6c8757702d'
95
+ expect(token.token_type).to eq 'Bearer'
96
+ expect(token.expires_in).to eq 21_600
97
+ expect(token.expires_at).to eq Time.at(1_542_940_199)
98
+ athlete = token.athlete
99
+ expect(athlete).to be_a Strava::Models::Athlete
100
+ expect(athlete.updated_at).to eq Time.parse('2018-11-19 01:44:15 UTC')
101
+ expect(athlete.firstname).to eq 'Daniel'
102
+ expect(athlete.lastname).to eq 'Block'
103
+ expect(athlete.city).to eq 'New York'
104
+ end
105
+ it 'refreshes the token', vcr: { cassette_name: 'oauth_token_refresh_token' } do
106
+ token = client.oauth_token(
107
+ refresh_token: '8b15b6bb9d64a225d0c50db4854a8d6c8757702d',
108
+ grant_type: 'refresh_token'
109
+ )
110
+ expect(token).to be_a Strava::Models::Token
111
+ expect(token.access_token).to eq 'new-access-token'
112
+ expect(token.refresh_token).to eq 'new-refresh-token'
113
+ expect(token.token_type).to eq 'Bearer'
114
+ expect(token.expires_in).to eq 14_282
115
+ expect(token.expires_at).to eq Time.at(1_542_940_199)
116
+ expect(token.athlete).to be nil
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Strava::OAuth::Config do
4
+ describe '#defaults' do
5
+ it 'sets endpoint' do
6
+ expect(Strava::OAuth.config.endpoint).to eq 'https://www.strava.com/oauth'
7
+ expect(Strava::OAuth.config.client_id).to be nil
8
+ expect(Strava::OAuth.config.client_secret).to be nil
9
+ end
10
+ end
11
+ describe '#configure' do
12
+ before do
13
+ Strava::OAuth.configure do |config|
14
+ config.client_id = 'client id'
15
+ config.client_secret = 'client secret'
16
+ end
17
+ end
18
+ it 'sets client id and secret' do
19
+ expect(Strava::OAuth.config.endpoint).to eq 'https://www.strava.com/oauth'
20
+ expect(Strava::OAuth.config.client_id).to eq 'client id'
21
+ expect(Strava::OAuth.config.client_secret).to eq 'client secret'
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Strava do
4
+ it 'has a version' do
5
+ expect(Strava::VERSION).to_not be nil
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Strava::Web::Client do
4
+ describe '#endpoint' do
5
+ it 'is not implemented' do
6
+ expect { subject.endpoint }.to raise_error NotImplementedError
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe Strava::Web::Config do
4
+ end
@@ -0,0 +1,131 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.shared_examples 'web client' do
4
+ before do
5
+ described_class.config.reset
6
+ Strava::Web::Config.reset
7
+ end
8
+ context 'with defaults' do
9
+ let(:client) { described_class.new }
10
+ context '#initialize' do
11
+ it 'implements endpoint' do
12
+ expect(client.endpoint).to_not be nil
13
+ end
14
+ it 'includes default http configuration' do
15
+ expect(client.user_agent).to eq 'Strava Ruby Client/0.1.0'
16
+ end
17
+ it 'sets user-agent' do
18
+ expect(client.user_agent).to eq Strava::Web::Config.user_agent
19
+ expect(client.user_agent).to include Strava::VERSION
20
+ end
21
+ (Strava::Web::Config::ATTRIBUTES - [:logger]).each do |key|
22
+ it "sets #{key}" do
23
+ expect(client.send(key)).to eq Strava::Web::Config.send(key)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ context 'with custom settings' do
29
+ context '#initialize' do
30
+ Strava::Web::Config::ATTRIBUTES.each do |key|
31
+ context key.to_s do
32
+ let(:client) { described_class.new(key => 'custom') }
33
+ it "sets #{key}" do
34
+ expect(client.send(key)).to_not eq Strava::Web::Config.send(key)
35
+ expect(client.send(key)).to eq 'custom'
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ context 'global config' do
42
+ let(:client) { described_class.new }
43
+ context 'user-agent' do
44
+ before do
45
+ Strava::Web::Client.configure do |config|
46
+ config.user_agent = 'custom/user-agent'
47
+ end
48
+ end
49
+ context '#initialize' do
50
+ it 'sets user-agent' do
51
+ expect(client.user_agent).to eq 'custom/user-agent'
52
+ end
53
+ it 'creates a connection with the custom user-agent' do
54
+ expect(client.send(:connection).headers).to include(
55
+ 'Accept' => 'application/json; charset=utf-8',
56
+ 'User-Agent' => 'custom/user-agent'
57
+ )
58
+ end
59
+ end
60
+ end
61
+ context 'proxy' do
62
+ before do
63
+ Strava::Web::Client.configure do |config|
64
+ config.proxy = 'http://localhost:8080'
65
+ end
66
+ end
67
+ context '#initialize' do
68
+ it 'sets proxy' do
69
+ expect(client.proxy).to eq 'http://localhost:8080'
70
+ end
71
+ it 'creates a connection with the proxy' do
72
+ expect(client.send(:connection).proxy.uri.to_s).to eq 'http://localhost:8080'
73
+ end
74
+ end
75
+ end
76
+ context 'SSL options' do
77
+ before do
78
+ Strava::Web::Client.configure do |config|
79
+ config.ca_path = '/ca_path'
80
+ config.ca_file = '/ca_file'
81
+ end
82
+ end
83
+ context '#initialize' do
84
+ it 'sets ca_path and ca_file' do
85
+ expect(client.ca_path).to eq '/ca_path'
86
+ expect(client.ca_file).to eq '/ca_file'
87
+ end
88
+ it 'creates a connection with ssl options' do
89
+ ssl = client.send(:connection).ssl
90
+ expect(ssl.ca_path).to eq '/ca_path'
91
+ expect(ssl.ca_file).to eq '/ca_file'
92
+ end
93
+ end
94
+ end
95
+ context 'logger option' do
96
+ let(:logger) { Logger.new(STDOUT) }
97
+ before do
98
+ Strava::Web::Client.configure do |config|
99
+ config.logger = logger
100
+ end
101
+ end
102
+ context '#initialize' do
103
+ it 'sets logger' do
104
+ expect(client.logger).to eq logger
105
+ end
106
+ it 'creates a connection with a logger' do
107
+ expect(client.send(:connection).builder.handlers).to include ::Faraday::Response::Logger
108
+ end
109
+ end
110
+ end
111
+ context 'timeout options' do
112
+ before do
113
+ Strava::Web::Client.configure do |config|
114
+ config.timeout = 10
115
+ config.open_timeout = 15
116
+ end
117
+ end
118
+ context '#initialize' do
119
+ it 'sets timeout and open_timeout' do
120
+ expect(client.timeout).to eq 10
121
+ expect(client.open_timeout).to eq 15
122
+ end
123
+ it 'creates a connection with timeout options' do
124
+ conn = client.send(:connection)
125
+ expect(conn.options.timeout).to eq 10
126
+ expect(conn.options.open_timeout).to eq 15
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end