attune 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -1
- data/attune.gemspec +1 -1
- data/lib/attune/client.rb +63 -6
- data/lib/attune/configurable.rb +4 -0
- data/lib/attune/default.rb +2 -0
- data/lib/attune/version.rb +1 -1
- data/spec/attune/client_spec.rb +25 -2
- data/spec/attune/json_logger_spec.rb +1 -1
- data/spec/remote_spec.rb +17 -2
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa77fa0fcbd44e02a0b3a26e722a76f3e7fabbff
|
4
|
+
data.tar.gz: bc15acc01376c94db3192f81a90d114aa6040e91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04272c55ef9a1f7095a2e77beaf008451aa73bf115fc5f99cf791f34e76e1dad2ffda8c3f1fde06b40203530fd6643822c563bd389772430b1f3ec9dbcb04b34
|
7
|
+
data.tar.gz: b95899dd52c6d526944569c775bfae2e942bfda23766b346792bfb68f28a0c6bc1103055ae9f5e86f1199842b788719348ef24f49d6fb888b2ba4585ce0600e6
|
data/README.md
CHANGED
@@ -77,12 +77,20 @@ class ProductsController
|
|
77
77
|
end
|
78
78
|
```
|
79
79
|
|
80
|
+
The client provides a way to request a new auth_token through the API
|
81
|
+
|
82
|
+
``` ruby
|
83
|
+
client = Attune::Client.new
|
84
|
+
auth_token = client.get_auth_token('my-client-id', 'my-client-secret')
|
85
|
+
```
|
86
|
+
|
80
87
|
### Configuration
|
81
88
|
|
82
89
|
Attune can be configured globally
|
83
90
|
|
84
91
|
``` ruby
|
85
92
|
Attune.configure do |c|
|
93
|
+
c.auth_token = "my-secure-auth-token"
|
86
94
|
c.endpoint = "http://example.com/"
|
87
95
|
c.timeout = 5
|
88
96
|
end
|
@@ -91,7 +99,7 @@ end
|
|
91
99
|
Settings can also be overridden on a client object
|
92
100
|
|
93
101
|
``` ruby
|
94
|
-
client = Attune::Client.new(timeout: 2)
|
102
|
+
client = Attune::Client.new(auth_token: "my-secure-auth-token", timeout: 2)
|
95
103
|
```
|
96
104
|
|
97
105
|
See the documentation for
|
data/attune.gemspec
CHANGED
@@ -24,6 +24,6 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_development_dependency "rake"
|
25
25
|
spec.add_development_dependency "rspec"
|
26
26
|
spec.add_development_dependency "yard"
|
27
|
-
spec.add_development_dependency "redcarpet"
|
27
|
+
spec.add_development_dependency "redcarpet", ">= 3.1.1"
|
28
28
|
spec.add_development_dependency "simplecov"
|
29
29
|
end
|
data/lib/attune/client.rb
CHANGED
@@ -7,6 +7,12 @@ module Attune
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
+
class AuthenticationException < Faraday::Error::ClientError
|
11
|
+
def initialize(message="Authentication credentials not accepted")
|
12
|
+
super(message)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
10
16
|
class Client
|
11
17
|
include Attune::Configurable
|
12
18
|
|
@@ -19,13 +25,44 @@ module Attune
|
|
19
25
|
# )
|
20
26
|
#
|
21
27
|
# @param [Hash] options Options for connection (see Attune::Configurable)
|
22
|
-
# @
|
28
|
+
# @return A new client object
|
23
29
|
def initialize(options={})
|
24
30
|
Attune::Configurable::KEYS.each do |key|
|
25
31
|
send("#{key}=", options[key] || Attune::Default.send(key))
|
26
32
|
end
|
27
33
|
end
|
28
34
|
|
35
|
+
# Request an auth token
|
36
|
+
#
|
37
|
+
# @example Generate a new auth token
|
38
|
+
# token = client.get_auth_token("client id", "secret")
|
39
|
+
# @param [String] client_id The client identifier.
|
40
|
+
# @param [String] client_secret The secret key for the client.
|
41
|
+
# @return An auth token if credentials accepted
|
42
|
+
# @raise [ArgumentError] if client_id or client_secret is not provided
|
43
|
+
# @raise [AuthenticationException] if client_id or client_secret are not accepted
|
44
|
+
# @raise [Faraday::Error::ClientError] if the request fails or exceeds the timeout
|
45
|
+
def get_auth_token(client_id, client_secret)
|
46
|
+
raise ArgumentError, "client_id required" unless client_id
|
47
|
+
raise ArgumentError, "client_secret required" unless client_secret
|
48
|
+
|
49
|
+
response = post_form("oauth/token",
|
50
|
+
client_id: client_id,
|
51
|
+
client_secret: client_secret,
|
52
|
+
grant_type: :client_credentials
|
53
|
+
)
|
54
|
+
if response
|
55
|
+
body = JSON.parse(response.body)
|
56
|
+
if body['error']
|
57
|
+
raise AuthenticationException, body['error_description']
|
58
|
+
end
|
59
|
+
body['access_token']
|
60
|
+
else
|
61
|
+
# Return a new UUID if there was an exception and we're in mock mode
|
62
|
+
SecureRandom.uuid
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
29
66
|
# Create an anonymous tracked user
|
30
67
|
#
|
31
68
|
# @example Generate a new id (preferred)
|
@@ -43,13 +80,14 @@ module Attune
|
|
43
80
|
# @return id [String]
|
44
81
|
# @raise [ArgumentError] if user_agent is not provided
|
45
82
|
# @raise [Faraday::Error::ClientError] if the request fails or exceeds the timeout
|
83
|
+
# @raise [AuthenticationException] if authorization header not accepted
|
46
84
|
def create_anonymous(options)
|
47
85
|
raise ArgumentError, "user_agent required" unless options[:user_agent]
|
48
86
|
if id = options[:id]
|
49
87
|
put("anonymous/#{id}", {user_agent: options[:user_agent]})
|
50
88
|
id
|
51
89
|
else
|
52
|
-
if response =
|
90
|
+
if response = post_json("anonymous", {user_agent: options[:user_agent]})
|
53
91
|
response[:location][/\Aurn:id:([a-z0-9\-]+)\Z/, 1]
|
54
92
|
else
|
55
93
|
# Return a new UUID if there was an exception and we're in mock mode
|
@@ -77,6 +115,7 @@ module Attune
|
|
77
115
|
# @return ranking [Array<String>] The entities in their ranked order
|
78
116
|
# @raise [ArgumentError] if required parameters are missing
|
79
117
|
# @raise [Faraday::Error::ClientError] if the request fails or exceeds the timeout
|
118
|
+
# @raise [AuthenticationException] if authorization header not accepted
|
80
119
|
def get_rankings(options)
|
81
120
|
qs = encoded_ranking_params(options)
|
82
121
|
if response = get("rankings/#{qs}", customer: options.fetch(:customer, 'none'))
|
@@ -107,6 +146,7 @@ module Attune
|
|
107
146
|
# @param [Array<Hash>] multi_options An array of options (see #get_rankings)
|
108
147
|
# @return [Array<Array<String>>] rankings
|
109
148
|
# @raise [Faraday::Error::ClientError] if the request fails or exceeds the timeout
|
149
|
+
# @raise [AuthenticationException] if authorization header not accepted
|
110
150
|
def multi_get_rankings(multi_options)
|
111
151
|
requests = multi_options.map do |options|
|
112
152
|
encoded_ranking_params(options)
|
@@ -139,6 +179,7 @@ module Attune
|
|
139
179
|
# 'cd171f7c-560d-4a62-8d65-16b87419a58'
|
140
180
|
# )
|
141
181
|
# @raise [Faraday::Error::ClientError] if the request fails or exceeds the timeout
|
182
|
+
# @raise [AuthenticationException] if authorization header not accepted
|
142
183
|
def bind(id, customer_id)
|
143
184
|
put("bindings/anonymous=#{id}&customer=#{customer_id}")
|
144
185
|
true
|
@@ -168,8 +209,18 @@ module Attune
|
|
168
209
|
handle_exception(e)
|
169
210
|
end
|
170
211
|
|
171
|
-
def
|
172
|
-
adapter.post(path,
|
212
|
+
def post_form(path, params={})
|
213
|
+
adapter.post(path, params)
|
214
|
+
rescue Faraday::Error::ClientError => e
|
215
|
+
handle_exception(e)
|
216
|
+
end
|
217
|
+
|
218
|
+
def post_json(path, params={})
|
219
|
+
adapter.post do |req|
|
220
|
+
req.url path
|
221
|
+
req.headers['Content-Type'] = 'application/json'
|
222
|
+
req.body = ::JSON.dump(params)
|
223
|
+
end
|
173
224
|
rescue Faraday::Error::ClientError => e
|
174
225
|
handle_exception(e)
|
175
226
|
end
|
@@ -178,13 +229,19 @@ module Attune
|
|
178
229
|
if exception_handler == :mock
|
179
230
|
nil
|
180
231
|
else
|
181
|
-
|
232
|
+
if e.response && e.response[:status] == 401
|
233
|
+
raise AuthenticationException, e
|
234
|
+
else
|
235
|
+
raise e
|
236
|
+
end
|
182
237
|
end
|
183
238
|
end
|
184
239
|
|
185
240
|
def adapter
|
186
241
|
raise DisabledException if disabled?
|
187
|
-
Faraday.new(url: endpoint, builder: middleware, request: {timeout: timeout})
|
242
|
+
conn = Faraday.new(url: endpoint, builder: middleware, request: {timeout: timeout})
|
243
|
+
conn.authorization :Bearer, auth_token unless !auth_token
|
244
|
+
conn
|
188
245
|
end
|
189
246
|
end
|
190
247
|
end
|
data/lib/attune/configurable.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Attune
|
2
2
|
module Configurable
|
3
3
|
KEYS = [
|
4
|
+
:auth_token,
|
4
5
|
:endpoint,
|
5
6
|
:middleware,
|
6
7
|
:disabled,
|
@@ -8,6 +9,9 @@ module Attune
|
|
8
9
|
:timeout
|
9
10
|
]
|
10
11
|
|
12
|
+
# The Authorization token
|
13
|
+
attr_accessor :auth_token
|
14
|
+
|
11
15
|
# The HTTP endpoint to connect to
|
12
16
|
attr_accessor :endpoint
|
13
17
|
|
data/lib/attune/default.rb
CHANGED
data/lib/attune/version.rb
CHANGED
data/spec/attune/client_spec.rb
CHANGED
@@ -39,6 +39,15 @@ describe Attune::Client do
|
|
39
39
|
}.to raise_exception(Faraday::Error::ConnectionFailed)
|
40
40
|
stubs.verify_stubbed_calls
|
41
41
|
end
|
42
|
+
it "will raise AuthenticationException" do
|
43
|
+
stubs.post("oauth/token",
|
44
|
+
{:client_id=>"id", :client_secret=>"secret", grant_type: :client_credentials}
|
45
|
+
){ [200, {}, %[{"error":"invalid_client","error_description":"Bad client credentials"}]] }
|
46
|
+
expect {
|
47
|
+
client.get_auth_token("id", "secret")
|
48
|
+
}.to raise_exception(Attune::AuthenticationException)
|
49
|
+
stubs.verify_stubbed_calls
|
50
|
+
end
|
42
51
|
end
|
43
52
|
|
44
53
|
describe "disabled" do
|
@@ -52,6 +61,11 @@ describe Attune::Client do
|
|
52
61
|
end
|
53
62
|
context "with mock" do
|
54
63
|
let(:options){ {disabled: true, exception_handler: :mock} }
|
64
|
+
|
65
|
+
it "mocks get_auth_token" do
|
66
|
+
result = client.get_auth_token("id", "secret")
|
67
|
+
expect(result).to match(/^[a-z0-9\-]+$/)
|
68
|
+
end
|
55
69
|
it "mocks create_anonymous with an id" do
|
56
70
|
result = client.create_anonymous(id: '12345', user_agent: 'Mozilla/5.0')
|
57
71
|
expect(result).to eq('12345')
|
@@ -81,6 +95,15 @@ describe Attune::Client do
|
|
81
95
|
end
|
82
96
|
end
|
83
97
|
|
98
|
+
it "can get_auth_token requesting a token" do
|
99
|
+
stubs.post("oauth/token",
|
100
|
+
{:client_id=>"id", :client_secret=>"secret", grant_type: :client_credentials}
|
101
|
+
){ [200, {}, %[{"access_token":"secret-token","token_type":"bearer"}]] }
|
102
|
+
token = client.get_auth_token('id', 'secret')
|
103
|
+
|
104
|
+
expect(token).to eq('secret-token')
|
105
|
+
end
|
106
|
+
|
84
107
|
it "can create_anonymous generating an id" do
|
85
108
|
stubs.post("anonymous", %[{"user_agent":"Mozilla/5.0"}]){ [200, {location: 'urn:id:abcd123'}, nil] }
|
86
109
|
id = client.create_anonymous(user_agent: 'Mozilla/5.0')
|
@@ -104,7 +127,7 @@ describe Attune::Client do
|
|
104
127
|
end
|
105
128
|
|
106
129
|
it "can get_rankings" do
|
107
|
-
stubs.get("rankings/anonymous=abcd123&
|
130
|
+
stubs.get("rankings/anonymous=abcd123&entities=1001%2C%2C1002%2C%2C1003%2C%2C1004&entity_collection=products&ip=none&view=b%2Fmens-pants"){ [200, {}, %[{"ranking":["1004","1003","1002","1001"]}]] }
|
108
131
|
rankings = client.get_rankings(
|
109
132
|
id: 'abcd123',
|
110
133
|
view: 'b/mens-pants',
|
@@ -119,7 +142,7 @@ describe Attune::Client do
|
|
119
142
|
let(:req1){ CGI::escape 'anonymous=0cddbc0-6114-11e3-949a-0800200c9a66&view=b%2Fmens-pants&entity_collection=products&entities=1001%2C%2C1002%2C%2C1003%2C%2C1004&ip=none' }
|
120
143
|
let(:req2){ CGI::escape 'anonymous=0cddbc0-6114-11e3-949a-0800200c9a66&view=b%2Fmens-pants&entity_collection=products&entities=2001%2C%2C2002%2C%2C2003%2C%2C2004&ip=none' }
|
121
144
|
it "can multi_get_rankings" do
|
122
|
-
stubs.get("/rankings?ids=anonymous%3D0cddbc0-6114-11e3-949a-0800200c9a66%
|
145
|
+
stubs.get("/rankings?ids=anonymous%3D0cddbc0-6114-11e3-949a-0800200c9a66%26entities%3D1001%252C%252C1002%252C%252C1003%252C%252C1004%26entity_collection%3Dproducts%26ip%3Dnone%26view%3Db%252Fmens-pants&ids=anonymous%3D0cddbc0-6114-11e3-949a-0800200c9a66%26entities%3D2001%252C%252C2002%252C%252C2003%252C%252C2004%26entity_collection%3Dproducts%26ip%3Dnone%26view%3Db%252Fmens-pants") do
|
123
146
|
[200, {}, <<-JSON]
|
124
147
|
{
|
125
148
|
"errors": {},
|
@@ -24,6 +24,6 @@ describe Attune::JsonLogger do
|
|
24
24
|
response.status.should == 200
|
25
25
|
response.body.should == "foobar"
|
26
26
|
|
27
|
-
expect(logged).to
|
27
|
+
expect(logged).to match(/{"ref":null,"v":1,"protocol":"http","host":"example.com","path":"\/test","t":12345000,"r_id":"eaa45af2-efc3-45ef-90da-9bcb56758e77","status":200,"ua":"Faraday v\d+.\d+.\d+","method":"get","perf":{"total":120.0}}/)
|
28
28
|
end
|
29
29
|
end
|
data/spec/remote_spec.rb
CHANGED
@@ -2,8 +2,23 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe "remote requests" do
|
4
4
|
let(:endpoint){ ENV['REMOTE_ENDPOINT'] }
|
5
|
-
|
6
|
-
|
5
|
+
let(:auth_token){ ENV['AUTH_TOKEN'] }
|
6
|
+
|
7
|
+
let(:client_id) { ENV['CLIENT_ID'] }
|
8
|
+
let(:client_secret) { ENV['CLIENT_SECRET'] }
|
9
|
+
|
10
|
+
before do
|
11
|
+
pending "REMOTE_ENDPOINT required for remote spec" unless endpoint
|
12
|
+
pending "AUTH_TOKEN required for remote spec" unless auth_token
|
13
|
+
end
|
14
|
+
let!(:client){ Attune::Client.new(endpoint: endpoint, auth_token: auth_token) }
|
15
|
+
|
16
|
+
it "can request an auth_token given a client id and secret" do
|
17
|
+
pending "CLIENT_ID required for get_auth_token spec" unless client_id
|
18
|
+
pending "CLIENT_SECRET required for get_auth_token spec" unless client_secret
|
19
|
+
token = client.get_auth_token(client_id, client_secret)
|
20
|
+
token.should =~ /[a-z0-9\-]+/
|
21
|
+
end
|
7
22
|
|
8
23
|
it "can create an anonymous user" do
|
9
24
|
id = client.create_anonymous(user_agent: 'Mozilla/5.0')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attune
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Hawthorn
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-03-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - '>='
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
89
|
+
version: 3.1.1
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - '>='
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
96
|
+
version: 3.1.1
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: simplecov
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -158,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
158
|
version: '0'
|
159
159
|
requirements: []
|
160
160
|
rubyforge_project:
|
161
|
-
rubygems_version: 2.1
|
161
|
+
rubygems_version: 2.2.1
|
162
162
|
signing_key:
|
163
163
|
specification_version: 4
|
164
164
|
summary: Client for the Attune product ranking API.
|