wcc-data 0.1.0

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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.env.example +3 -0
  3. data/.gitignore +18 -0
  4. data/.rspec +2 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +29 -0
  8. data/Rakefile +36 -0
  9. data/circle.yml +6 -0
  10. data/lib/wcc/data.rb +26 -0
  11. data/lib/wcc/data/config.rb +45 -0
  12. data/lib/wcc/data/enumerated_type.rb +63 -0
  13. data/lib/wcc/data/faraday_client_app_token_auth.rb +64 -0
  14. data/lib/wcc/data/mapper.rb +16 -0
  15. data/lib/wcc/data/mapper/attributes.rb +54 -0
  16. data/lib/wcc/data/mapper/json_response.rb +20 -0
  17. data/lib/wcc/data/mapper/rest_configuration.rb +36 -0
  18. data/lib/wcc/data/mapper/rest_query.rb +28 -0
  19. data/lib/wcc/data/model.rb +11 -0
  20. data/lib/wcc/data/nucleus.rb +15 -0
  21. data/lib/wcc/data/nucleus/address.rb +17 -0
  22. data/lib/wcc/data/nucleus/base.rb +6 -0
  23. data/lib/wcc/data/nucleus/campus.rb +47 -0
  24. data/lib/wcc/data/nucleus/client_app_token.rb +30 -0
  25. data/lib/wcc/data/nucleus/contact.rb +33 -0
  26. data/lib/wcc/data/nucleus/gender.rb +26 -0
  27. data/lib/wcc/data/nucleus/group.rb +18 -0
  28. data/lib/wcc/data/nucleus/marital_status.rb +41 -0
  29. data/lib/wcc/data/nucleus/user.rb +49 -0
  30. data/lib/wcc/data/rack_client_app_token_auth.rb +77 -0
  31. data/lib/wcc/data/response.rb +29 -0
  32. data/lib/wcc/data/rest_endpoint.rb +33 -0
  33. data/lib/wcc/data/service.rb +55 -0
  34. data/lib/wcc/data/version.rb +5 -0
  35. data/spec/spec_helper.rb +22 -0
  36. data/spec/support/inheritable_class_attribute_examples.rb +16 -0
  37. data/spec/wcc/data/config_spec.rb +113 -0
  38. data/spec/wcc/data/enumerated_type_spec.rb +176 -0
  39. data/spec/wcc/data/faraday_client_app_token_auth_spec.rb +224 -0
  40. data/spec/wcc/data/mapper/attributes_spec.rb +94 -0
  41. data/spec/wcc/data/mapper/json_response_spec.rb +50 -0
  42. data/spec/wcc/data/mapper/rest_configuration_spec.rb +43 -0
  43. data/spec/wcc/data/mapper/rest_query_spec.rb +50 -0
  44. data/spec/wcc/data/model_spec.rb +33 -0
  45. data/spec/wcc/data/nucleus/campus_spec.rb +29 -0
  46. data/spec/wcc/data/rack_client_app_token_auth_spec.rb +219 -0
  47. data/spec/wcc/data/response_spec.rb +57 -0
  48. data/spec/wcc/data/rest_endpoint_spec.rb +71 -0
  49. data/spec/wcc/data/service_spec.rb +128 -0
  50. data/spec/wcc/data_spec.rb +25 -0
  51. data/wcc-data.gemspec +28 -0
  52. metadata +194 -0
