chasqui 0.9.3 → 1.0.0.pre.rc1

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 (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