wcc-data 0.3.9 → 0.4.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,237 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe WCC::Data::FaradayClientAppTokenAuth do
4
+ let(:app) { spy(:application) }
5
+
6
+ describe WCC::Data::FaradayClientAppTokenAuth::RedisCache do
7
+ let(:connection) { instance_spy(Redis) }
8
+ let(:connection_lambda) { -> (&blk) { blk.call(connection) } }
9
+
10
+ describe "#initialize" do
11
+ it "requires a callable for Redis connection" do
12
+ expect { described_class.new }.to raise_error(ArgumentError)
13
+ obj = described_class.new(connection_lambda)
14
+ expect(obj.connection).to eq(connection_lambda)
15
+ end
16
+
17
+ it "allows setting a :cache_key option to change the token store" do
18
+ obj = described_class.new(connection_lambda, cache_key: "store")
19
+ expect(obj.cache_key).to eq("store")
20
+ end
21
+
22
+ it "defaults to the value of the DEFAULT_CACHE_KEY constant" do
23
+ obj = described_class.new(connection_lambda)
24
+ expect(obj.cache_key).to eq(described_class::DEFAULT_CACHE_KEY)
25
+ end
26
+ end
27
+
28
+ describe "#[]" do
29
+ subject(:cache) { described_class.new(connection_lambda) }
30
+
31
+ it "returns HGET on the cache_key for the specified field" do
32
+ expect(connection)
33
+ .to receive(:hget).with(cache.cache_key, "example.com").and_return("123abc")
34
+ expect(cache["example.com"]).to eq("123abc")
35
+ end
36
+ end
37
+
38
+ describe "#[]=" do
39
+ subject(:cache) { described_class.new(connection_lambda) }
40
+
41
+ it "does HSET on the cache_key for the specified field and value" do
42
+ cache["example.com"] = "abc123"
43
+ expect(connection)
44
+ .to have_received(:hset).with(cache.cache_key, "example.com", "abc123")
45
+ end
46
+
47
+ context "with a nil value" do
48
+ it "does HDEL on the cache_key for the specified field" do
49
+ cache["example.com"] = nil
50
+ expect(connection)
51
+ .to have_received(:hdel).with(cache.cache_key, "example.com")
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "#initialize" do
58
+ it "takes an app as param one and sets to @app" do
59
+ obj = described_class.new(:app)
60
+ expect(obj.send(:instance_variable_get, :@app)).to eq(:app)
61
+ end
62
+
63
+ it "takes a :cache option" do
64
+ obj = described_class.new(:app, cache: :cache)
65
+ expect(obj.cache).to eq(:cache)
66
+ end
67
+
68
+ it "takes a :token_factory option" do
69
+ obj = described_class.new(:app, token_factory: :factory)
70
+ expect(obj.token_factory).to eq(:factory)
71
+ end
72
+
73
+ it "defaults :cache option to an instance of RedisCache" do
74
+ obj = described_class.new(:app)
75
+ expect(obj.cache).to be_a(described_class::RedisCache)
76
+ expect(obj.cache.connection).to eq(Sidekiq.method(:redis))
77
+ end
78
+
79
+ it "defaults :token_factory option to a callable" do
80
+ obj = described_class.new(:app)
81
+ expect(obj.token_factory).to respond_to(:call)
82
+ end
83
+
84
+ describe "default token_factory" do
85
+ it "returns #token property from a Nucleus::ClientAppToken create" do
86
+ token = instance_spy(WCC::Data::Nucleus::ClientAppToken)
87
+
88
+ expect(WCC::Data::Nucleus::ClientAppToken)
89
+ .to receive(:create).with(hostname: "example.com").and_return(token)
90
+ expect(token).to receive(:token).and_return("abc123")
91
+ expect(described_class.new(:app).token_factory.("example.com"))
92
+ .to eq("abc123")
93
+ end
94
+ end
95
+ end
96
+
97
+ describe "#call" do
98
+ let(:app) { spy(:application) }
99
+ subject(:auth) { described_class.new(app) }
100
+ let(:env) {
101
+ {
102
+ url: URI("http://example.com"),
103
+ request_headers: {},
104
+ }
105
+ }
106
+
107
+ before do
108
+ allow(auth)
109
+ .to receive(:token_for).with("example.com").and_return("abc123")
110
+ end
111
+
112
+ it "sets env's Authorization header using value from #token_for" do
113
+ auth.call(env)
114
+ expect(env[:request_headers]["Authorization"])
115
+ .to eq("Bearer abc123")
116
+ end
117
+
118
+ it "calls @app with env" do
119
+ expect(app).to receive(:call).with(env)
120
+ auth.call(env)
121
+ end
122
+
123
+ it "adds an on_complete handler to @app" do
124
+ allow(app).to receive(:call).and_return(app).ordered
125
+ expect(app).to receive(:on_complete).ordered
126
+ auth.call(env)
127
+ end
128
+
129
+ describe "on_complete handler" do
130
+ subject(:auth) { described_class.new(app, cache: cache) }
131
+ let(:cache) { instance_spy(described_class::RedisCache) }
132
+ let(:handler) {
133
+ handler = nil
134
+ allow(app).to receive(:call).and_return(app)
135
+ allow(app).to receive(:on_complete) { |&block| handler = block }
136
+
137
+ auth.call(env)
138
+
139
+ handler
140
+ }
141
+
142
+ it "removes value from cache when response is unsuccessful" do
143
+ expect(cache).to receive(:[]=).with("example.com", nil).exactly(2).times
144
+ handler.(status: 200)
145
+ handler.(status: 400)
146
+ handler.(status: 500)
147
+ end
148
+
149
+ it "retries the request after clearing the cache on a 403 response" do
150
+ expect(app).to receive(:call).with(env).ordered
151
+ expect(cache).to receive(:[]=).with("example.com", nil).ordered
152
+ expect(app).to receive(:call).with(env).ordered
153
+ handler.(status: 403)
154
+ end
155
+ end
156
+
157
+ class FakeApp
158
+ attr_accessor :response
159
+ def initialize(response)
160
+ @response = response
161
+ end
162
+ def call(request)
163
+ @request = request
164
+ self
165
+ end
166
+ def on_complete
167
+ @request[:body] = "response"
168
+ yield(response)
169
+ end
170
+ end
171
+
172
+ describe "multiple 403 errors" do
173
+ subject(:auth) { described_class.new(app, cache: cache, token_factory: -> (_) { "value" }) }
174
+ let(:cache) { instance_spy(described_class::RedisCache) }
175
+ let(:app) { FakeApp.new(body: "response", status: 403) }
176
+
177
+ it "doesn't retry forever" do
178
+ auth.call(env)
179
+ end
180
+
181
+ it "resets the body to original request body" do
182
+ expect(app).to receive(:call).with(hash_including(body: "request")).twice.and_call_original
183
+ auth.call(env.merge(body: "request"))
184
+ end
185
+ end
186
+ end
187
+
188
+ describe "#token_for" do
189
+ let(:cache) { instance_spy(described_class::RedisCache) }
190
+ subject(:auth) { described_class.new(:app, cache: cache) }
191
+
192
+ context "when cache is non-nil" do
193
+ before do
194
+ allow(cache).to receive(:[]).with("example.com").and_return("abc123")
195
+ end
196
+
197
+ it "returns value from cache" do
198
+ expect(auth.token_for("example.com")).to eq("abc123")
199
+ end
200
+
201
+ it "does not call @token_factory" do
202
+ expect(auth.token_factory).to_not receive(:call)
203
+ auth.token_for("example.com")
204
+ end
205
+ end
206
+
207
+ context "when cache is empty" do
208
+ before do
209
+ allow(cache).to receive(:[]).and_return(nil)
210
+ allow(auth.token_factory)
211
+ .to receive(:call).with("example.com").and_return("abc123")
212
+ end
213
+
214
+ it "calls @token_factory with passed host" do
215
+ expect(auth.token_for("example.com")).to eq("abc123")
216
+ end
217
+
218
+ it "stores the value in the cache" do
219
+ expect(cache).to receive(:[]=).with("example.com", "abc123")
220
+ auth.token_for("example.com")
221
+ end
222
+
223
+ it "passes error up the chain when service unavailable" do
224
+ allow(auth.token_factory)
225
+ .to receive(:call)
226
+ .and_raise(WCC::Data::Mapper::ServiceUnavailable)
227
+
228
+ expect {
229
+ auth.call({
230
+ url: URI("http://example.com"),
231
+ request_headers: {},
232
+ })
233
+ }.to raise_error(WCC::Data::Mapper::ServiceUnavailable)
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ describe WCC::Data::Mapper::Attributes do
4
+ let(:klass) {
5
+ Class.new do
6
+ include WCC::Data::Mapper::Attributes
7
+ end
8
+ }
9
+
10
+ describe "::attribute" do
11
+ subject { klass }
12
+ it_behaves_like :inheritable_class_attributes, :attributes
13
+
14
+ it "takes a name and optional options and stores them in ::attributes" do
15
+ subject.attribute :name, foo: :bar
16
+ expect(subject.attributes).to eq("name" => { foo: :bar })
17
+ end
18
+
19
+ it "freezes the options hash" do
20
+ subject.attribute :name
21
+ expect { subject.attributes["name"][:foo] = "foo" }.to raise_error(RuntimeError)
22
+ end
23
+
24
+ it "defines an instance method by the same name" do
25
+ expect(subject.new).to_not respond_to(:name)
26
+ subject.attribute :name
27
+ expect(subject.new).to respond_to(:name)
28
+ end
29
+
30
+ context "defined instance method" do
31
+ it "returns the value set on the instance for this attribute" do
32
+ subject.attribute :name
33
+ obj = subject.new("name" => "foo")
34
+ expect(obj.name).to eq("foo")
35
+ end
36
+ end
37
+
38
+ context "with writer option" do
39
+ it "defines a writer instance method" do
40
+ subject.attribute :name, writer: true
41
+ obj = subject.new("name" => "foo")
42
+ obj.name = "bar"
43
+ expect(obj.name).to eq("bar")
44
+ end
45
+ end
46
+ end
47
+
48
+ describe "#initialize" do
49
+ subject { klass }
50
+
51
+ it "takes a hash of attributes and sets it to @attributes" do
52
+ obj = subject.new(foo: :bar)
53
+ expect(obj.attributes).to eq(foo: :bar)
54
+ end
55
+ end
56
+
57
+ describe "#[]" do
58
+ before(:each) do
59
+ klass.attribute :name
60
+ end
61
+ subject { klass.new("name" => "foo", "other" => :value) }
62
+
63
+ it "returns raw values of defined attributes" do
64
+ expect(subject[:name]).to eq("foo")
65
+ expect(subject["name"]).to eq("foo")
66
+ end
67
+
68
+ it "returns a key error for undefined attributes" do
69
+ expect { subject[:other] }.to raise_error(KeyError)
70
+ end
71
+ end
72
+
73
+ describe "#[]=" do
74
+ before(:each) do
75
+ klass.attribute :name
76
+ end
77
+ subject { klass.new("name" => "foo", "other" => :value) }
78
+
79
+ it "sets raw values of defined attributes" do
80
+ subject[:name] = "value1"
81
+ expect(subject[:name]).to eq("value1")
82
+ end
83
+
84
+ it "allows string values as key" do
85
+ subject["name"] = "value1"
86
+ expect(subject[:name]).to eq("value1")
87
+ end
88
+
89
+ it "returns a key error for undefined attributes" do
90
+ expect { subject[:other] = "value" }.to raise_error(KeyError)
91
+ end
92
+ end
93
+
94
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe WCC::Data::Mapper::JSONResponse do
4
+ let(:klass) {
5
+ Class.new do
6
+ extend WCC::Data::Mapper::JSONResponse
7
+
8
+ attr_accessor :val
9
+ def initialize(val=nil) @val = val end
10
+ end
11
+ }
12
+
13
+ describe "::new_from_response" do
14
+ let(:singular_response) {
15
+ double(json: { key: "value" }, status: 200)
16
+ }
17
+ let(:plural_response) {
18
+ double(json: [
19
+ { name: "abc" },
20
+ { name: "def" },
21
+ ], status: 200)
22
+ }
23
+
24
+ it "returns a new instance from singular response" do
25
+ obj = klass.new_from_response(singular_response)
26
+ expect(obj).to be_a(klass)
27
+ expect(obj.val).to eq(singular_response.json)
28
+ end
29
+
30
+ it "returns array of instances from array response" do
31
+ obj = klass.new_from_response(plural_response)
32
+ expect(obj).to be_a(Array)
33
+ expect(obj[0].val).to eq(plural_response.json[0])
34
+ expect(obj[1].val).to eq(plural_response.json[1])
35
+ end
36
+
37
+ it "raises RecordNotFound when response status is 404" do
38
+ allow(singular_response).to receive(:status).and_return(404)
39
+ expect { klass.new_from_response(singular_response) }
40
+ .to raise_error(WCC::Data::Mapper::RecordNotFound)
41
+
42
+ end
43
+
44
+ it "raises ServiceUnavailable when response status is 503" do
45
+ allow(singular_response).to receive(:status).and_return(503)
46
+ expect { klass.new_from_response(singular_response) }
47
+ .to raise_error(WCC::Data::Mapper::ServiceUnavailable)
48
+
49
+ end
50
+
51
+ it "raises InvalidResponse error for non-json objects" do
52
+ expect { klass.new_from_response(double(json: 1, status: 200)) }
53
+ .to raise_error(WCC::Data::Mapper::InvalidResponse)
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe WCC::Data::Mapper::RESTConfiguration do
4
+ let(:klass) {
5
+ Class.new.tap do |klass|
6
+ klass.extend described_class
7
+ end
8
+ }
9
+ subject { klass }
10
+ before(:each) do
11
+ WCC::Data.config.applications[:rest_configuration_test].uri = "http://test.app/"
12
+ subject.set_endpoint(:rest_configuration_test, "bar/")
13
+ end
14
+ after(:each) do
15
+ WCC::Data.config.applications.delete(:rest_configuration_test)
16
+ end
17
+
18
+ describe "::set_endpoint" do
19
+ it_behaves_like :inheritable_class_attributes, :endpoint_config
20
+
21
+ it "sets @endpoint_config hash with values from args" do
22
+ expect(subject.endpoint_config).to eq(app: :rest_configuration_test, uri: "bar/")
23
+ end
24
+ end
25
+
26
+ describe "::endpoint" do
27
+ it "returns instance of RESTEndpoint" do
28
+ expect(subject.endpoint).to be_a(WCC::Data::RESTEndpoint)
29
+ end
30
+
31
+ it "sets service using appname and provided URI string" do
32
+ expect(subject.endpoint.service.uri).to eq(URI("http://test.app/bar/"))
33
+ end
34
+ end
35
+
36
+ context "lazy configuration" do
37
+ it "sets endpoint data only when endpoint is called" do
38
+ WCC::Data.config.applications[:rest_configuration_test].uri = "http://new.test.app/"
39
+ expect(subject.endpoint.service.uri).to eq(URI("http://new.test.app/bar/"))
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe WCC::Data::Mapper::RESTQuery do
4
+ let(:klass) {
5
+ Class.new.tap do |klass|
6
+ klass.extend described_class
7
+ end
8
+ }
9
+
10
+ let(:endpoint) { double(:endpoint) }
11
+ before(:each) do
12
+ allow(klass).to receive(:endpoint) { endpoint }
13
+ end
14
+
15
+ shared_examples_for :handles_undefined_endpoint do |method, args|
16
+ let(:endpoint) { nil }
17
+ it "raises an EndpointUndefined exception" do
18
+ expect { klass.send(method, *args) }.to raise_error(WCC::Data::Mapper::EndpointUndefined)
19
+ end
20
+ end
21
+
22
+ describe "::find" do
23
+ it_behaves_like :handles_undefined_endpoint, :find, :id_param
24
+
25
+ it "calls show(id) on defined endpoint and builds on response" do
26
+ expect(endpoint).to receive(:show).with(:id_param).and_return(:response)
27
+ expect(klass).to receive(:new_from_response).with(:response).and_return(:new_response)
28
+ expect(klass.find(:id_param)).to eq(:new_response)
29
+ end
30
+ end
31
+
32
+ describe "::list" do
33
+ it_behaves_like :handles_undefined_endpoint, :list, :params
34
+
35
+ it "calls index(params) on defined endpoint and builds on response" do
36
+ expect(endpoint).to receive(:index).with(params: :params).and_return(:response)
37
+ expect(klass).to receive(:new_from_response).with(:response).and_return(:new_response)
38
+ expect(klass.list(:params)).to eq(:new_response)
39
+ end
40
+
41
+ it "allows calling with no params" do
42
+ expect(endpoint).to receive(:index).with(params: {})
43
+ expect(klass).to receive(:new_from_response)
44
+ klass.list
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe WCC::Data::Model do
4
+
5
+ describe "inherited behavior" do
6
+ subject { described_class }
7
+
8
+ includes = [
9
+ WCC::Data::Mapper::Attributes,
10
+ ]
11
+
12
+ extensions = [
13
+ WCC::Data::Mapper::JSONResponse,
14
+ WCC::Data::Mapper::RESTConfiguration,
15
+ WCC::Data::Mapper::RESTQuery,
16
+ ]
17
+
18
+ includes.each do |mod|
19
+ it "includes #{mod}" do
20
+ expect(subject.ancestors).to include(mod)
21
+ end
22
+ end
23
+
24
+ extensions.each do |mod|
25
+ it "extends #{mod}" do
26
+ extended_modules = (class << subject; self; end).included_modules
27
+ expect(extended_modules).to include(mod)
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe WCC::Data::Nucleus::Campus do
4
+ it 'inherits EnumeratedType functionality' do
5
+ expect(described_class.ancestors).to include(WCC::Data::EnumeratedType)
6
+ end
7
+
8
+ context 'with defined data' do
9
+ it 'defines Dallas campus' do
10
+ campus = described_class[:dallas]
11
+ expect(campus).to be_a(described_class)
12
+ expect(campus.name).to eq('Dallas')
13
+ end
14
+
15
+ it 'defines Fort Worth campus' do
16
+ campus = described_class[:ft_worth]
17
+ expect(campus).to be_a(described_class)
18
+ expect(campus.name).to eq('Fort Worth')
19
+ end
20
+
21
+ it 'defines Plano campus' do
22
+ campus = described_class[:plano]
23
+ expect(campus).to be_a(described_class)
24
+ expect(campus.name).to eq('Plano')
25
+ end
26
+
27
+ it 'defines Frisco campus' do
28
+ campus = described_class[:frisco]
29
+ expect(campus).to be_a(described_class)
30
+ expect(campus.name).to eq('Frisco')
31
+ expect(campus.code).to eq('FRS')
32
+ end
33
+ end
34
+ end