rails-pipeline 1.1.1
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/MIT-LICENSE +20 -0
- data/README.md +227 -0
- data/Rakefile +27 -0
- data/bin/pipeline +138 -0
- data/bin/redis-to-ironmq.rb +20 -0
- data/lib/rails-pipeline.rb +34 -0
- data/lib/rails-pipeline/emitter.rb +121 -0
- data/lib/rails-pipeline/handlers/activerecord_crud.rb +35 -0
- data/lib/rails-pipeline/handlers/base_handler.rb +19 -0
- data/lib/rails-pipeline/handlers/logger.rb +13 -0
- data/lib/rails-pipeline/ironmq_publisher.rb +37 -0
- data/lib/rails-pipeline/ironmq_pulling_subscriber.rb +96 -0
- data/lib/rails-pipeline/ironmq_subscriber.rb +21 -0
- data/lib/rails-pipeline/pipeline_version.rb +40 -0
- data/lib/rails-pipeline/protobuf/encrypted_message.pb.rb +37 -0
- data/lib/rails-pipeline/protobuf/encrypted_message.proto +18 -0
- data/lib/rails-pipeline/redis_forwarder.rb +207 -0
- data/lib/rails-pipeline/redis_ironmq_forwarder.rb +12 -0
- data/lib/rails-pipeline/redis_publisher.rb +71 -0
- data/lib/rails-pipeline/sns_publisher.rb +62 -0
- data/lib/rails-pipeline/subscriber.rb +185 -0
- data/lib/rails-pipeline/symmetric_encryptor.rb +127 -0
- data/lib/rails-pipeline/version.rb +3 -0
- data/lib/tasks/rails-pipeline_tasks.rake +4 -0
- data/spec/emitter_spec.rb +141 -0
- data/spec/handlers/activerecord_crud_spec.rb +100 -0
- data/spec/handlers/logger_spec.rb +42 -0
- data/spec/ironmp_pulling_subscriber_spec.rb +98 -0
- data/spec/ironmq_publisher_spec.rb +37 -0
- data/spec/pipeline_version_spec.rb +35 -0
- data/spec/redis_forwarder_spec.rb +99 -0
- data/spec/redis_publisher_spec.rb +36 -0
- data/spec/sns_publisher_spec.rb +28 -0
- data/spec/subscriber_spec.rb +278 -0
- data/spec/symmetric_encryptor_spec.rb +21 -0
- metadata +175 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'pipeline_helper'
|
3
|
+
|
4
|
+
describe RailsPipeline::IronmqPublisher do
|
5
|
+
before do
|
6
|
+
@default_emitter = DefaultIronmqEmitter.new({foo: "baz"}, without_protection: true)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should call post for IronMQ" do
|
10
|
+
expect_any_instance_of(IronMQ::Queue).to receive(:post) { |instance, serialized_encrypted_data|
|
11
|
+
base64_decoded_data = Base64.strict_decode64(JSON.parse(serialized_encrypted_data)['payload'])
|
12
|
+
|
13
|
+
encrypted_data = RailsPipeline::EncryptedMessage.parse(base64_decoded_data)
|
14
|
+
|
15
|
+
serialized_payload = DefaultEmitter.decrypt(encrypted_data)
|
16
|
+
data = DefaultEmitter_1_0.parse(serialized_payload)
|
17
|
+
|
18
|
+
expect(instance.name).to eq("harrys-#{Rails.env}-v1-default_emitters")
|
19
|
+
expect(encrypted_data.type_info).to eq(DefaultEmitter_1_0.to_s)
|
20
|
+
expect(data.foo).to eq("baz")
|
21
|
+
}.once
|
22
|
+
@default_emitter.emit
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should actually publish message to IronMQ" do
|
26
|
+
@default_emitter.emit
|
27
|
+
@default_emitter.emit
|
28
|
+
@default_emitter.emit
|
29
|
+
@default_emitter.emit
|
30
|
+
@default_emitter.emit
|
31
|
+
@default_emitter.emit
|
32
|
+
@default_emitter.emit
|
33
|
+
@default_emitter.emit
|
34
|
+
@default_emitter.emit
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RailsPipeline::PipelineVersion do
|
4
|
+
context 'major' do
|
5
|
+
it "parses the string correctly" do
|
6
|
+
expect(RailsPipeline::PipelineVersion.new("1_0").major).to eq(1)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'minor' do
|
11
|
+
it "parses the string correctly" do
|
12
|
+
expect(RailsPipeline::PipelineVersion.new("1_3").minor).to eq(3)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'comparison' do
|
17
|
+
let(:v1_0) { RailsPipeline::PipelineVersion.new('1_0') }
|
18
|
+
let(:v2_0) { RailsPipeline::PipelineVersion.new('2_0') }
|
19
|
+
let(:v1_3) { RailsPipeline::PipelineVersion.new('1_3') }
|
20
|
+
let(:v1_10) { RailsPipeline::PipelineVersion.new('1_10') }
|
21
|
+
let(:v1_10bis) { RailsPipeline::PipelineVersion.new('1_10') }
|
22
|
+
|
23
|
+
it { expect(v1_0).to be < v2_0 }
|
24
|
+
it { expect(v1_3).to be > v1_0 }
|
25
|
+
it { expect(v1_10).to be < v2_0 }
|
26
|
+
it { expect(v1_10).to be > v1_3 }
|
27
|
+
it { expect(v1_10).to eq(v1_10bis) }
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#to_s' do
|
31
|
+
it "renders correctly" do
|
32
|
+
expect("#{RailsPipeline::PipelineVersion.new('2_1')}").to eq("2_1")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'pipeline_helper'
|
4
|
+
require 'forwarder_helper'
|
5
|
+
|
6
|
+
describe RailsPipeline::RedisForwarder do
|
7
|
+
before do
|
8
|
+
@emitter = DefaultRedisEmitter.new({foo: "baz"}, without_protection: true)
|
9
|
+
@redis_queue = @emitter._key
|
10
|
+
@forwarder = DummyRedisForwarder.new(@redis_queue)
|
11
|
+
@in_progress_queue = @forwarder._in_progress_queue
|
12
|
+
@redis = @emitter._redis
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
context "having one message on the queue" do
|
17
|
+
before do
|
18
|
+
@redis.del(@redis_queue)
|
19
|
+
@redis.del(@in_progress_queue)
|
20
|
+
expect(@redis.llen(@redis_queue)).to eq 0
|
21
|
+
@emitter.emit # emit a message
|
22
|
+
|
23
|
+
expect(@redis.llen(@redis_queue)).to eq 1
|
24
|
+
expect(@forwarder._processed).to eq 0
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should re-publish messages off the queue" do
|
28
|
+
|
29
|
+
# Spy on the publish method
|
30
|
+
expect(@forwarder).to receive(:publish).once { |topic, data|
|
31
|
+
expect(topic).to eq "harrys-#{Rails.env}-v1-default_emitters"
|
32
|
+
expect(data).to include "DefaultEmitter_1_0"
|
33
|
+
}
|
34
|
+
|
35
|
+
@forwarder.process_queue
|
36
|
+
|
37
|
+
# We should have processed the one message
|
38
|
+
expect(@redis.llen(@redis_queue)).to eq 0
|
39
|
+
expect(@forwarder._processed).to eq 1
|
40
|
+
|
41
|
+
# Just check that we can handle an empty queue OK
|
42
|
+
@forwarder.process_queue
|
43
|
+
expect(@redis.llen(@redis_queue)).to eq 0
|
44
|
+
expect(@forwarder._processed).to eq 1
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should have an in_progress message temporarily" do
|
48
|
+
# Inside the publish method, let's inspect the in_progress queue
|
49
|
+
expect(@forwarder).to receive(:publish).once { |topic, data|
|
50
|
+
expect(@redis.llen(@in_progress_queue)).to eq 1
|
51
|
+
expect(@redis.lrange(@in_progress_queue, 0, 1)[0]).to eq data
|
52
|
+
}
|
53
|
+
@forwarder.process_queue
|
54
|
+
expect(@redis.llen(@in_progress_queue)).to eq 0
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should re-queue failed messages" do
|
58
|
+
# Check what happens when publish() raises an exception...
|
59
|
+
expect(@forwarder).to receive(:publish).once.and_raise("dummy publishing error")
|
60
|
+
@forwarder.process_queue # will fail and put it back on the queue
|
61
|
+
expect(@redis.llen(@redis_queue)).to eq 1
|
62
|
+
expect(@redis.llen(@in_progress_queue)).to eq 0
|
63
|
+
expect(@forwarder._processed).to eq 0
|
64
|
+
|
65
|
+
# Now process again with no errors...
|
66
|
+
expect(@forwarder).to receive(:publish).once
|
67
|
+
@forwarder.process_queue # will fail and put it back on the queue
|
68
|
+
expect(@redis.llen(@redis_queue)).to eq 0
|
69
|
+
expect(@forwarder._processed).to eq 1
|
70
|
+
end
|
71
|
+
|
72
|
+
context "with an abandoned message" do
|
73
|
+
before do
|
74
|
+
@redis.rpoplpush(@redis_queue, @in_progress_queue)
|
75
|
+
expect(@redis.llen(@redis_queue)).to eq 0
|
76
|
+
expect(@redis.llen(@in_progress_queue)).to eq 1
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should re-queue timed-out in-progress messages" do
|
80
|
+
@forwarder.check_for_failures
|
81
|
+
expect(@redis.llen(@redis_queue)).to eq 1
|
82
|
+
expect(@redis.llen(@in_progress_queue)).to eq 0
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should check the in-progress queue at the right times" do
|
88
|
+
now = Time.now
|
89
|
+
expect(@forwarder).to receive(:check_for_failures).once
|
90
|
+
expect(@forwarder).to receive(:process_queue).once.and_call_original
|
91
|
+
@forwarder.run
|
92
|
+
Timecop.freeze(now + 1.second) {
|
93
|
+
expect(@forwarder).not_to receive(:check_for_failures)
|
94
|
+
expect(@forwarder).to receive(:process_queue).once.and_call_original
|
95
|
+
@forwarder.run
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
require_relative 'pipeline_helper'
|
4
|
+
|
5
|
+
describe RailsPipeline::RedisPublisher do
|
6
|
+
before do
|
7
|
+
@test_emitter = TestRedisEmitter.new({foo: "bar"}, without_protection: true)
|
8
|
+
@default_emitter = DefaultRedisEmitter.new({foo: "baz"}, without_protection: true)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should publish message to Redis" do
|
12
|
+
Redis.any_instance.should_receive(:lpush).once { |instance, key, serialized_encrypted_data|
|
13
|
+
key.should eql RailsPipeline::RedisPublisher.namespace
|
14
|
+
encrypted_data = RailsPipeline::EncryptedMessage.parse(serialized_encrypted_data)
|
15
|
+
expect(encrypted_data.type_info).to eq(DefaultEmitter_1_0.to_s)
|
16
|
+
serialized_payload = DefaultEmitter.decrypt(encrypted_data)
|
17
|
+
data = DefaultEmitter_1_0.parse(serialized_payload)
|
18
|
+
expect(data.foo).to eq("baz")
|
19
|
+
# message is encrypted, but we tested that in pipeline_emitter_spec
|
20
|
+
}
|
21
|
+
@default_emitter.emit
|
22
|
+
end
|
23
|
+
|
24
|
+
it "just print some timings" do
|
25
|
+
@default_emitter.emit
|
26
|
+
@default_emitter.emit
|
27
|
+
@default_emitter.emit
|
28
|
+
@default_emitter.emit
|
29
|
+
@default_emitter.emit
|
30
|
+
@default_emitter.emit
|
31
|
+
@default_emitter.emit
|
32
|
+
@default_emitter.emit
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'pipeline_helper'
|
3
|
+
|
4
|
+
describe RailsPipeline::SnsPublisher do
|
5
|
+
before do
|
6
|
+
@default_emitter = DefaultSnsEmitter.new({foo: "baz"}, without_protection: true)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should publish message to SNS" do
|
10
|
+
allow_any_instance_of(AWS::SNS::Topic).to receive(:publish) { |instance, message, options|
|
11
|
+
options[:subject].should eql "DefaultSnsEmitter-"
|
12
|
+
options[:sqs].should eql message
|
13
|
+
encrypted_data = RailsPipeline::EncryptedMessage.parse(message)
|
14
|
+
expect(encrypted_data.type_info).to eq(DefaultEmitter_1_0.to_s)
|
15
|
+
serialized_payload = DefaultEmitter.decrypt(encrypted_data)
|
16
|
+
data = DefaultEmitter_1_0.parse(serialized_payload)
|
17
|
+
expect(data.foo).to eq("baz")
|
18
|
+
# message is encrypted, but we tested that in pipeline_emitter_spec
|
19
|
+
}
|
20
|
+
@default_emitter.emit
|
21
|
+
end
|
22
|
+
|
23
|
+
# Skipped since I can't be bothered to set permissions in circle
|
24
|
+
skip "should actually send to sns" do
|
25
|
+
@default_emitter.emit
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'pipeline_helper'
|
4
|
+
|
5
|
+
describe RailsPipeline::Subscriber do
|
6
|
+
before do
|
7
|
+
@test_emitter = TestEmitter.new({foo: "bar"}, without_protection: true)
|
8
|
+
|
9
|
+
@test_message = @test_emitter.create_message("2_0", RailsPipeline::EncryptedMessage::EventType::CREATED)
|
10
|
+
@subscriber = TestSubscriber.new
|
11
|
+
TestSubscriber.handler_method_cache = {}
|
12
|
+
OtherSubscriber.handler_method_cache = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
context "when there is no compatible version registered for the message" do
|
17
|
+
before do
|
18
|
+
RailsPipeline::Subscriber.register(TestEmitter_2_0, TestModel)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should handle correct messages" do
|
22
|
+
expect(@subscriber).to receive(:handle_payload).once
|
23
|
+
@subscriber.handle_envelope(@test_message)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "when there is a compatible version registered for the message" do
|
28
|
+
before do
|
29
|
+
@test_emitter = TestEmitter.new({foo: "bar"}, without_protection: true)
|
30
|
+
RailsPipeline::Subscriber.stub(:registered_handlers){{}}
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should log the inability to process the message" do
|
34
|
+
expect(RailsPipeline.logger).to receive(:info).once
|
35
|
+
@subscriber.handle_envelope(@test_message)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should raise exception on malformed messages" do
|
40
|
+
@test_message = RailsPipeline::EncryptedMessage.new(salt: "jhkjehd", iv: "khdkjehdkejhdkjehdkjhed")
|
41
|
+
expect(@subscriber).not_to receive(:handle_payload)
|
42
|
+
expect{@subscriber.handle_envelope(@test_message)}.to raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'api_key' do
|
46
|
+
before do
|
47
|
+
@test_message = @test_emitter.create_message("2_0", RailsPipeline::EncryptedMessage::EventType::CREATED)
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'with wrong api key' do
|
51
|
+
it "should drop messages" do
|
52
|
+
@test_message.api_key = '123XYZ'
|
53
|
+
expect{@subscriber.handle_envelope(@test_message)}.to raise_error(RailsPipeline::Subscriber::WrongApiKeyError)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'with no api key' do
|
58
|
+
it "should drop messages" do
|
59
|
+
@test_message.api_key = nil
|
60
|
+
expect{@subscriber.handle_envelope(@test_message)}.to raise_error(RailsPipeline::Subscriber::NoApiKeyError)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'with a correct api key' do
|
65
|
+
it "should accept the envelope with correct api_key" do
|
66
|
+
stub_const('ENV', {'PIPELINE_API_KEYS' => '123XYZ'})
|
67
|
+
@test_message.api_key = '123XYZ'
|
68
|
+
expect{@subscriber.handle_envelope(@test_message)}.not_to raise_error
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should accept the envelope with any correct api_key" do
|
72
|
+
stub_const('ENV', {'PIPELINE_API_KEYS' => '123XYZ,456UVW'})
|
73
|
+
@test_message.api_key = '456UVW'
|
74
|
+
expect{@subscriber.handle_envelope(@test_message)}.not_to raise_error
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "with decrypted payload" do
|
80
|
+
before do
|
81
|
+
@payload_str = @subscriber.class.decrypt(@test_message)
|
82
|
+
clazz = Object.const_get(@test_message.type_info)
|
83
|
+
@payload = clazz.parse(@payload_str)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should get the version right" do
|
87
|
+
expect(@payload.class.name).to eq "TestEmitter_2_0"
|
88
|
+
version = @subscriber._version(@payload)
|
89
|
+
expect(version).to eq RailsPipeline::PipelineVersion.new("2_0")
|
90
|
+
end
|
91
|
+
|
92
|
+
context "with registered target class" do
|
93
|
+
before do
|
94
|
+
RailsPipeline::Subscriber.register(TestEmitter_2_0, TestModel)
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'without handler' do
|
98
|
+
it "should map to the right target" do
|
99
|
+
expect(@subscriber.target_class(@payload)).to eq TestModel
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should instantiate a target" do
|
103
|
+
expect(TestModel).to receive(:new).once.and_call_original
|
104
|
+
expect(TestModel).to receive(:from_pipeline_2_0).once.and_call_original
|
105
|
+
allow_any_instance_of(TestModel).to receive(:save!)
|
106
|
+
target = @subscriber.handle_payload(@payload, @test_message)
|
107
|
+
expect(target.foo).to eq @payload.foo
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'with a handler' do
|
112
|
+
before do
|
113
|
+
RailsPipeline::Subscriber.register(
|
114
|
+
TestEmitter_2_0, TestModel, RailsPipeline::SubscriberHandler::ActiveRecordCRUD)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should map to the correct handler' do
|
118
|
+
expect(@subscriber.target_handler(@payload)).to eq(RailsPipeline::SubscriberHandler::ActiveRecordCRUD)
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should call the correct handler' do
|
122
|
+
expect_any_instance_of(RailsPipeline::SubscriberHandler::ActiveRecordCRUD).to receive(:handle_payload).once
|
123
|
+
target = @subscriber.handle_payload(@payload, @test_message)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context "with a registered target Proc" do
|
129
|
+
before do
|
130
|
+
@called = false
|
131
|
+
RailsPipeline::Subscriber.register(TestEmitter_2_0, Proc.new {
|
132
|
+
@called = true
|
133
|
+
})
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should map to the right target" do
|
137
|
+
expect(@subscriber.target_class(@payload).is_a?(Proc)).to eq true
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should run the proc" do
|
141
|
+
@subscriber.handle_payload(@payload, @test_message)
|
142
|
+
expect(@called).to eq true
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
context "without registered target" do
|
148
|
+
before do
|
149
|
+
RailsPipeline::Subscriber.register(TestEmitter_2_0, nil)
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should not instantiate a target" do
|
153
|
+
@subscriber.handle_payload(@payload, @test_message)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe '#most_suitable_method' do
|
159
|
+
let(:subscriber) { TestSubscriber.new }
|
160
|
+
let(:fake_class) { Class.new }
|
161
|
+
before do
|
162
|
+
stub_const("TestClass", fake_class)
|
163
|
+
# Clear TestSubscriber cache
|
164
|
+
TestSubscriber.handler_method_cache = {}
|
165
|
+
end
|
166
|
+
|
167
|
+
context 'when receiver class has the correct version method' do
|
168
|
+
before do
|
169
|
+
TestClass.define_singleton_method(:from_pipeline_1_1) { }
|
170
|
+
end
|
171
|
+
let(:version) { RailsPipeline::PipelineVersion.new('1_1') }
|
172
|
+
it 'picks the method' do
|
173
|
+
subscriber.most_suitable_handler_method_name(version, TestClass).should eq(:from_pipeline_1_1)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context 'when receiver class has a handler with same major and lower minor handler method' do
|
178
|
+
before do
|
179
|
+
TestClass.define_singleton_method(:from_pipeline_1_0) { }
|
180
|
+
end
|
181
|
+
let(:version) { RailsPipeline::PipelineVersion.new('1_1') }
|
182
|
+
it 'picks the method' do
|
183
|
+
subscriber.most_suitable_handler_method_name(version, TestClass).should eq(:from_pipeline_1_0)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
context 'when receiver has multiple methods defined' do
|
188
|
+
before do
|
189
|
+
TestClass.define_singleton_method(:from_pipeline_1_0) { }
|
190
|
+
TestClass.define_singleton_method(:from_pipeline_1_5) { }
|
191
|
+
TestClass.define_singleton_method(:from_pipeline_1_2) { }
|
192
|
+
TestClass.define_singleton_method(:from_pipeline_1_1) { }
|
193
|
+
end
|
194
|
+
let(:version) { RailsPipeline::PipelineVersion.new('1_4') }
|
195
|
+
it 'picks the closest lower method' do
|
196
|
+
subscriber.most_suitable_handler_method_name(version, TestClass).should eq(:from_pipeline_1_2)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'when receiver has multiple methods defined' do
|
201
|
+
before do
|
202
|
+
TestClass.define_singleton_method(:from_pipeline_1_2) { }
|
203
|
+
TestClass.define_singleton_method(:from_pipeline_1_0) { }
|
204
|
+
TestClass.define_singleton_method(:from_pipeline_1_4) { }
|
205
|
+
TestClass.define_singleton_method(:from_pipeline_1_5) { }
|
206
|
+
end
|
207
|
+
let(:version) { RailsPipeline::PipelineVersion.new('1_4') }
|
208
|
+
it 'picks the closest lower method' do
|
209
|
+
subscriber.most_suitable_handler_method_name(version, TestClass).should eq(:from_pipeline_1_4)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context 'when receiver class does not have a handler method' do
|
214
|
+
let(:version) { RailsPipeline::PipelineVersion.new('1_1') }
|
215
|
+
it 'returns nil' do
|
216
|
+
subscriber.most_suitable_handler_method_name(version, TestClass).should be_nil
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context 'methods cache' do
|
222
|
+
let(:subscriber) { TestSubscriber.new }
|
223
|
+
let(:other_subscriber) { OtherSubscriber.new }
|
224
|
+
let(:version) { RailsPipeline::PipelineVersion.new('1_1') }
|
225
|
+
let(:fake_class) { Class.new }
|
226
|
+
before do
|
227
|
+
stub_const("TestClass", fake_class)
|
228
|
+
TestClass.define_singleton_method(:from_pipeline_1_0) { }
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'exists' do
|
232
|
+
TestSubscriber.handler_method_cache.should_not be_nil
|
233
|
+
end
|
234
|
+
|
235
|
+
context 'with empty cache' do
|
236
|
+
|
237
|
+
it 'caches method handler for version' do
|
238
|
+
subscriber.most_suitable_handler_method_name(version, TestClass)
|
239
|
+
TestSubscriber.handler_method_cache[version].should eq(:from_pipeline_1_0)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
context 'with non empty cache' do
|
244
|
+
before do
|
245
|
+
# warms the cache
|
246
|
+
subscriber.most_suitable_handler_method_name(version, TestClass)
|
247
|
+
end
|
248
|
+
|
249
|
+
it "reads value from cache" do
|
250
|
+
TestClass.should_not_receive(:methods)
|
251
|
+
TestSubscriber.handler_method_cache.should_receive(:[]).with(version).once.and_call_original
|
252
|
+
subscriber.most_suitable_handler_method_name(version, TestClass)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
context 'cache is attached to each class' do
|
257
|
+
let(:fake_class2) { Class.new }
|
258
|
+
before do
|
259
|
+
stub_const("TestClass2", fake_class2)
|
260
|
+
TestClass2.define_singleton_method(:from_pipeline_1_1) { }
|
261
|
+
TestClass2.define_singleton_method(:from_pipeline_2_0) { }
|
262
|
+
end
|
263
|
+
let(:v1_1) { RailsPipeline::PipelineVersion.new('1_1') }
|
264
|
+
let(:v2_0) { RailsPipeline::PipelineVersion.new('2_0') }
|
265
|
+
|
266
|
+
it 'caches methods in separate buckets' do
|
267
|
+
subscriber.most_suitable_handler_method_name(v1_1, TestClass)
|
268
|
+
other_subscriber.most_suitable_handler_method_name(v1_1, TestClass2)
|
269
|
+
other_subscriber.most_suitable_handler_method_name(v2_0, TestClass2)
|
270
|
+
TestSubscriber.handler_method_cache != OtherSubscriber.handler_method_cache
|
271
|
+
TestSubscriber.handler_method_cache.length.should eq(1)
|
272
|
+
OtherSubscriber.handler_method_cache.length.should eq(2)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|