chasqui 0.9.3 → 1.0.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +60 -39
  4. data/Vagrantfile +15 -0
  5. data/lib/chasqui.rb +28 -55
  6. data/lib/chasqui/broker.rb +3 -3
  7. data/lib/chasqui/{multi_broker.rb → brokers/redis_broker.rb} +11 -11
  8. data/lib/chasqui/config.rb +26 -6
  9. data/lib/chasqui/queue_adapter.rb +14 -0
  10. data/lib/chasqui/queue_adapters/redis_queue_adapter.rb +51 -0
  11. data/lib/chasqui/subscriber.rb +56 -49
  12. data/lib/chasqui/subscriptions.rb +67 -0
  13. data/lib/chasqui/version.rb +1 -1
  14. data/lib/chasqui/worker.rb +81 -0
  15. data/spec/integration/pubsub_examples.rb +20 -20
  16. data/spec/integration/resque_spec.rb +1 -1
  17. data/spec/integration/setup/subscribers.rb +25 -9
  18. data/spec/integration/sidekiq_spec.rb +1 -1
  19. data/spec/lib/chasqui/{multi_broker_spec.rb → brokers/redis_broker_spec.rb} +54 -26
  20. data/spec/lib/chasqui/cli_spec.rb +1 -1
  21. data/spec/lib/chasqui/config_spec.rb +121 -0
  22. data/spec/lib/chasqui/fake_queue_adapter_spec.rb +5 -0
  23. data/spec/lib/chasqui/queue_adapters/redis_queue_adapter_spec.rb +73 -0
  24. data/spec/lib/chasqui/subscriber_spec.rb +95 -43
  25. data/spec/lib/chasqui/subscriptions_spec.rb +60 -0
  26. data/spec/lib/chasqui/worker_spec.rb +96 -0
  27. data/spec/lib/chasqui_spec.rb +32 -191
  28. data/spec/spec_helper.rb +1 -1
  29. data/spec/support/chasqui_spec_helpers.rb +28 -0
  30. data/spec/support/fake_queue_adapter.rb +3 -0
  31. data/spec/support/fake_subscriber.rb +2 -1
  32. data/spec/support/shared_examples/queue_adapter_examples.rb +13 -0
  33. metadata +25 -18
  34. data/lib/chasqui/subscription.rb +0 -53
  35. data/lib/chasqui/workers/resque_worker.rb +0 -25
  36. data/lib/chasqui/workers/sidekiq_worker.rb +0 -45
  37. data/lib/chasqui/workers/worker.rb +0 -34
  38. data/spec/lib/chasqui/subscription_spec.rb +0 -35
  39. data/spec/lib/chasqui/workers/resque_worker_spec.rb +0 -27
  40. data/spec/lib/chasqui/workers/sidekiq_worker_spec.rb +0 -34
@@ -3,7 +3,7 @@ require 'integration/pubsub_examples'
3
3
 
4
4
  if sidekiq_supported_ruby_version?
5
5
  describe "sidekiq integration", integration: true do
6
- include_examples 'pubsub', :sidekiq, :start_sidekiq_workers
6
+ include_examples 'pubsub', :start_sidekiq_workers
7
7
 
8
8
  def start_sidekiq_workers
9
9
  @pids << fork do
@@ -1,7 +1,22 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Chasqui::MultiBroker do
4
- let(:broker) { Chasqui::MultiBroker.new }
3
+ class Worker1
4
+ include Chasqui::Subscriber
5
+ subscribe channel: 'app1', queue: 'queue1'
6
+ end
7
+
8
+ class Worker2
9
+ include Chasqui::Subscriber
10
+ subscribe channel: 'app2', queue: 'queue2'
11
+ end
12
+
13
+ class Worker3
14
+ include Chasqui::Subscriber
15
+ subscribe channel: 'app1', queue: 'queue3'
16
+ end
17
+
18
+ describe Chasqui::RedisBroker do
19
+ let(:broker) { Chasqui::RedisBroker.new }
5
20
 
6
21
  before do
7
22
  reset_chasqui
@@ -12,14 +27,13 @@ describe Chasqui::MultiBroker do
12
27
 
13
28
  describe '#forward_event' do
14
29
  before do
