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,33 @@
1
+ module WCC::Data
2
+
3
+ class RESTEndpoint
4
+ attr_reader :service
5
+
6
+ def initialize(args={})
7
+ @service = args[:service]
8
+ end
9
+
10
+ def index(options={})
11
+ service.get(options)
12
+ end
13
+
14
+ def show(id, options={})
15
+ service.get(options.merge(uri: id.to_s))
16
+ end
17
+
18
+ def create(attrs, options={})
19
+ service.post(options.merge(body: attrs))
20
+ end
21
+
22
+ def update(id, attrs, options={})
23
+ service.patch(options.merge(uri: id.to_s, body: attrs))
24
+ end
25
+
26
+ def destroy(id, options={})
27
+ service.delete(options.merge(uri: id.to_s))
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
@@ -0,0 +1,55 @@
1
+ module WCC::Data
2
+ class Service
3
+ attr_reader :uri, :connection
4
+
5
+ VERBS = Set.new(%i[get post put patch delete])
6
+
7
+ def initialize(args={})
8
+ @uri = URI(args.fetch(:uri) { "" })
9
+ @connection = args.fetch(:connection) { nil }
10
+ end
11
+
12
+ def merge(arg=nil)
13
+ right = WCC::Data.Service(arg)
14
+ self.class.new(
15
+ uri: merge_uris(uri, right.uri),
16
+ connection: right.connection || connection
17
+ )
18
+ end
19
+
20
+ VERBS.each do |verb|
21
+ define_method(verb) do |options|
22
+ Response.new(
23
+ connection.run_request(
24
+ verb,
25
+ merge_uris(uri, options.fetch(:uri) { "" }),
26
+ options[:body],
27
+ options[:headers]
28
+ ) do |request|
29
+ request.params.update(options[:params]) if options[:params]
30
+ end
31
+ )
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def merge_uris(uri1, uri2)
38
+ URI(uri1) + URI(uri2)
39
+ end
40
+
41
+ end
42
+
43
+ def self.Service(hash_or_object)
44
+ case hash_or_object
45
+ when Service
46
+ hash_or_object
47
+ when Hash
48
+ Service.new(hash_or_object)
49
+ when NilClass
50
+ Service.new
51
+ else
52
+ raise ArgumentError
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,5 @@
1
+ module WCC
2
+ module Data
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,22 @@
1
+ SPEC_DIR = File.dirname(__FILE__)
2
+ FIXTURES_DIR = File.join(SPEC_DIR, "fixtures")
3
+
4
+ $LOAD_PATH.unshift File.join(SPEC_DIR, "..", "lib")
5
+ $LOAD_PATH.unshift SPEC_DIR
6
+
7
+ require 'dotenv'
8
+ Dotenv.load
9
+
10
+ require 'wcc/data'
11
+ require 'sidekiq'
12
+
13
+ Dir[File.join(SPEC_DIR, "support", "*.rb")].each do |support_file|
14
+ require "support/#{File.basename(support_file, ".rb")}"
15
+ end
16
+
17
+ RSpec.configure do |config|
18
+ config.run_all_when_everything_filtered = true
19
+ config.filter_run :focus
20
+
21
+ config.order = 'random'
22
+ end
@@ -0,0 +1,16 @@
1
+ shared_examples_for :inheritable_class_attributes do |name|
2
+ let(:subclass) { Class.new(subject) }
3
+
4
+ it "inherited value is the same as parent class" do
5
+ subject.send(:instance_variable_set, :"@#{name}", foo: :bar)
6
+ expect(subject.send(name)).to eq(foo: :bar)
7
+ expect(subclass.send(name)).to eq(foo: :bar)
8
+ end
9
+
10
+ it "inherited reference is unique from the parent class" do
11
+ subject.send(:instance_variable_set, :"@#{name}", foo: :bar)
12
+ subclass.send(name)[:bar] = :baz
13
+ expect(subject.send(name)).to eq(foo: :bar)
14
+ end
15
+ end
16
+
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+
3
+ describe WCC::Data::Config::Application do
4
+ let(:unit) { WCC::Data::Config::Application }
5
+ subject { unit.new(:name) }
6
+
7
+ describe "#initialize" do
8
+ it "takes a single name symbol and stores in @name" do
9
+ obj = unit.new(:name)
10
+ expect(obj.name).to eq(:name)
11
+ end
12
+
13
+ it "takes a connection option and stores it in @connection" do
14
+ obj = unit.new(:name, connection: "foo")
15
+ expect(obj.connection).to eq("foo")
16
+ end
17
+
18
+ it "sets to a blank Faraday::Connection" do
19
+ obj = unit.new(:name)
20
+ expect(obj.connection).to be_a(Faraday::Connection)
21
+ end
22
+ end
23
+
24
+ describe "#uri=" do
25
+ it "converts a string to a URI and stores in @uri" do
26
+ subject.uri = "http://test"
27
+ expect(subject.uri).to be_a(URI)
28
+ expect(subject.uri.scheme).to eq("http")
29
+ end
30
+
31
+ it "uses the URI object if set with a URI" do
32
+ subject.uri = test_value = URI("http://test")
33
+ expect(subject.uri).to eq(test_value)
34
+ end
35
+ end
36
+
37
+ describe "#service" do
38
+ before(:each) do
39
+ subject.uri = "http://test.com/"
40
+ end
41
+
42
+ it "returns a service object based on the URI" do
43
+ obj = subject.service
44
+ expect(obj).to be_a(WCC::Data::Service)
45
+ expect(obj.uri).to eq(subject.uri)
46
+ end
47
+
48
+ it "passes the application connection through to the service" do
49
+ obj = subject.service
50
+ expect(obj.connection).to eq(subject.connection)
51
+ end
52
+ end
53
+
54
+ describe "@connection" do
55
+ it "gets and sets" do
56
+ subject.connection = "foo"
57
+ expect(subject.connection).to eq("foo")
58
+ end
59
+ end
60
+ end
61
+
62
+ describe WCC::Data::Config do
63
+ let(:unit) { WCC::Data::Config }
64
+ subject { unit.new }
65
+
66
+
67
+ describe "#applications" do
68
+ it "returns a hash" do
69
+ expect(subject.applications).to be_a(Hash)
70
+ end
71
+
72
+ it "defaults empty keys to an application" do
73
+ expect(subject.applications[:foo]).to be_a(WCC::Data::Config::Application)
74
+ end
75
+
76
+ it "contains a default entry for :nucleus" do
77
+ expect(subject.applications[:nucleus].uri).to eq(
78
+ URI(ENV['NUCLEUS_URL'])
79
+ )
80
+ end
81
+
82
+ context "with APP_CLIENT_ID and APP_CLIENT_SECRET ENV vars set" do
83
+ after do
84
+ ENV['APP_CLIENT_ID'] = ENV['APP_CLIENT_SECRET'] = nil
85
+ end
86
+
87
+ it "sets the connection's basic auth for nucleus" do
88
+ ENV['APP_CLIENT_ID'] = "test"
89
+ ENV['APP_CLIENT_SECRET'] = "value"
90
+
91
+ header = subject.applications[:nucleus].connection.headers["Authorization"]
92
+
93
+ expect(header)
94
+ .to eq(Faraday::Request::BasicAuthentication.header("test", "value"))
95
+ end
96
+ end
97
+
98
+ context "with APP_CLIENT_ID and APP_CLIENT_SECRET not both set" do
99
+ it "does not set the basic auth for nucleus" do
100
+ header = subject.applications[:nucleus].connection.headers["Authorization"]
101
+
102
+ expect(header).to be_nil
103
+ end
104
+ end
105
+ end
106
+
107
+ describe "#apps alias" do
108
+ it "aliases to #applications" do
109
+ expect(subject.apps).to eq(subject.applications)
110
+ end
111
+ end
112
+
113
+ end
@@ -0,0 +1,176 @@
1
+ require "spec_helper"
2
+
3
+ describe WCC::Data::EnumeratedType do
4
+
5
+ context "with configured attributes" do
6
+ let(:type) {
7
+ Class.new(described_class) do
8
+ attributes :test1, :test2
9
+ end
10
+ }
11
+ let(:object) { type.new(data) }
12
+ let(:data) {
13
+ {
14
+ test1: "test data 1",
15
+ test2: "test data 2",
16
+ ignored: "",
17
+ }
18
+ }
19
+
20
+ it "adds a method for all arguments passed" do
21
+ expect(object).to respond_to(:test1)
22
+ expect(object).to respond_to(:test2)
23
+ end
24
+
25
+ it "sets values from initialize hash" do
26
+ expect(object.test1).to eq("test data 1")
27
+ expect(object.test2).to eq("test data 2")
28
+ end
29
+
30
+ describe "::defined_attributes" do
31
+ it "should return attributes" do
32
+ expect(type.defined_attributes).to eq([:test1, :test2])
33
+ end
34
+ end
35
+ end
36
+
37
+ context "with child classes" do
38
+ let(:parent) {
39
+ Class.new(described_class) do
40
+ attributes :attr1
41
+ attributes :attr2
42
+ end
43
+ }
44
+ let(:child) {
45
+ Class.new(parent) do
46
+ attributes :attr3, :attr4
47
+ end
48
+ }
49
+ let(:parent_object) { parent.new(data) }
50
+ let(:child_object) { child.new(data) }
51
+ let(:data) {
52
+ {
53
+ attr1: "data1",
54
+ attr2: "data2",
55
+ attr3: "data3",
56
+ attr4: "data4",
57
+ }
58
+ }
59
+
60
+ describe "parent class" do
61
+ it "does not include child attributes" do
62
+ expect(parent_object).to_not respond_to(:attr3)
63
+ expect(parent_object).to_not respond_to(:attr4)
64
+ end
65
+
66
+ it "includes both attributes" do
67
+ expect(parent_object).to respond_to(:attr1)
68
+ expect(parent_object).to respond_to(:attr2)
69
+ end
70
+
71
+ it "sets both attributes on initialize" do
72
+ expect(parent_object.attr1).to eq("data1")
73
+ expect(parent_object.attr2).to eq("data2")
74
+ end
75
+ end
76
+
77
+ describe "child class" do
78
+ it "includes all defined_attributes" do
79
+ expect(child.defined_attributes).to eq([:attr1, :attr2, :attr3, :attr4])
80
+ end
81
+
82
+ it "includes parent attributes and child attributes readers" do
83
+ expect(child_object.attr1).to eq("data1")
84
+ expect(child_object.attr2).to eq("data2")
85
+ expect(child_object.attr3).to eq("data3")
86
+ expect(child_object.attr4).to eq("data4")
87
+ end
88
+ end
89
+
90
+ end
91
+
92
+ context "with db, matches?, and attributes configured" do
93
+ let(:type) {
94
+ Class.new(described_class) do
95
+ attributes :id, :name, :key
96
+
97
+ def matches?(value)
98
+ [id, key].include?(value)
99
+ end
100
+
101
+ def self.db
102
+ [
103
+ { id: 1, name: "One", key: :one },
104
+ { id: 2, name: "Two", key: :two },
105
+ { id: 3, name: "Three", key: :three },
106
+ { id: 4, name: "Four", key: :three },
107
+ ]
108
+ end
109
+ end
110
+ }
111
+
112
+ describe "::[]" do
113
+ it "returns an instance of type if match found" do
114
+ expect(type[1]).to be_a(type)
115
+ end
116
+
117
+ it "returns nil if no match found" do
118
+ expect(type[:nil]).to be_nil
119
+ end
120
+
121
+ it "returns first match when multiple matches present" do
122
+ expect(type[:three]).to eq(type.all[2])
123
+ end
124
+ end
125
+
126
+ describe "::all" do
127
+ it "returns instances of EnumeratedType for each item in db" do
128
+ expect(type.all.size).to eq(4)
129
+ expect(type.all[0]).to be_a(type)
130
+ expect(type.all[1]).to be_a(type)
131
+ expect(type.all[2]).to be_a(type)
132
+ expect(type.all[3]).to be_a(type)
133
+ end
134
+
135
+ it "sets attributes for each record properly" do
136
+ expect(type.all[0].id).to eq(1)
137
+ expect(type.all[0].name).to eq("One")
138
+ expect(type.all[0].key).to eq(:one)
139
+ expect(type.all[1].key).to eq(:two)
140
+ expect(type.all[2].key).to eq(:three)
141
+ end
142
+
143
+ it "caches the results" do
144
+ expect(type.all[0].object_id).to eq(type.all[0].object_id)
145
+ end
146
+ end
147
+
148
+ describe "::reset" do
149
+ it "clears out all and forces new object creation" do
150
+ first_id = type.all[0].object_id
151
+ type.reset
152
+ expect(type.all[0].object_id).to_not eq(first_id)
153
+ end
154
+ end
155
+ end
156
+
157
+ describe "#initialize" do
158
+ it "allows passing no argument" do
159
+ expect { described_class.new }.to_not raise_error
160
+ end
161
+ end
162
+
163
+ describe "::db" do
164
+ it "raises NotImplementedError with instructions" do
165
+ expect { described_class.db }.to raise_error(NotImplementedError, /subclass/i)
166
+ end
167
+ end
168
+
169
+ describe "#matches?" do
170
+ it "raises NotImplementedError with instructions" do
171
+ object = described_class.new({})
172
+ expect { object.matches?(:val) }.to raise_error(NotImplementedError, /subclass/i)
173
+ end
174
+ end
175
+
176
+ end
@@ -0,0 +1,224 @@
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
+ end
223
+ end
224
+ end