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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 71b45b8b01601ab2b240d6c5243a39c0991a23d9
4
- data.tar.gz: fd29c20cac7f27e6cb3aa00f9a6c8ec4c54070a9
3
+ metadata.gz: aa77fa0fcbd44e02a0b3a26e722a76f3e7fabbff
4
+ data.tar.gz: bc15acc01376c94db3192f81a90d114aa6040e91
5
5
  SHA512:
6
- metadata.gz: 830ae6660ed070894351cb69c13da91827fbb9abaa43a79377e527d1f8d339be1c6c20391280f4b9a315363c8cb7d24f344e6e9ccc0ba166d327651cbe9549f5
7
- data.tar.gz: b7dd011b6e0003d0fa5d7e7a69cb53666dd3773e1cd50de389e870bb928ba75d9a1e4506d73b4af124d37a8eeac464a1b14cf66bf7473241fe7bbc618201844b
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
- # @returns A new client object
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 = post("anonymous", {user_agent: options[:user_agent]})
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 post(path, params={})
172
- adapter.post(path, ::JSON.dump(params))
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
- raise e
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
@@ -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
 
@@ -16,6 +16,8 @@ module Attune
16
16
  # Log all requests
17
17
  builder.use Attune::CallDropping
18
18
 
19
+ builder.request :url_encoded
20
+
19
21
  # Allow one retry per request
20
22
  builder.request :retry, 1
21
23
 
@@ -1,3 +1,3 @@
1
1
  module Attune
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -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&view=b%2Fmens-pants&entity_collection=products&entities=1001%2C%2C1002%2C%2C1003%2C%2C1004&ip=none"){ [200, {}, %[{"ranking":["1004","1003","1002","1001"]}]] }
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%26view%3Db%252Fmens-pants%26entity_collection%3Dproducts%26entities%3D1001%252C%252C1002%252C%252C1003%252C%252C1004%26ip%3Dnone&ids=anonymous%3D0cddbc0-6114-11e3-949a-0800200c9a66%26view%3Db%252Fmens-pants%26entity_collection%3Dproducts%26entities%3D2001%252C%252C2002%252C%252C2003%252C%252C2004%26ip%3Dnone") do
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 eq %[{"ref":null,"v":1,"protocol":"http","host":"example.com","path":"/test","t":12345000,"r_id":"eaa45af2-efc3-45ef-90da-9bcb56758e77","status":200,"ua":"Faraday v0.8.8","method":"get","perf":{"total":120.0}}]
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
- before { pending "REMOTE_ENDPOINT required for remote spec" unless endpoint }
6
- let!(:client){ Attune::Client.new(endpoint: endpoint) }
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.3
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-01-07 00:00:00.000000000 Z
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: '0'
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: '0'
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.11
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.