15
- Chasqui.config.channel = 'app1'
16
- Chasqui.subscribe channel: 'app1', queue: 'queue1'
17
- Chasqui.subscribe channel: 'app2', queue: 'queue2'
18
- Chasqui.subscribe channel: 'app1', queue: 'queue3'
30
+ Chasqui.register Worker1
31
+ Chasqui.register Worker2
32
+ Chasqui.register Worker3
19
33
  end
20
34
 
21
35
  it 'places the event on all subscriber queues' do
22
- Chasqui.publish 'foo.bar', 'A'
36
+ Chasqui.publish 'app1', foo: 'bar'
23
37
  broker.forward_event
24
38
 
25
39
  expect(nnredis.llen(broker.inbox_queue)).to eq(0)
@@ -28,7 +42,12 @@ describe Chasqui::MultiBroker do
28
42
  expect(nnredis.llen('queue:queue2')).to eq(0)
29
43
  expect(nnredis.llen('queue:queue3')).to eq(1)
30
44
 
31
- event = { 'event' => 'foo.bar', 'channel' => 'app1', 'data' => ['A'], 'created_at' => Time.now.to_f.to_s }
45
+ event = {
46
+ 'channel' => 'app1',
47
+ 'payload' => [{ 'foo' => 'bar' }],
48
+ 'created_at' => Time.now.to_f.to_s,
49
+ 'retry' => true
50
+ }
32
51
 
33
52
  job1 = JSON.parse nnredis.lpop('queue:queue1')
34
53
  expect(job1['args']).to include(event)
@@ -43,13 +62,19 @@ describe Chasqui::MultiBroker do
43
62
  begin
44
63
  Timeout::timeout(1) do
45
64
  Chasqui.config.redis = Redis.new
46
- Chasqui.config.channel = 'app2'
47
- Chasqui.publish 'foo.bar', 'A'
65
+ Chasqui.publish 'app2', foo: 'bar'
48
66
 
49
67
  job = JSON.parse nnredis.blpop('queue:queue2')[1]
50
- expect(job).to include('class' => 'Chasqui::Subscriber__queue2')
51
- expect(job).to include('args' =>
52
- [{ 'event' => 'foo.bar', 'channel' => 'app2', 'data' => ['A'], 'created_at' => Time.now.to_f.to_s }])
68
+ expect(job).to include('class' => 'Chasqui::Workers::Worker2')
69
+
70
+ event = {
71
+ 'channel' => 'app2',
72
+ 'payload' => [{ 'foo' => 'bar' }],
73
+ 'created_at' => Time.now.to_f.to_s,
74
+ 'retry' => true
75
+ }
76
+
77
+ expect(job).to include('args' => [event])
53
78
  end
54
79
  ensure
55
80
  thread.kill
@@ -57,9 +82,8 @@ describe Chasqui::MultiBroker do
57
82
  end
58
83
 
59
84
  it "doesn't lose events if the broker fails" do
60
- Chasqui.config.channel = 'app2'
61
- Chasqui.publish 'foo', 'process'
62
- Chasqui.publish 'foo', 'keep in queue'
85
+ Chasqui.publish 'app2', 'process'
86
+ Chasqui.publish 'app2', 'keep in queue'
63
87
  allow(broker.redis).to receive(:smembers).and_raise(Redis::ConnectionError)
64
88
 
65
89
  expect(-> { broker.forward_event }).to raise_error(Redis::ConnectionError)
@@ -72,7 +96,11 @@ describe Chasqui::MultiBroker do
72
96
 
73
97
  job = JSON.parse nnredis.lpop('queue:queue2')
74
98
  expect(job['args']).to include(
75
- 'event' => 'foo', 'channel' => 'app2', 'data' => ['process'], 'created_at' => Time.now.to_f.to_s)
99
+ 'channel' => 'app2',
100
+ 'payload' => ['process'],
101
+ 'created_at' => Time.now.to_f.to_s,
102
+ 'retry' => true
103
+ )
76
104
  expect(nnredis.llen(broker.in_progress_queue)).to eq(0)
77
105
  end
78
106
 
@@ -84,33 +112,33 @@ describe Chasqui::MultiBroker do
84
112
 
85
113
  describe '#build_job' do
86
114
  it 'includes useful metadata' do
