attune 0.0.3 → 0.0.4

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.
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.