strava-ruby-client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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