87
- event = { 'event' => 'foo', 'channel' => 'bar', 'data' => [] }
88
- job = JSON.parse broker.build_job('my-queue', event)
115
+ event = { 'channel' => 'fox4', 'payload' => [] }
116
+ job = JSON.parse broker.build_job('my-queue', 'MyWorker', event)
89
117
 
90
- expect(job['class']).to eq('Chasqui::Subscriber__my_queue')
118
+ expect(job['class']).to eq('MyWorker')
91
119
  expect(job['args']).to include(event)
92
120
  expect(job['queue']).to eq('my-queue')
93
121
  expect(job['jid']).to match(/^[0-9a-f]{24}/i)
94
122
  expect(job['created_at']).to be_within(0.01).of(Time.now.to_f)
95
123
  expect(job['enqueued_at']).to be_within(0.01).of(Time.now.to_f)
96
- expect(job['retry']).to eq(false)
124
+ expect(job['retry']).to be nil
97
125
  end
98
126
 
99
127
  it 'uses the event created_at time' do
100
128
  created_at = (Time.now - 3).to_f
101
- job = JSON.parse broker.build_job('my-queue', 'created_at' => created_at.to_s)
129
+ job = JSON.parse broker.build_job('my-queue', 'MyWorker', 'created_at' => created_at.to_s)
102
130
  expect(job['created_at']).to eq(created_at)
103
131
  end
104
132
 
105
133
  it 'uses the event retry value' do
106
- job = JSON.parse broker.build_job('my-queue', 'retry' => true)
134
+ job = JSON.parse broker.build_job('my-queue', 'MyWorker', 'retry' => true)
107
135
  expect(job['retry']).to be true
108
136
 
109
- job = JSON.parse broker.build_job('my-queue', 'retry' => false)
137
+ job = JSON.parse broker.build_job('my-queue', 'MyWorker', 'retry' => false)
110
138
  expect(job['retry']).to be false
111
139
 
112
- job = JSON.parse broker.build_job('my-queue', 'retry' => nil)
113
- expect(job['retry']).to be false
140
+ job = JSON.parse broker.build_job('my-queue', 'MyWorker', 'retry' => nil)
141
+ expect(job['retry']).to be nil
114
142
  end
115
143
  end
116
144
  end
@@ -33,7 +33,7 @@ describe Chasqui::CLI do
33
33
  expect(cli.inbox_queue).to eq('inbox2')
34
34
 
35
35
  cli.configure
36
- expect(Chasqui.inbox).to eq('inbox2')
36
+ expect(Chasqui.inbox_queue).to eq('inbox2')
37
37
  end
38
38
 
