routemaster-client 3.1.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
-