@@ -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,50 @@
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 InvalidResponse error for non-json objects" do
45
+ expect { klass.new_from_response(double(json: 1, status: 200)) }
46
+ .to raise_error(WCC::Data::Mapper::InvalidResponse)
47
+ end
48
+ end
49
+
50
+ 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,29 @@
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
+
10
+ it "defines Dallas campus" do
11
+ dallas = described_class[:dallas]
12
+ expect(dallas).to be_a(described_class)
13
+ expect(dallas.name).to eq("Dallas")
14
+ end
15
+
16
+ it "defines Fort Worth campus" do
17
+ dallas = described_class[:ft_worth]
18
+ expect(dallas).to be_a(described_class)
19
+ expect(dallas.name).to eq("Fort Worth")
20
+ end
21
+
22
+ it "defines Plano campus" do
23
+ dallas = described_class[:plano]
24
+ expect(dallas).to be_a(described_class)
25
+ expect(dallas.name).to eq("Plano")
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,219 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe WCC::Data::RackClientAppTokenAuth do
4
+
5
+ describe WCC::Data::RackClientAppTokenAuth::RedisCache do
6
+ let(:connection) { instance_spy(Redis) }
7
+ let(:connection_lambda) { -> (&blk) { blk.call(connection) } }
8
+
9
+ describe "#initialize" do
10
+ it "requires a callable for Redis connection" do
11
+ expect { described_class.new }.to raise_error(ArgumentError)
12
+ obj = described_class.new(connection_lambda)
13
+ expect(obj.connection).to eq(connection_lambda)
14
+ end
15
+
16
+ it "allows setting a :cache_key option to change the token store" do
17
+ obj = described_class.new(connection_lambda, cache_key: "store")
18
+ expect(obj.cache_key).to eq("store")
19
+ end
20
+
21
+ it "allows setting the :cache_length" do
22
+ obj = described_class.new(connection_lambda, cache_length: 5)
23
+ expect(obj.cache_length).to eq(5)
24
+ end
25
+
26
+ it "defaults :cache_key to the value of the DEFAULT_CACHE_KEY constant" do
27
+ obj = described_class.new(connection_lambda)
28
+ expect(obj.cache_key).to eq(described_class::DEFAULT_CACHE_KEY)
29
+ end
30
+
31
+ it "defaults :cache_length to the value of DEFAULT_CACHE_LENGTH constant" do
32
+ obj = described_class.new(connection_lambda)
33
+ expect(obj.cache_length).to eq(described_class::DEFAULT_CACHE_LENGTH)
34
+ end
35
+ end
36
+
37
+ describe "#<<" do
38
+ subject(:auth) { described_class.new(connection_lambda, cache_key: "test", cache_length: 123) }
39
+
40
+ it "calls SET with @cache_key joined to token by a dot and set to '1'" do
41
+ auth << "token"
42
+ expect(connection).to have_received(:set).with("test.token", "1")
43
+ end
44
+
45
+ it "calls EXPIRE with the configured cache length" do
46
+ auth << "token"
47
+ expect(connection).to have_received(:expire).with("test.token", 123)
48
+ end
49
+ end
50
+
51
+ describe "#find" do
52
+ subject(:auth) { described_class.new(connection_lambda, cache_key: "test") }
53
+
54
+ it "returns the value from GET on the joined token key" do
55
+ expect(connection).to receive(:get).with("test.token").and_return("1")
56
+ expect(auth.find("token")).to eq("1")
57
+ end
58
+ end
59
+ end
60
+
61
+ describe "#initialize" do
62
+ it "takes an app argument and sets to @app" do
63
+ obj = described_class.new(:app)
64
+ expect(obj.app).to eq(:app)
65
+ end
66
+
67
+ it "takes a :cache option and sets to @cache" do
68
+ obj = described_class.new(:app, cache: :cache)
69
+ expect(obj.cache).to eq(:cache)
70
+ end
71
+
72
+ it "takes a :lookup_token and sets to @lookup_token" do
73
+ obj = described_class.new(:app, lookup_token: :lookup)
74
+ expect(obj.lookup_token).to eq(:lookup)
75
+ end
76
+
77
+ it "defaults :cache option to an instance of RedisCache" do
78
+ obj = described_class.new(:app)
79
+ expect(obj.cache).to be_a(described_class::RedisCache)
80
+ expect(obj.cache.connection).to eq(Sidekiq.method(:redis))
81
+ end
82
+
83
+ describe "default :lookup_token value" do
84
+ it "tries to fetch a token with the given value" do
85
+ expect(WCC::Data::Nucleus::ClientAppToken)
86
+ .to receive(:find).with("abc123").and_return(:val)
87
+ expect(described_class.new(:app).lookup_token.("abc123"))
88
+ .to eq(:val)
89
+ end
90
+
91
+ it "returns nil when InvalidResponse raised" do
92
+ allow(WCC::Data::Nucleus::ClientAppToken)
93
+ .to receive(:find).and_raise(WCC::Data::Mapper::InvalidResponse)
94
+ expect(described_class.new(:app).lookup_token.("abc123"))
95
+ .to be_nil
96
+ end
97
+
98
+ it "returns nil when RecordNotFound raised" do
99
+ allow(WCC::Data::Nucleus::ClientAppToken)
100
+ .to receive(:find).and_raise(WCC::Data::Mapper::RecordNotFound)
101
+ expect(described_class.new(:app).lookup_token.("abc123"))
102
+ .to be_nil
103
+ end
104
+ end
105
+
106
+ end
107
+
108
+ describe "#find" do
109
+ let(:cache) { instance_spy(described_class::RedisCache) }
110
+ subject(:auth) { described_class.new(:app, cache: cache) }
111
+
112
+ context "with token in the cache" do
113
+ before do
114
+ allow(auth.cache).to receive(:find).with("abc123").and_return("1")
115
+ end
116
+
117
+ it "calls find with token on the cache and then returns cached value" do
118
+ expect(auth.find("abc123"))
119
+ .to eq("1")
120
+ end
121
+
122
+ it "does not call lookup_token" do
123
+ expect(auth.lookup_token).to_not receive(:call)
124
+ auth.find("abc123")
125
+ end
126
+ end
127
+
128
+ context "with an empty cache" do
129
+ before do
130
+ allow(cache).to receive(:find).and_return(nil)
131
+ end
132
+
133
+ it "calls lookup_token with the token" do
134
+ expect(auth.lookup_token)
135
+ .to receive(:call).with("abc123").and_return(true)
136
+ expect(auth.find("abc123")).to eq(true)
137
+ end
138
+
139
+ context "with a valid token" do
140
+ before do
141
+ allow(auth.lookup_token).to receive(:call).and_return(true)
142
+ end
143
+
144
+ it "shovels the value into the cache" do
145
+ expect(cache).to receive(:<<).with("abc123")
146
+ auth.find("abc123")
147
+ end
148
+
149
+ it "returns a truthy value" do
150
+ expect(auth.find("abc123")).to be_truthy
151
+ end
152
+ end
153
+
154
+ context "with an invalid token" do
155
+ before do
156
+ allow(auth.lookup_token).to receive(:call).and_return(false)
157
+ end
158
+
159
+ it "does not shovel value into the cache" do
160
+ expect(auth.cache).to_not receive(:<<)
161
+ auth.find("abc123")
162
+ end
163
+
164
+ it "returns a falsey value" do
165
+ expect(auth.find("abc123")).to be_falsey
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ describe "#call" do
172
+ subject(:auth) { described_class.new(-> (env) {}, cache: cache) }
173
+ let(:cache) { instance_spy(described_class::RedisCache) }
174
+ let(:env) {
175
+ {
176
+ "HTTP_AUTHORIZATION" => "Bearer abc123",
177
+ }
178
+ }
179
+
180
+
181
+ it "passes the bearer token onto the find method" do
182
+ expect(auth).to receive(:find).with("abc123")
183
+ auth.call(env)
184
+ end
185
+
186
+ context "truthy find result" do
187
+ before do
188
+ allow(auth).to receive(:find).and_return(true)
189
+ end
190
+
191
+ it "passes the request on to the app" do
192
+ expect(auth.app).to receive(:call).with(env)
193
+ auth.call(env)
194
+ end
195
+
196
+ it "returns the value of app" do
197
+ allow(auth.app).to receive(:call).and_return(:value)
198
+ expect(auth.call(env)).to eq(:value)
199
+ end
200
+ end
201
+
202
+ context "falsey find result" do
203
+ before do
204
+ allow(auth).to receive(:find).and_return(false)
205
+ end
206
+
207
+ it "does not call app" do
208
+ expect(auth.app).to_not receive(:call)
209
+ auth.call(env)
210
+ end
211
+
212
+ it "returns a 403 response" do
213
+ response = auth.call(env)
214
+
215
+ expect(response).to eq([403, {}, '{"error":"Invalid Bearer Token"}'])
216
+ end
217
+ end
218
+ end
219
+ end