39
39
  it 'enables verbose mode (debug logging)' do
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chasqui::Config do
4
+
5
+ context 'defaults' do
6
+ it { expect(subject.channel_prefix).to be nil }
7
+ it { expect(subject.default_queue).to eq('chasqui-subscribers') }
8
+ it { expect(subject.inbox_queue).to eq('inbox') }
9
+ it { expect(subject.redis.client.db).to eq(0) }
10
+ it { expect(subject.broker_poll_interval).to eq(3) }
11
+ it { expect(subject.queue_adapter).to eq(Chasqui::QueueAdapters::RedisQueueAdapter) }
12
+
13
+ it do
14
+ # remove chasqui's test environment logger
15
+ subject[:logger] = nil
16
+ expect(subject.logger).to be_kind_of(Logger)
17
+ end
18
+
19
+ it { expect(subject.logger.level).to eq(Logger::INFO) }
20
+ it { expect(subject.logger.progname).to eq('chasqui') }
21
+ end
22
+
23
+ it 'configures the channel prefix' do
24
+ subject.channel_prefix = 'com.example.test'
25
+ expect(subject.channel_prefix).to eq('com.example.test')
26
+ end
27
+
28
+ it 'configures the default queue' do
29
+ subject.default_queue = 'my-app'
30
+ expect(subject.default_queue).to eq('my-app')
31
+ end
32
+
33
+ it 'configures the inbox queue' do
34
+ subject.inbox_queue = 'foo'
35
+ expect(subject.inbox_queue).to eq('foo')
36
+ end
37
+
38
+ it 'configures the broker poll interval' do
39
+ subject.broker_poll_interval = 1
40
+ expect(subject.broker_poll_interval).to eq(1)
41
+ end
42
+
43
+ it 'configures the queue adapter' do
44
+ subject.queue_adapter = FakeQueueAdapter
45
+ expect(subject.queue_adapter).to eq(FakeQueueAdapter)
46
+ end
47
+
48
+ context 'redis' do
49
+ it 'accepts config options' do
50
+ redis_config = { host: '10.0.3.24' }
51
+ subject.redis = redis_config
52
+ expect(subject.redis.client.host).to eq('10.0.3.24')
53
+ end
54
+
55
+ it 'accepts an initialized client' do
56
+ redis = Redis.new db: 2
57
+ subject.redis = redis
58
+ expect(subject.redis.client.db).to eq(2)
59
+ end
60
+
61
+ it 'accepts URLs' do
62
+ subject.redis = 'redis://10.0.1.21:12345/0'
63
+ expect(subject.redis.client.host).to eq('10.0.1.21')
64
+ end
65
+
66
+ it 'uses a namespace' do
67
+ subject.redis.set 'foo', 'bar'
68
+ expect(subject.redis.redis.get 'chasqui:foo').to eq('bar')
69
+ end
70
+ end
71
+
72
+ describe 'logger' do
73
+ it 'accepts a log device' do
74
+ logs = StringIO.new
75
+ subject.logger = logs
76
+ subject.logger.info "status"
77
+ subject.logger.warn "error"
78
+
79
+ logs.rewind
80
+ output = logs.read
81
+
82
+ %w(chasqui INFO status WARN error).each do |text|
83
+ expect(output).to match(text)
84
+ end
85
+ end
86
+
87
+ it 'accepts a logger-like object' do
88
+ fake_logger = FakeLogger.new
89
+ subject.logger = fake_logger
90
+ expect(subject.logger).to eq(fake_logger)
91
+ expect(subject.logger.progname).to eq('chasqui')
92
+ end
93
+ end
94
+
95
+ context 'worker_backend' do
96
+ it 'chooses resque' do
97
+ allow(Object).to receive(:const_defined?).with(:Resque).and_return true
98
+ allow(Object).to receive(:const_defined?).with(:Sidekiq).and_return false
99
+ expect(subject.worker_backend).to eq(:resque)
100
+ end
101
+
102
+ it 'chooses sidekiq' do
103
+ allow(Object).to receive(:const_defined?).with(:Resque).and_return true
104
+ allow(Object).to receive(:const_defined?).with(:Sidekiq).and_return true
105
+ expect(subject.worker_backend).to eq(:sidekiq)
106
+ end
107
+
108
+ it 'refuses to guess' do
109
+ allow(Object).to receive(:const_defined?).with(:Resque).and_return false
110
+ allow(Object).to receive(:const_defined?).with(:Sidekiq).and_return false
111
+ expect(subject.worker_backend).to be nil
112
+ end
113
+
114
+ it 'allows you to choose the worker backend' do
115
+ allow(Object).to receive(:const_defined?).with(:Resque).and_return true
116
+ allow(Object).to receive(:const_defined?).with(:Sidekiq).and_return true
117
+ subject.worker_backend = :resque
118
+ expect(subject.worker_backend).to eq(:resque)
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe FakeQueueAdapter do
4
+ it_behaves_like 'a queue adapter'
5
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ class MySubscriber
4
+ include Chasqui::Subscriber
5
+ subscribe channel: 'channel-name', queue: 'queue-name'
6
+ end
7
+
8
+ describe Chasqui::QueueAdapters::RedisQueueAdapter do
9
+ it_behaves_like 'a queue adapter'
10
+
11
+ let(:subscriber) { MySubscriber }
12
+
13
+ describe '#bind / #unbind' do
14
+ let(:key) { 'subscriptions:channel-name' }
15
+
16
+ before { reset_chasqui_workers }
17
+ after { reset_chasqui_workers }
18
+
19
+ context 'resque backend' do
20
+ before do
21
+ reset_chasqui
22
+ Chasqui.config.worker_backend = :resque
23
+ Resque.redis.namespace = :resque
24
+ end
25
+
26
+ it 'persists the subscriptions to redis' do
27
+ subject.bind(subscriber)
28
+ subscriptions = redis.smembers(key)
29
+ expect(subscriptions).to eq(
30
+ ['resque/Chasqui::Workers::MySubscriber/resque:queue:queue-name'])
31
+
32
+ expect(Chasqui::Workers.constants).to include(:MySubscriber)
33
+ worker = Chasqui::Workers.const_get :MySubscriber
34
+ expect(worker.subscriber).to eq(MySubscriber)
35
+
36
+ redis.sadd key, 'random'
37
+
38
+ subject.unbind(subscriber)
39
+ subscriptions = redis.smembers('subscriptions:channel-name')
40
+ expect(subscriptions).to eq(['random'])
41
+ end
42
+ end
43
+
44
+ if sidekiq_supported_ruby_version?
45
+ context 'sidekiq backend' do
46
+ before do
47
+ reset_chasqui
48
+ Chasqui.config.worker_backend = :sidekiq
49
+ Sidekiq.configure_client { |c| c.redis = { namespace: nil } }
50
+ end
51
+
52
+ it 'persists the subscription to redis' do
53
+ subject.bind(subscriber)
54
+ subscriptions = redis.smembers('subscriptions:channel-name')
55
+ expect(subscriptions).to eq(
56
+ ['sidekiq/Chasqui::Workers::MySubscriber/queue:queue-name'])
57
+ expect(redis_no_namespace.smembers 'queues').to eq(['queue-name'])
58
+
59
+ expect(Chasqui::Workers.constants).to include(:MySubscriber)
60
+ worker = Chasqui::Workers.const_get :MySubscriber
61
+ expect(worker.subscriber).to eq(MySubscriber)
62
+
63
+ redis.sadd key, 'random'
64
+
65
+ subject.unbind(subscriber)
66
+ subscriptions = redis.smembers('subscriptions:channel-name')
67
+ expect(subscriptions).to eq(['random'])
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ end
@@ -1,67 +1,119 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Chasqui::Subscriber do
4
- let(:subscriber) { Chasqui::Subscriber.new 'my-queue', 'my.channel' }
4
+ let(:subscriber_class) { Class.new { include Chasqui::Subscriber } }
5
+ let(:redis) { Redis.new }
5
6
 
