routemaster-client 3.1.0 → 3.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.
@@ -8,5 +8,6 @@ module Routemaster
8
8
  class MissingAsyncBackendError < Error; end
9
9
  class MissingAttributeError < ConfigurationError; end
10
10
  class InvalidAttributeError < ConfigurationError; end
11
+ class ConnectionError < Error; end
11
12
  end
12
13
  end
@@ -0,0 +1,32 @@
1
+ module Routemaster
2
+ module Client
3
+ class Subscription
4
+
5
+ attr_reader :subscriber, :callback, :topics, :events
6
+
7
+ def initialize(options)
8
+ @subscriber = options.fetch('subscriber')
9
+ @callback = options.fetch('callback')
10
+ @topics = options.fetch('topics')
11
+ @events = _symbolize_keys options.fetch('events')
12
+ end
13
+
14
+ def attributes
15
+ {
16
+ subscriber: @subscriber,
17
+ callback: @callback,
18
+ topics: @topics,
19
+ events: @events
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ def _symbolize_keys(h)
26
+ {}.tap do |res|
27
+ h.each { |k,v| res[k.to_sym] = v }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ module Routemaster
2
+ module Client
3
+ class Topic
4
+
5
+ attr_reader :name, :publisher, :events
6
+
7
+ def initialize(options)
8
+ @name = options.fetch('name')
9
+ @publisher = options.fetch('publisher')
10
+ @events = options.fetch('events')
11
+ end
12
+
13
+ def attributes
14
+ { name: @name, publisher: @publisher, events: @events }
15
+ end
16
+
17
+ end
18
+ end
19
+
20
+ # For backwards compatibility (TODO: remove in v4)
21
+ Topic = Client::Topic
22
+ end
@@ -1,5 +1,5 @@
1
1
  module Routemaster
2
2
  module Client
3
- VERSION = '3.1.0'
3
+ VERSION = '3.1.1'
4
4
  end
5
5
  end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+ require 'routemaster/cli/top_level'
3
+ require 'webmock/rspec'
4
+
5
+ describe Routemaster::CLI::Pub, type: :cli do
6
+ before { allow_bus_pulse 'bus.dev', 's3cr3t' }
7
+
8
+ context 'with too few arguments' do
9
+ let(:argv) { [] }
10
+ it { expect { perform }.to raise_error(Routemaster::CLI::Exit) }
11
+ it { expect { perform rescue nil }.to change { stderr.string }.to a_string_matching(/Usage/) }
12
+ end
13
+
14
+ context 'with correct arguments' do
15
+ let(:argv) { %w[pub created widgets https://example.com/widgets/1 -b bus.dev -t s3cr3t] }
16
+ it {
17
+ expect(client).to receive(:created).with('widgets', 'https://example.com/widgets/1')
18
+ perform
19
+ }
20
+ end
21
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+ require 'routemaster/cli/top_level'
3
+ require 'webmock/rspec'
4
+
5
+ describe Routemaster::CLI::Sub, type: :cli do
6
+ before { allow_bus_pulse 'bus.dev', 's3cr3t' }
7
+
8
+ describe 'add' do
9
+ context 'with correct arguments' do
10
+ let(:argv) { %w[sub add https://my-service.dev cats dogs -b bus.dev -t s3cr3t] }
11
+
12
+ it {
13
+ expect(client).to receive(:subscribe).with(topics: %w[cats dogs], callback: 'https://my-service.dev')
14
+ perform
15
+ }
16
+ end
17
+ end
18
+
19
+ describe 'del' do
20
+ context 'with a list of topics' do
21
+ let(:argv) { %w[sub del cats dogs -b bus.dev -t s3cr3t] }
22
+ it {
23
+ expect(client).to receive(:unsubscribe).with('cats', 'dogs')
24
+ perform
25
+ }
26
+ end
27
+
28
+ context 'without arguments' do
29
+ let(:argv) { %w[sub del -b bus.dev -t s3cr3t] }
30
+ it {
31
+ expect(client).to receive(:unsubscribe_all).with(no_args)
32
+ perform
33
+ }
34
+ end
35
+ end
36
+
37
+ describe 'list' do
38
+ context 'with correct arguments' do
39
+ let(:argv) { %w[sub list -b bus.dev -t s3cr3t] }
40
+ before {
41
+ allow(client).to receive(:monitor_subscriptions).and_return([
42
+ Routemaster::Client::Subscription.new(
43
+ 'subscriber' => 'service--f000-b44r-b44r',
44
+ 'callback' => 'https://serviced.dev',
45
+ 'topics' => %w[cats dogs],
46
+ 'events' => {
47
+ 'queued' => 1234
48
+ })
49
+ ])
50
+ }
51
+
52
+ it {
53
+ expect(client).to receive(:monitor_subscriptions).with(no_args)
54
+ perform
55
+ }
56
+ it {
57
+ expect { perform }.to change { stdout.string }.to a_string_matching(/service--f000-b44r-b44r/)
58
+ }
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ require 'routemaster/cli/top_level'
3
+ require 'webmock/rspec'
4
+
5
+ describe Routemaster::CLI::Token, type: :cli do
6
+ before { allow_bus_pulse 'bus.dev', 's3cr3t' }
7
+
8
+ describe 'add' do
9
+ context 'with correct arguments' do
10
+ let(:argv) { %w[token add my-service -b bus.dev -t s3cr3t] }
11
+
12
+ it {
13
+ expect(client).to receive(:token_add).with(name: 'my-service', token: nil)
14
+ perform
15
+ }
16
+
17
+ it {
18
+ allow(client).to receive(:token_add).and_return('my-service--dead-0000-beef')
19
+ expect { perform }.to change { stdout.string }.to("my-service--dead-0000-beef\n")
20
+ }
21
+ end
22
+ end
23
+
24
+ describe 'del' do
25
+ context 'with correct arguments' do
26
+ let(:argv) { %w[token del my-service--dead-0000-beef -b bus.dev -t s3cr3t] }
27
+ it {
28
+ expect(client).to receive(:token_del).with(token: 'my-service--dead-0000-beef')
29
+ perform
30
+ }
31
+ end
32
+ end
33
+
34
+ describe 'list' do
35
+ context 'with correct arguments' do
36
+ let(:argv) { %w[token list -b bus.dev -t s3cr3t] }
37
+ before {
38
+ allow(client).to receive(:token_list).and_return({ 'service--t0ken' => 'service' })
39
+ }
40
+
41
+ it {
42
+ expect(client).to receive(:token_list).with(no_args)
43
+ perform
44
+ }
45
+ it {
46
+ expect { perform }.to change { stdout.string }.to "service--t0ken\tservice\n"
47
+ }
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+ require 'routemaster/client/subscription'
3
+
4
+ describe Routemaster::Client::Subscription do
5
+ describe '#initialize' do
6
+ let(:options) {{
7
+ 'subscriber' => 'alice',
8
+ 'callback' => 'https://example.com/events',
9
+ 'topics' => %w[widgets],
10
+ 'events' => {},
11
+ }}
12
+
13
+ subject { described_class.new(options) }
14
+
15
+ it 'passes' do
16
+ expect { subject }.not_to raise_error
17
+ end
18
+ end
19
+ end
@@ -1,6 +1,6 @@
1
- require 'routemaster/topic'
1
+ require 'routemaster/client/topic'
2
2
 
3
- describe Routemaster::Topic do
3
+ describe Routemaster::Client::Topic do
4
4
 
5
5
  let(:name) { 'widgets' }
6
6
  let(:publisher) { 'demo' }
@@ -2,9 +2,10 @@ require 'spec_helper'
2
2
  require 'spec/support/configuration_helper'
3
3
  require 'routemaster/client'
4
4
  require 'routemaster/client/backends/sidekiq'
5
- require 'routemaster/topic'
5
+ require 'routemaster/client/topic'
6
6
  require 'webmock/rspec'
7
7
  require 'sidekiq/testing'
8
+ require 'securerandom'
8
9
 
9
10
  describe Routemaster::Client do
10
11
 
@@ -69,10 +70,10 @@ describe Routemaster::Client do
69
70
  end
70
71
  end
71
72
 
72
- shared_examples 'an event sender' do
73
+ shared_examples 'an event sender' do |spec_options|
73
74
  let(:callback) { 'https://app.example.com/widgets/123' }
74
75
  let(:topic) { 'widgets' }
75
- let(:perform) { subject.send(event, topic, callback, **flags) }
76
+ let(:perform) { subject.send(method, topic, callback, **flags) }
76
77
  let(:http_status) { nil }
77
78
 
78
79
  before do
@@ -97,6 +98,25 @@ describe Routemaster::Client do
97
98
  perform
98
99
  end
99
100
 
101
+ it 'sends the url and type' do
102
+ @stub.with do |req|
103
+ data = JSON.parse(req.body)
104
+ expect(data['type']).to eq event
105
+ expect(data['url']).to eq callback
106
+ end
107
+ perform
108
+ end
109
+
110
+ if spec_options && spec_options[:set_timestamp]
111
+ it 'sets a timestamp' do
112
+ @stub.with do |req|
113
+ data = JSON.parse(req.body)
114
+ expect(data['timestamp']).to be_a_kind_of(Integer)
115
+ end
116
+ perform
117
+ end
118
+ end
119
+
100
120
  it 'fails with a bad callback URL' do
101
121
  callback.replace 'http.foo.bar'
102
122
  expect { perform }.to raise_error(Routemaster::Client::InvalidArgumentError)
@@ -121,7 +141,7 @@ describe Routemaster::Client do
121
141
  let(:http_status) { 500 }
122
142
 
123
143
  it 'raises an exception' do
124
- expect { perform }.to raise_error(RuntimeError)
144
+ expect { perform }.to raise_error(Routemaster::Client::ConnectionError, 'event rejected (status: 500)')
125
145
  end
126
146
  end
127
147
 
@@ -136,7 +156,7 @@ describe Routemaster::Client do
136
156
 
137
157
  context 'with explicit timestamp' do
138
158
  let(:timestamp) { (Time.now.to_f * 1e3).to_i }
139
- let(:perform) { subject.send(event, topic, callback, t: timestamp) }
159
+ let(:perform) { subject.send(method, topic, callback, t: timestamp) }
140
160
 
141
161
  before do
142
162
  @stub = stub_request(:post, 'https://@bus.example.com/topics/widgets').
@@ -171,7 +191,7 @@ describe Routemaster::Client do
171
191
 
172
192
  context 'with a data payload' do
173
193
  let(:timestamp) { (Time.now.to_f * 1e3).to_i }
174
- let(:perform) { subject.send(event, topic, callback, data: data) }
194
+ let(:perform) { subject.send(method, topic, callback, data: data) }
175
195
  let(:data) {{ 'foo' => 'bar' }}
176
196
 
177
197
  before do
@@ -202,7 +222,8 @@ describe Routemaster::Client do
202
222
  context 'with default flags' do
203
223
  %w[created updated deleted noop].each do |m|
204
224
  describe "##{m}" do
205
- let(:event) { m.to_sym }
225
+ let(:method) { m.to_sym }
226
+ let(:event) { m.sub(/d$/, '') }
206
227
  let(:flags) { {} }
207
228
  it_behaves_like 'an event sender'
208
229
  end
@@ -232,46 +253,49 @@ describe Routemaster::Client do
232
253
 
233
254
  context 'with the sidekiq async back end configured' do
234
255
  reset_sidekiq_config_between_tests!
235
-
256
+
236
257
  before do
237
258
  options[:async_backend] = Routemaster::Client::Backends::Sidekiq.configure do |config|
238
259
  config.queue = :realtime
239
260
  config.retry = true
240
261
  end
241
262
  end
242
-
263
+
243
264
  around do |example|
244
265
  Sidekiq::Testing.inline! do
245
266
  example.run
246
267
  end
247
268
  end
248
-
269
+
249
270
  context 'with default options' do
250
271
  let(:flags) { {} }
251
-
272
+
252
273
  %w[created updated deleted noop].each do |m|
253
274
  describe "##{m}" do
254
- let(:event) { m }
275
+ let(:method) { m }
276
+ let(:event) { m.sub(/d$/, '') }
255
277
  it_behaves_like 'an event sender'
256
278
  end
257
279
  end
258
280
  end
259
-
281
+
260
282
  context 'with :async option' do
261
283
  let(:flags) { { async: true } }
262
-
284
+
263
285
  %w[created updated deleted noop].each do |m|
264
286
  describe "##{m}" do
265
- let(:event) { m }
266
- it_behaves_like 'an event sender'
287
+ let(:method) { m }
288
+ let(:event) { m.sub(/d$/, '') }
289
+ it_behaves_like 'an event sender', set_timestamp: true
267
290
  end
268
291
  end
269
292
  end
270
-
293
+
271
294
  describe 'deprecated *_async methods' do
272
295
  %w[created updated deleted noop].each do |m|
273
296
  describe "##{m}_async" do
274
- let(:event) { "#{m}_async" }
297
+ let(:method) { "#{m}_async" }
298
+ let(:event) { m.sub(/d$/, '') }
275
299
  let(:flags) { {} }
276
300
  it_behaves_like 'an event sender'
277
301
  end
@@ -324,7 +348,7 @@ describe Routemaster::Client do
324
348
 
325
349
  it 'fails on HTTP error' do
326
350
  @stub.to_return(status: 500)
327
- expect { perform }.to raise_error(RuntimeError, 'subscribe rejected')
351
+ expect { perform }.to raise_error(Routemaster::Client::ConnectionError, 'subscribe rejected (status: 500)')
328
352
  end
329
353
 
330
354
  it 'accepts a uuid' do
@@ -356,7 +380,7 @@ describe Routemaster::Client do
356
380
 
357
381
  it 'fails on HTTP error' do
358
382
  @stub.to_return(status: 500)
359
- expect { perform }.to raise_error(RuntimeError)
383
+ expect { perform }.to raise_error(Routemaster::Client::ConnectionError, 'unsubscribe rejected (status: 500)')
360
384
  end
361
385
  end
362
386
 
@@ -376,7 +400,7 @@ describe Routemaster::Client do
376
400
 
377
401
  it 'fails on HTTP error' do
378
402
  @stub.to_return(status: 500)
379
- expect { perform }.to raise_error(RuntimeError)
403
+ expect { perform }.to raise_error(Routemaster::Client::ConnectionError, 'unsubscribe all rejected (status: 500)')
380
404
  end
381
405
  end
382
406
 
@@ -403,58 +427,112 @@ describe Routemaster::Client do
403
427
 
404
428
  it 'fails on HTTP error' do
405
429
  @stub.to_return(status: 500)
406
- expect { perform }.to raise_error(RuntimeError)
430
+ expect { perform }.to raise_error(Routemaster::Client::ConnectionError, 'failed to delete topic (status: 500)')
407
431
  end
408
432
  end
409
433
 
410
434
 
411
- describe '#monitor_topics' do
435
+ context 'monitoring methods' do
436
+ let(:default_headers) {{}}
412
437
 
413
- let(:perform) { subject.monitor_topics }
414
- let(:expected_result) do
415
- [
416
- {
417
- name: 'widgets',
418
- publisher: 'demo',
419
- events: 12589
420
- }
421
- ]
438
+ shared_context 'successful connection to bus' do
439
+ before do
440
+ @stub = stub_request(:get, url)
441
+ .with(basic_auth: [options[:uuid], 'x'], headers: default_headers)
442
+ .to_return(status: 200, body: expected_result.to_json)
443
+ end
422
444
  end
423
445
 
424
- context 'the connection to the bus is successful' do
446
+ shared_context 'failing connection to bus' do
425
447
  before do
426
- @stub = stub_request(:get, 'https://bus.example.com/topics').
427
- with(basic_auth: [options[:uuid], 'x']).
428
- with { |r|
429
- r.headers['Content-Type'] == 'application/json'
430
- }.to_return {
431
- { status: 200, body: expected_result.to_json }
432
- }
448
+ @stub = stub_request(:get, url)
449
+ .with(basic_auth: [options[:uuid], 'x'], headers: default_headers)
450
+ .to_return(status: 500)
433
451
  end
452
+ end
434
453
 
435
- it 'expects a collection of topics' do
436
- expect(perform.map(&:attributes)).to eql(expected_result)
454
+ describe '#monitor_topics' do
455
+ let(:url) { 'https://bus.example.com/topics' }
456
+ let(:perform) { subject.monitor_topics }
457
+ let(:expected_result) do
458
+ [
459
+ {
460
+ name: 'widgets',
461
+ publisher: 'demo',
462
+ events: 12589
463
+ }
464
+ ]
465
+ end
466
+
467
+ context 'the connection to the bus is successful' do
468
+ include_context 'successful connection to bus'
469
+
470
+ it 'expects a collection of topics' do
471
+ expect(perform.map(&:attributes)).to eql(expected_result)
472
+ end
473
+ end
474
+
475
+ context 'the connection to the bus errors' do
476
+ include_context 'failing connection to bus'
477
+
478
+ it 'expects a collection of topics' do
479
+ expect { perform }.to raise_error(Routemaster::Client::ConnectionError, 'failed to connect to /topics (status: 500)')
480
+ end
437
481
  end
438
482
  end
439
483
 
440
- context 'the connection to the bus errors' do
441
- before do
442
- @stub = stub_request(:get, 'https://bus.example.com/topics').
443
- with(basic_auth: [options[:uuid], 'x']).
444
- with { |r|
445
- r.headers['Content-Type'] == 'application/json'
446
- }.to_return(status: 500)
484
+ describe '#monitor_subscriptions' do
485
+ let(:url) { 'https://bus.example.com/subscriptions' }
486
+ let(:perform) { subject.monitor_subscriptions }
487
+ let(:expected_result) do
488
+ [{
489
+ subscriber: 'bob',
490
+ callback: 'https://app.example.com/events',
491
+ topics: ['widgets', 'kitten'],
492
+ events: { sent: 1, queued: 100, oldest: 10_000 }
493
+ }]
447
494
  end
448
495
 
449
- it 'expects a collection of topics' do
450
- expect { perform }.to raise_error(RuntimeError)
496
+ context 'the connection to the bus is successful' do
497
+ include_context 'successful connection to bus'
498
+
499
+ it 'expects a collection of subscriptions' do
500
+ expect(perform.map(&:attributes)).to eql(expected_result)
501
+ end
502
+ end
503
+
504
+ context 'the connection to the bus errors' do
505
+ include_context 'failing connection to bus'
506
+
507
+ it 'expects a collection of topics' do
508
+ expect { perform }.to raise_error(Routemaster::Client::ConnectionError)
509
+ end
451
510
  end
452
511
  end
453
512
  end
454
-
455
- describe '#monitor_subscriptions' do
456
- it 'passes'
513
+
514
+ describe '#reset_connection' do
515
+ context 'can reset class vars to change params' do
516
+ let(:instance_uuid) { SecureRandom.uuid }
517
+ let(:options) {{
518
+ url: 'https://@bus.example.com',
519
+ uuid: instance_uuid,
520
+ verify_ssl: false,
521
+ lazy: true
522
+ }}
523
+
524
+ before do
525
+ Routemaster::Client::Connection.reset_connection
526
+ @stub = stub_request(:get, 'https://@bus.example.com/topics').with({basic_auth: [instance_uuid, 'x']})
527
+ .to_return(status: 200, body: [{ name: "topic.name", publisher: "topic.publisher", events: "topic.get_count" }].to_json)
528
+ end
529
+
530
+ after { Routemaster::Client::Connection.reset_connection }
531
+
532
+ it 'connects with new params' do
533
+ subject.monitor_topics
534
+ expect(@stub).to have_been_requested
535
+ end
536
+ end
457
537
  end
458
-
459
538
  end
460
-