routemaster-drain 1.0.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.
- checksums.yaml +7 -0
- data/.env.test +6 -0
- data/.gitignore +7 -0
- data/.gitmodules +3 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +16 -0
- data/.yardopts +6 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +122 -0
- data/Guardfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +261 -0
- data/Rakefile +1 -0
- data/lib/routemaster/cache.rb +133 -0
- data/lib/routemaster/config.rb +57 -0
- data/lib/routemaster/dirty/filter.rb +49 -0
- data/lib/routemaster/dirty/map.rb +63 -0
- data/lib/routemaster/dirty/state.rb +33 -0
- data/lib/routemaster/drain.rb +5 -0
- data/lib/routemaster/drain/basic.rb +40 -0
- data/lib/routemaster/drain/caching.rb +43 -0
- data/lib/routemaster/drain/mapping.rb +43 -0
- data/lib/routemaster/drain/terminator.rb +31 -0
- data/lib/routemaster/fetcher.rb +63 -0
- data/lib/routemaster/jobs/cache.rb +12 -0
- data/lib/routemaster/jobs/cache_and_sweep.rb +16 -0
- data/lib/routemaster/middleware/authenticate.rb +59 -0
- data/lib/routemaster/middleware/cache.rb +29 -0
- data/lib/routemaster/middleware/dirty.rb +33 -0
- data/lib/routemaster/middleware/filter.rb +26 -0
- data/lib/routemaster/middleware/parse.rb +43 -0
- data/lib/routemaster/middleware/root_post_only.rb +18 -0
- data/lib/routemaster/redis_broker.rb +42 -0
- data/routemaster-drain.gemspec +28 -0
- data/spec/routemaster/cache_spec.rb +115 -0
- data/spec/routemaster/dirty/filter_spec.rb +77 -0
- data/spec/routemaster/dirty/map_spec.rb +122 -0
- data/spec/routemaster/dirty/state_spec.rb +41 -0
- data/spec/routemaster/drain/basic_spec.rb +37 -0
- data/spec/routemaster/drain/caching_spec.rb +47 -0
- data/spec/routemaster/drain/mapping_spec.rb +51 -0
- data/spec/routemaster/drain/terminator_spec.rb +61 -0
- data/spec/routemaster/fetcher_spec.rb +56 -0
- data/spec/routemaster/middleware/authenticate_spec.rb +59 -0
- data/spec/routemaster/middleware/cache_spec.rb +35 -0
- data/spec/routemaster/middleware/dirty_spec.rb +33 -0
- data/spec/routemaster/middleware/filter_spec.rb +35 -0
- data/spec/routemaster/middleware/parse_spec.rb +69 -0
- data/spec/routemaster/middleware/root_post_only_spec.rb +30 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/events.rb +9 -0
- data/spec/support/rack_test.rb +23 -0
- data/spec/support/uses_dotenv.rb +11 -0
- data/spec/support/uses_redis.rb +15 -0
- data/spec/support/uses_webmock.rb +12 -0
- data/test.rb +17 -0
- metadata +247 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'spec/support/uses_redis'
|
3
|
+
require 'routemaster/dirty/filter'
|
4
|
+
|
5
|
+
describe Routemaster::Dirty::Filter do
|
6
|
+
uses_redis
|
7
|
+
|
8
|
+
def make_url(idx) ; "https://example.com/#{idx}" ; end
|
9
|
+
|
10
|
+
describe '#initialize' do
|
11
|
+
it 'passes without options' do
|
12
|
+
expect { described_class.new }.not_to raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'accepts :redis' do
|
16
|
+
expect { described_class.new(redis: double) }.not_to raise_error
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#run' do
|
21
|
+
let(:options) {{ redis: redis }}
|
22
|
+
subject { described_class.new(options) }
|
23
|
+
|
24
|
+
let(:result) { subject.run(payload) }
|
25
|
+
let(:payload) { [] }
|
26
|
+
|
27
|
+
context 'blank slate' do
|
28
|
+
%w(create delete update).each do |event|
|
29
|
+
let(:url) { make_url(1) }
|
30
|
+
|
31
|
+
it "keeps a '#{event}' event" do
|
32
|
+
event = { 'topic' => 'stuff', 'type' => event, 'url' => url, 't' => 1234 }
|
33
|
+
payload.push event
|
34
|
+
expect(result).to include(event)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'with a prior event' do
|
40
|
+
let(:url) { make_url(1) }
|
41
|
+
let(:prior_event) {{ 'topic' => 'stuff', 'type' => 'update', 'url' => url, 't' => 1234 }}
|
42
|
+
|
43
|
+
before { payload.push prior_event }
|
44
|
+
|
45
|
+
it "keeps a newer event" do
|
46
|
+
newer_event = { 'topic' => 'stuff', 'type' => 'update', 'url' => url, 't' => 1235 }
|
47
|
+
payload.push newer_event
|
48
|
+
expect(result).to eq([newer_event])
|
49
|
+
end
|
50
|
+
|
51
|
+
it "does keep an older event" do
|
52
|
+
older_event = { 'topic' => 'stuff', 'type' => 'update', 'url' => url, 't' => 1233 }
|
53
|
+
payload.push older_event
|
54
|
+
expect(result).to eq([prior_event])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'with a prior state' do
|
59
|
+
let(:url) { make_url(1) }
|
60
|
+
let(:prior_event) {{ 'topic' => 'stuff', 'type' => 'update', 'url' => url, 't' => 1234 }}
|
61
|
+
|
62
|
+
before { subject.run([prior_event]) }
|
63
|
+
|
64
|
+
it "keeps a newer event" do
|
65
|
+
newer_event = { 'topic' => 'stuff', 'type' => 'update', 'url' => url, 't' => 1235 }
|
66
|
+
payload.push newer_event
|
67
|
+
expect(result).to eq([newer_event])
|
68
|
+
end
|
69
|
+
|
70
|
+
it "does not keep an older event" do
|
71
|
+
older_event = { 'topic' => 'stuff', 'type' => 'update', 'url' => url, 't' => 1233 }
|
72
|
+
payload.push older_event
|
73
|
+
expect(result).to be_empty
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'spec/support/uses_redis'
|
3
|
+
require 'routemaster/dirty/map'
|
4
|
+
|
5
|
+
describe Routemaster::Dirty::Map do
|
6
|
+
uses_redis
|
7
|
+
|
8
|
+
subject { described_class.new(redis: redis) }
|
9
|
+
|
10
|
+
def url(idx) ; "https://example.com/#{idx}" ; end
|
11
|
+
|
12
|
+
def mark_urls(count)
|
13
|
+
1.upto(count) do |idx|
|
14
|
+
subject.mark(url(idx))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#mark' do
|
19
|
+
it 'passes' do
|
20
|
+
expect { subject.mark(url(1)) }.not_to raise_error
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns true if marking for the first time' do
|
24
|
+
expect(subject.mark(url(1))).to eq(true)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns false if re-marking' do
|
28
|
+
subject.mark(url(1))
|
29
|
+
expect(subject.mark(url(1))).to eq(false)
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'with a listener' do
|
33
|
+
let(:handler) { double }
|
34
|
+
before { subject.subscribe(handler, prefix: true) }
|
35
|
+
|
36
|
+
|
37
|
+
it 'broadcasts :dirty_entity on new mark' do
|
38
|
+
expect(handler).to receive(:on_dirty_entity).exactly(10).times
|
39
|
+
mark_urls(10)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'does not broadcast on re-marks' do
|
43
|
+
mark_urls(5)
|
44
|
+
expect(handler).to receive(:on_dirty_entity).exactly(5).times
|
45
|
+
mark_urls(10)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#sweep' do
|
51
|
+
it 'does not yield with no marks' do
|
52
|
+
expect { |b| subject.sweep(&b) }.not_to yield_control
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'yields marked URLs' do
|
56
|
+
mark_urls(3)
|
57
|
+
expect { |b| subject.sweep(&b) }.to yield_control.exactly(3).times
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'does not yield if called again' do
|
61
|
+
mark_urls(3)
|
62
|
+
subject.sweep { |url| true }
|
63
|
+
expect { |b| subject.sweep(&b) }.not_to yield_control
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'honours "next"' do
|
67
|
+
mark_urls(10)
|
68
|
+
subject.sweep { |url| next if url =~ /3/ ; true }
|
69
|
+
expect { |b| subject.sweep(&b) }.to yield_control.exactly(1).times
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'yields the same URL again if the block returns falsy' do
|
73
|
+
mark_urls(10)
|
74
|
+
subject.sweep { |url| url =~ /7/ ? false : true }
|
75
|
+
expect { |b| subject.sweep(&b) }.to yield_with_args(/7/)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'yields again if the block fails' do
|
79
|
+
mark_urls(1)
|
80
|
+
expect {
|
81
|
+
subject.sweep { |url| raise }
|
82
|
+
}.to raise_error(RuntimeError)
|
83
|
+
expect { |b| subject.sweep(&b) }.to yield_control.exactly(1).times
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe '#sweep_one' do
|
88
|
+
it 'takes one URL' do
|
89
|
+
mark_urls(3)
|
90
|
+
expect { |b| subject.sweep_one(url(1), &b) }.to yield_control.exactly(:once)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'processes exactly one URL' do
|
94
|
+
mark_urls(3)
|
95
|
+
subject.sweep_one(url(1)) { true }
|
96
|
+
expect(subject.count).to eq(2)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'does not sweep if block returns falsy' do
|
100
|
+
mark_urls(3)
|
101
|
+
subject.sweep_one(url(1)) { nil }
|
102
|
+
expect(subject.count).to eq(3)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe '#count' do
|
107
|
+
it 'is 0 by default' do
|
108
|
+
expect(subject.count).to eq(0)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'increases when marking' do
|
112
|
+
expect { mark_urls(10) }.to change { subject.count }.by(10)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'decreases when sweeping' do
|
116
|
+
mark_urls(10)
|
117
|
+
limit = 4
|
118
|
+
subject.sweep { |url| (limit -= 1) < 0 ? false : true }
|
119
|
+
expect(subject.count).to eq(6)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'spec/support/uses_redis'
|
3
|
+
require 'routemaster/dirty/state'
|
4
|
+
|
5
|
+
describe Routemaster::Dirty::State do
|
6
|
+
uses_redis
|
7
|
+
|
8
|
+
let(:argv) {[ 'https://example.com/1', 1234 ]}
|
9
|
+
subject { described_class.new(*argv) }
|
10
|
+
|
11
|
+
describe '#initialize' do
|
12
|
+
it 'accepts url, exists flag, timestamp in order' do
|
13
|
+
expect(subject.url).to eq('https://example.com/1')
|
14
|
+
expect(subject.t).to eq(1234)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '.get' do
|
19
|
+
it 'uses different keys for different URLs' do
|
20
|
+
keys = Set.new
|
21
|
+
allow(redis).to receive(:get) { |key| keys.add(key) ; nil }
|
22
|
+
described_class.get(redis, 'https://example.com/1')
|
23
|
+
described_class.get(redis, 'https://example.com/2')
|
24
|
+
expect(keys.size).to eq(2)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'return a null state when cache is empty' do
|
28
|
+
state = described_class.get(redis, 'https://example.com/1')
|
29
|
+
expect(state.url).to eq('https://example.com/1')
|
30
|
+
expect(state.t).to eq(0)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#save' do
|
35
|
+
it 'saves data that can be .get' do
|
36
|
+
subject.save(redis, 1)
|
37
|
+
loaded = described_class.get(redis, subject.url)
|
38
|
+
expect(loaded).to eq(subject)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'spec/support/rack_test'
|
3
|
+
require 'spec/support/uses_redis'
|
4
|
+
require 'spec/support/uses_dotenv'
|
5
|
+
require 'spec/support/events'
|
6
|
+
require 'routemaster/drain/basic'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
describe Routemaster::Drain::Basic do
|
10
|
+
uses_dotenv
|
11
|
+
uses_redis
|
12
|
+
|
13
|
+
let(:app) { described_class.new }
|
14
|
+
let(:listener) { double 'listener' }
|
15
|
+
|
16
|
+
before { app.subscribe(listener, prefix: true) }
|
17
|
+
|
18
|
+
let(:path) { '/' }
|
19
|
+
let(:payload) { [1,2,3,1].map { |idx| make_event(idx) }.to_json }
|
20
|
+
let(:environment) {{ 'CONTENT_TYPE' => 'application/json' }}
|
21
|
+
let(:perform) { post path, payload, environment }
|
22
|
+
|
23
|
+
before { authorize 'd3m0', 'x' }
|
24
|
+
|
25
|
+
it 'succeeds' do
|
26
|
+
perform
|
27
|
+
expect(last_response.status).to eq(204)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'emits events' do
|
31
|
+
expect(listener).to receive(:on_events_received) do |payload|
|
32
|
+
expect(payload.size).to eq(4)
|
33
|
+
end
|
34
|
+
perform
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'spec/support/rack_test'
|
3
|
+
require 'spec/support/uses_redis'
|
4
|
+
require 'spec/support/uses_dotenv'
|
5
|
+
require 'spec/support/events'
|
6
|
+
require 'routemaster/drain/caching'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
describe Routemaster::Drain::Caching do
|
10
|
+
uses_dotenv
|
11
|
+
uses_redis
|
12
|
+
|
13
|
+
let(:app) { described_class.new }
|
14
|
+
let(:listener) { double 'listener' }
|
15
|
+
|
16
|
+
before { app.subscribe(listener, prefix: true) }
|
17
|
+
|
18
|
+
let(:path) { '/' }
|
19
|
+
let(:payload) { [1,2,3,1].map { |idx| make_event(idx) }.to_json }
|
20
|
+
let(:environment) {{ 'CONTENT_TYPE' => 'application/json' }}
|
21
|
+
let(:perform) { post path, payload, environment }
|
22
|
+
|
23
|
+
before { authorize 'd3m0', 'x' }
|
24
|
+
|
25
|
+
it 'succeeds' do
|
26
|
+
perform
|
27
|
+
expect(last_response.status).to eq(204)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'emits events' do
|
31
|
+
expect(listener).to receive(:on_events_received) do |payload|
|
32
|
+
expect(payload.size).to eq(3)
|
33
|
+
end
|
34
|
+
perform
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'busts the cache' do
|
38
|
+
expect_any_instance_of(Routemaster::Cache).to receive(:bust).exactly(3).times
|
39
|
+
perform
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'schedules caching jobs' do
|
43
|
+
expect(Resque).to receive(:enqueue_to).exactly(3).times
|
44
|
+
perform
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'spec/support/rack_test'
|
3
|
+
require 'spec/support/uses_redis'
|
4
|
+
require 'spec/support/uses_dotenv'
|
5
|
+
require 'spec/support/events'
|
6
|
+
require 'routemaster/drain/mapping'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
describe Routemaster::Drain::Mapping do
|
10
|
+
uses_dotenv
|
11
|
+
uses_redis
|
12
|
+
|
13
|
+
let(:app) { described_class.new }
|
14
|
+
let(:listener) { double 'listener' }
|
15
|
+
|
16
|
+
before { app.subscribe(listener, prefix: true) }
|
17
|
+
|
18
|
+
let(:path) { '/' }
|
19
|
+
let(:payload) { [1,2,3,1].map { |idx| make_event(idx) }.to_json }
|
20
|
+
let(:environment) {{ 'CONTENT_TYPE' => 'application/json' }}
|
21
|
+
let(:perform) { post path, payload, environment }
|
22
|
+
|
23
|
+
before { authorize 'd3m0', 'x' }
|
24
|
+
|
25
|
+
it 'succeeds' do
|
26
|
+
perform
|
27
|
+
expect(last_response.status).to eq(204)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'filters events' do
|
31
|
+
expect(listener).to receive(:on_events_received) do |payload|
|
32
|
+
expect(payload.size).to eq(3) # the second 1 is filtered
|
33
|
+
end
|
34
|
+
perform
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:map) { Routemaster::Dirty::Map.new }
|
38
|
+
|
39
|
+
it 'uses dirty map' do
|
40
|
+
payload1 = [1,2,3,1].map { |idx| make_event(idx) }.to_json
|
41
|
+
payload2 = [1,4,5,2].map { |idx| make_event(idx) }.to_json
|
42
|
+
|
43
|
+
post path, payload1, environment
|
44
|
+
expect(map.count).to eq(3)
|
45
|
+
map.sweep_one("https://example.com/stuff/1") { true }
|
46
|
+
|
47
|
+
post path, payload2, environment
|
48
|
+
expect(map.count).to eq(4) # 2, 3, 4, 5 (1 is a repeat event, filtered out)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'spec/support/rack_test'
|
3
|
+
require 'spec/support/events'
|
4
|
+
require 'routemaster/drain/terminator'
|
5
|
+
|
6
|
+
describe Routemaster::Drain::Terminator do
|
7
|
+
let(:app) { described_class.new }
|
8
|
+
let(:listener) { double }
|
9
|
+
|
10
|
+
let(:perform) do
|
11
|
+
post '/whatever', '', 'routemaster.payload' => payload
|
12
|
+
end
|
13
|
+
|
14
|
+
before do
|
15
|
+
app.subscribe(listener, prefix: true)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
context 'when a payload is present' do
|
20
|
+
let(:payload) { [double('event')] }
|
21
|
+
|
22
|
+
it 'responds 204' do
|
23
|
+
perform
|
24
|
+
expect(last_response.status).to eq(204)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'broadcasts :events_received' do
|
28
|
+
expect(listener).to receive(:on_events_received).with(payload)
|
29
|
+
perform
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when a payload is present but empty' do
|
34
|
+
let(:payload) { [] }
|
35
|
+
|
36
|
+
it 'responds 204' do
|
37
|
+
perform
|
38
|
+
expect(last_response.status).to eq(204)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'does not broadcast :events_received' do
|
42
|
+
expect(listener).not_to receive(:on_events_received)
|
43
|
+
perform
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'when there is no payload' do
|
48
|
+
let(:payload) { nil }
|
49
|
+
|
50
|
+
it 'responds 400' do
|
51
|
+
perform
|
52
|
+
expect(last_response.status).to eq(400)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'does not broadcast' do
|
56
|
+
expect(listener).not_to receive(:on_events_received)
|
57
|
+
perform
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'spec/support/uses_dotenv'
|
3
|
+
require 'spec/support/uses_webmock'
|
4
|
+
require 'routemaster/fetcher'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
describe Routemaster::Fetcher do
|
8
|
+
uses_dotenv
|
9
|
+
uses_webmock
|
10
|
+
|
11
|
+
describe '.get' do
|
12
|
+
let(:url) { 'https://example.com/widgets/132' }
|
13
|
+
let(:headers) {{}}
|
14
|
+
subject { described_class.get(url, headers: headers) }
|
15
|
+
|
16
|
+
before do
|
17
|
+
@req = stub_request(:get, /example\.com/).to_return(
|
18
|
+
status: 200,
|
19
|
+
body: { id: 132, type: 'widget' }.to_json,
|
20
|
+
headers: {
|
21
|
+
'content-type' => 'application/json;v=1'
|
22
|
+
}
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'GETs from the URL' do
|
27
|
+
subject
|
28
|
+
expect(@req).to have_been_requested
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'has :status, :headers, :body' do
|
32
|
+
expect(subject.status).to eq(200)
|
33
|
+
expect(subject.headers).to have_key('content-type')
|
34
|
+
expect(subject.body).not_to be_nil
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'mashifies body' do
|
38
|
+
expect(subject.body.id).to eq(132)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'uses auth' do
|
42
|
+
subject
|
43
|
+
assert_requested(:get, /example/) do |req|
|
44
|
+
expect(req.uri.userinfo).to eq('username:s3cr3t')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'passes headers' do
|
49
|
+
headers['x-custom-header'] = 'why do you even'
|
50
|
+
subject
|
51
|
+
assert_requested(:get, /example/) do |req|
|
52
|
+
expect(req.headers).to include('X-Custom-Header')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|