6
- it { expect(subscriber.queue).to eq('my-queue') }
7
- it { expect(subscriber.channel).to eq('my.channel') }
7
+ describe '#event' do
8
+ it { expect(subscriber_class.new(event: 'foo').event).to eq('foo') }
9
+ it { expect(-> { subscriber_class.new }).to raise_error(KeyError) }
10
+ end
8
11
 
9
- describe '#on' do
10
- it 'registers the event handlers' do
11
- subscriber.on('foo') { |foo| foo }
12
- subscriber.on('zig.zag') { |*args| args }
12
+ describe '#redis' do
13
+ let(:subscriber) { subscriber_class.new event: nil }
13
14
 
14
- pattern = subscriber.matching_handler_patterns_for('foo').first
15
- expect(subscriber.call_handler(pattern, 'bar')).to eq('bar')
15
+ context 'default' do
16
+ it { expect(subscriber.redis).to eq(Chasqui.redis) }
17
+ end
16
18
 
17
- pattern = subscriber.matching_handler_patterns_for('zig.zag').first
18
- expect(subscriber.call_handler(pattern, 1, 2, 3, 4)).to eq([1, 2, 3, 4])
19
+ context 'custom' do
20
+ let(:redis) { Redis.new }
21
+ let(:subscriber) { subscriber_class.new event: nil, redis: redis }
22
+ it { expect(subscriber.redis.object_id).to eq(redis.object_id) }
19
23
  end
24
+ end
20
25
 
21
- it 'raises when registering duplicate handlers' do
22
- subscriber.on('foo') { |foo| foo }
23
- expect(-> {
24
- subscriber.on('foo') { |a, b| a + b }
25
- }).to raise_error(Chasqui::HandlerAlreadyRegistered)
26
+ describe '#logger' do
27
+ let(:subscriber) { subscriber_class.new event: nil }
28
+
29
+ context 'default' do
30
+ it { expect(subscriber.logger).to eq(Chasqui.logger) }
31
+ end
32
+
33
+ context 'custom' do
34
+ let(:logger) { FakeLogger.new }
35
+ let(:subscriber) { subscriber_class.new event: nil, logger: logger }
36
+ it { expect(subscriber.logger).to eq(logger) }
26
37
  end
27
38
  end
28
39
 
29
- describe '#matching_handler_patterns_for' do
30
- it 'always returns an array' do
31
- expect(subscriber.matching_handler_patterns_for('unknown')).to eq([])
40
+ describe '.channels' do
41
+ before do
42
+ expect(Chasqui).to receive(:register).with(subscriber_class).at_least(:once)
43
+ subscriber_class.subscribe channel: 'some.channel'
32
44
  end
33
45
 
34
- it 'matches single event' do
35
- p = Proc.new { }
36
- subscriber.on('foo', &p)
37
- expect(subscriber.matching_handler_patterns_for('foo')).to eq([/\Afoo\z/])
46
+ context 'default' do
47
+ it { expect(subscriber_class.channels).to include('some.channel') }
38
48
  end
39
49
 
40
- it 'matches wildcards' do
41
- p = 6.times.map { Proc.new { } }
42
- subscriber.on('foo*', &p[0])
43
- subscriber.on('bar', &p[1])
44
- subscriber.on('*bar', &p[2])
45
- subscriber.on('*', &p[3])
46
- subscriber.on('*a*', &p[4])
47
- subscriber.on('*z*', &p[5])
48
- expect(subscriber.matching_handler_patterns_for('foo.bar')).to eq([
49
- /\Afoo.*\z/,
50
- /\A.*bar\z/,
51
- /\A.*\z/,
52
- /\A.*a.*\z/
53
- ])
50
+ context 'with default prefix' do
51
+ before do
52
+ Chasqui.config.channel_prefix = 'prefix'
53
+ subscriber_class.subscribe channel: 'some.channel'
54
+ end
55
+
56
+ it { expect(subscriber_class.channels).to include('prefix.some.channel') }
57
+ end
58
+
59
+ context 'with custom prefix' do
60
+ before { Chasqui.config.channel_prefix = 'prefix' }
61
+
62
+ it 'uses the custom prefix' do
63
+ subscriber_class.subscribe channel: 'another.channel', prefix: 'custom'
64
+ expect(subscriber_class.channels).to include('custom.another.channel')
65
+ end
66
+
67
+ it 'removes the prefix' do
68
+ subscriber_class.subscribe channel: 'another.channel', prefix: nil
69
+ expect(subscriber_class.channels).to include('another.channel')
70
+ end
71
+ end
72
+
73
+ context 'multiple channels' do
74
+ before { subscriber_class.subscribe channel: ['foo', 'bar'], prefix: 'custom' }
75
+
76
+ it 'subscribes to multiple channels' do
77
+ expect(subscriber_class.channels).to eq(['custom.foo', 'custom.bar'])
78
+ end
79
+ end
80
+ end
81
+
82
+ describe '.queue' do
83
+ context 'default' do
84
+ it { expect(subscriber_class.queue).to eq(Chasqui.default_queue) }
85
+ end
86
+
87
+ context 'custom' do
88
+ before do
89
+ expect(Chasqui).to receive(:register).with(subscriber_class)
90
+ subscriber_class.subscribe queue: 'custom-queue'
91
+ end
92
+
93
+ it { expect(subscriber_class.queue).to eq('custom-queue') }
54
94
  end
55
95
  end
56
96
 
57
97
  describe '#perform' do
58
- it 'calls the matching event handlers' do
59
- calls = []
60
- subscriber.on('foo.bar') { |a, b| calls << a + b }
61
- subscriber.on('foo.*') { |a, b| calls << a ** b }
62
- subscriber.perform(redis, { 'event' => 'foo.bar', 'data' => [3, 4] })
63
- expect(calls.sort).to eq([7, 81])
98
+ let(:subscriber) { subscriber_class.new event: nil }
99
+
100
+ it 'is not implemented' do
101
+ expect(-> {
102
+ subscriber.perform foo: 'bar'
103
+ }).to raise_error(NotImplementedError)
64
104
  end
65
105
  end
66
106
 
107
+ describe '.inherited' do
108
+ before { allow(Chasqui).to receive(:register) }
109
+
110
+ it 'maintains a registry of inherited classes' do
111
+ klass = Class.new do
112
+ include Chasqui::Subscriber
113
+ subscribe
114
+ end
115
+
116
+ expect(Chasqui::Subscriber.subscribers).to include(klass)
117
+ end
118
+ end
67
119
  end