routemaster-client 0.0.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.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .bundle/
2
+ tmp
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "rebund"]
2
+ path = rebund
3
+ url = https://github.com/mezis/rebund.git
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ -I.
2
+ --color
3
+ --format progress
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p545
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - 2.1.1
7
+ script:
8
+ - bundle exec rspec
9
+ install:
10
+ - "./rebund/run download"
11
+ - bundle install --path vendor/bundle
12
+ after_script:
13
+ - "./rebund/run upload"
14
+ env:
15
+ global:
16
+ secure: "kMxJcT8xyQ+QyCeKW5lDP44IU99Y8iN1AWZo9Z1BKN6HigGa8Ua8O/aD8AJjrTfRRSjnM402utmW6mk78RYVxJha3+69jJYr25I9EeyYhV2cuRu+5Ictxfcb9R9VhS0b6LDncz9h55xDS1UA35rvj+EOO4/M9moccw7T2qzPa2U="
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source ENV.fetch('GEM_SOURCE', 'https://rubygems.org')
2
+
3
+ # Specify your gem's dependencies in routemaster_client.gemspec
4
+ gemspec
5
+
6
+ # just here to avoid a safety warning
7
+ gem 'psych'
data/Gemfile.lock ADDED
@@ -0,0 +1,93 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ routemaster-client (0.0.1)
5
+ faraday
6
+ net-http-persistent
7
+ sinatra
8
+
9
+ GEM
10
+ remote: http://eu.yarp.io/
11
+ specs:
12
+ addressable (2.3.6)
13
+ celluloid (0.15.2)
14
+ timers (~> 1.1.0)
15
+ coderay (1.1.0)
16
+ crack (0.4.2)
17
+ safe_yaml (~> 1.0.0)
18
+ diff-lcs (1.2.5)
19
+ faraday (0.9.0)
20
+ multipart-post (>= 1.2, < 3)
21
+ ffi (1.9.3)
22
+ formatador (0.2.5)
23
+ guard (2.6.1)
24
+ formatador (>= 0.2.4)
25
+ listen (~> 2.7)
26
+ lumberjack (~> 1.0)
27
+ pry (>= 0.9.12)
28
+ thor (>= 0.18.1)
29
+ guard-rspec (4.2.10)
30
+ guard (~> 2.1)
31
+ rspec (>= 2.14, < 4.0)
32
+ listen (2.7.8)
33
+ celluloid (>= 0.15.2)
34
+ rb-fsevent (>= 0.9.3)
35
+ rb-inotify (>= 0.9)
36
+ lumberjack (1.0.6)
37
+ method_source (0.8.2)
38
+ multipart-post (2.0.0)
39
+ net-http-persistent (2.9.4)
40
+ pry (0.9.12.6)
41
+ coderay (~> 1.0)
42
+ method_source (~> 0.8)
43
+ slop (~> 3.4)
44
+ pry-nav (0.2.3)
45
+ pry (~> 0.9.10)
46
+ psych (2.0.5)
47
+ rack (1.5.2)
48
+ rack-protection (1.5.3)
49
+ rack
50
+ rack-test (0.6.2)
51
+ rack (>= 1.0)
52
+ rake (10.3.2)
53
+ rb-fsevent (0.9.4)
54
+ rb-inotify (0.9.5)
55
+ ffi (>= 0.5.0)
56
+ rspec (3.0.0)
57
+ rspec-core (~> 3.0.0)
58
+ rspec-expectations (~> 3.0.0)
59
+ rspec-mocks (~> 3.0.0)
60
+ rspec-core (3.0.1)
61
+ rspec-support (~> 3.0.0)
62
+ rspec-expectations (3.0.1)
63
+ diff-lcs (>= 1.2.0, < 2.0)
64
+ rspec-support (~> 3.0.0)
65
+ rspec-mocks (3.0.1)
66
+ rspec-support (~> 3.0.0)
67
+ rspec-support (3.0.0)
68
+ safe_yaml (1.0.3)
69
+ sinatra (1.4.5)
70
+ rack (~> 1.4)
71
+ rack-protection (~> 1.4)
72
+ tilt (~> 1.3, >= 1.3.4)
73
+ slop (3.5.0)
74
+ thor (0.19.1)
75
+ tilt (1.4.1)
76
+ timers (1.1.0)
77
+ webmock (1.18.0)
78
+ addressable (>= 2.3.6)
79
+ crack (>= 0.3.2)
80
+
81
+ PLATFORMS
82
+ ruby
83
+
84
+ DEPENDENCIES
85
+ bundler (~> 1.5)
86
+ guard-rspec
87
+ pry-nav
88
+ psych
89
+ rack-test
90
+ rake
91
+ routemaster-client!
92
+ rspec
93
+ webmock
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec, cmd: 'bundle exec rspec' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^routemaster/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
9
+ end
10
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 HouseTrip Ltd.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # routemaster_client
2
+
3
+ A Ruby API for the [Routemaster](https://github.com/HouseTrip/routemaster) event
4
+ bus.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'routemaster-client'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install routemaster-client
19
+
20
+ ## Usage
21
+
22
+ **Configure** your client:
23
+
24
+ ```ruby
25
+ require 'routemaster/client'
26
+ client = RoutemasterClient.new(url: 'https://bus.example.com', uuid: 'john-doe')
27
+ ```
28
+
29
+ You can also specify a timeout value in seconds if you like with the ```timeout``` option.
30
+
31
+ ```ruby
32
+ RoutemasterClient.new(url: 'https://bus.example.com', uuid: 'john-doe', timeout: 2)
33
+ ```
34
+
35
+
36
+ **Push** an event about an entity in the topic `widgets` with a callback URL:
37
+
38
+ ```ruby
39
+ client.created('widgets', 'https://app.example.com/widgets/1')
40
+ client.updated('widgets', 'https://app.example.com/widgets/2')
41
+ client.noop('widgets', 'https://app.example.com/widgets/3')
42
+ ```
43
+
44
+ There are methods for the four canonical event types: `created`, `updated`,
45
+ `deleted`, and `noop`.
46
+
47
+ `noop` is typically used when a subscriber is first connected (or reset), and
48
+ the publisher floods with `noop`s for all existing entities so subscribers can
49
+ refresh their view of the domain.
50
+
51
+
52
+ **Register** to be notified about `widgets` and `kitten` at most 60 seconds after
53
+ events, in batches of at most 500 events, to a given callback URL:
54
+
55
+ ```ruby
56
+ client.subscribe(
57
+ topics: ['widgets', 'kitten'],
58
+ callback: 'https://app.example.com/events',
59
+ uuid: 'john-doe',
60
+ timeout: 60_000,
61
+ max: 500)
62
+ ```
63
+
64
+
65
+ **Receive** events at path `/events` using a Rack middleware:
66
+
67
+ ```ruby
68
+ require 'routemaster/receiver'
69
+
70
+ class Handler
71
+ def on_events(batch)
72
+ batch.each do |event|
73
+ puts event['url']
74
+ end
75
+ end
76
+ end
77
+
78
+ use Routemaster::Receiver, {
79
+ path: '/events',
80
+ uuid: 'demo',
81
+ handler: Handler.new
82
+ }
83
+ ```
84
+
85
+
86
+ **Monitor** the status of topics and subscriptions:
87
+
88
+ ```ruby
89
+ client.monitor_topics
90
+ #=> [ { name: 'widgets', publisher: 'john-doe', events: 12589 }, ...]
91
+
92
+ client.monitor_subscriptions
93
+ #=> [ {
94
+ # subscriber: 'bob',
95
+ # callback: 'https://app.example.com/events',
96
+ # topics: ['widgets', 'kitten'],
97
+ # events: { sent: 21_450, queued: 498, oldest: 59_603 }
98
+ # } ... ]
99
+ ```
100
+
101
+ ## Contributing
102
+
103
+ 1. Fork it ( http://github.com/<my-github-username>/routemaster_client/fork )
104
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
105
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
106
+ 4. Push to the branch (`git push origin my-new-feature`)
107
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,13 @@
1
+ # Adapted from
2
+ # activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 44
3
+ class IO
4
+ def silence_stream(&block)
5
+ old_stream = dup
6
+ self.reopen('/dev/null')
7
+ self.sync = true
8
+ yield
9
+ ensure
10
+ reopen(old_stream)
11
+ old_stream.close
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ # In development environments, engineers will typically use self-signed
2
+ # certificates as it it not realistic to have valid cert chains for localhost
3
+ # or fake domains.
4
+ #
5
+ # This code disables SSL cert verification, and silences net-http-persistent's
6
+ # unnecessary warning.
7
+ #
8
+ # Note that this does _not_ apply in production or staging.
9
+ #
10
+ # http://docs.seattlerb.org/net-http-persistent/History_txt.html#documentation
11
+ # http://www.rubyinside.com/how-to-cure-nethttps-risky-default-https-behavior-4010.html
12
+ if ENV.fetch('RACK_ENV', 'development') !~ /production|staging/
13
+ require 'openssl'
14
+ require 'core_ext/silence_stream'
15
+
16
+ I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG = nil
17
+
18
+ $stderr.silence_stream do
19
+ OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ module Routemaster
2
+ class Client
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,105 @@
1
+ require 'routemaster/client/version'
2
+ require 'routemaster/client/openssl'
3
+ require 'uri'
4
+ require 'faraday'
5
+ require 'json'
6
+
7
+ module Routemaster
8
+ class Client
9
+ def initialize(options = {})
10
+ @_url = _assert_valid_url(options[:url])
11
+ @_uuid = options[:uuid]
12
+ @_timeout = options.fetch(:timeout, 1)
13
+
14
+ _assert (options[:uuid] =~ /^[a-z0-9_-]{1,64}$/), 'uuid should be alpha'
15
+ _assert_valid_timeout(@_timeout)
16
+
17
+ _conn.get('/pulse').tap do |response|
18
+ raise 'cannot connect to bus' unless response.success?
19
+ end
20
+ end
21
+
22
+ def created(topic, callback)
23
+ _send_event('create', topic, callback)
24
+ end
25
+
26
+ def updated(topic, callback)
27
+ _send_event('update', topic, callback)
28
+ end
29
+
30
+ def deleted(topic, callback)
31
+ _send_event('delete', topic, callback)
32
+ end
33
+
34
+ def noop(topic, callback)
35
+ _send_event('noop', topic, callback)
36
+ end
37
+
38
+ def subscribe(options = {})
39
+ if (options.keys - [:topics, :callback, :timeout, :max, :uuid]).any?
40
+ raise ArgumentError.new('bad options')
41
+ end
42
+ _assert options[:topics].kind_of?(Enumerable), 'topics required'
43
+ _assert options[:callback], 'callback required'
44
+ _assert_valid_timeout options[:timeout] if options[:timeout]
45
+ _assert_valid_max_events options[:max] if options[:max]
46
+
47
+ options[:topics].each { |t| _assert_valid_topic(t) }
48
+ _assert_valid_url(options[:callback])
49
+
50
+ data = options.to_json
51
+ response = _conn.post('/subscription') do |r|
52
+ r.headers['Content-Type'] = 'application/json'
53
+ r.body = data
54
+ end
55
+ # $stderr.puts response.status
56
+ unless response.success?
57
+ raise 'subscription rejected'
58
+ end
59
+ end
60
+
61
+
62
+ private
63
+
64
+ def _assert_valid_timeout(timeout)
65
+ _assert (0..3_600_000).include?(timeout), 'bad timeout'
66
+ end
67
+
68
+ def _assert_valid_max_events(max)
69
+ _assert (0..10_000).include?(max), 'bad max # events'
70
+ end
71
+
72
+ def _assert_valid_url(url)
73
+ uri = URI.parse(url)
74
+ _assert (uri.scheme == 'https'), 'HTTPS required'
75
+ return url
76
+ end
77
+
78
+ def _assert_valid_topic(topic)
79
+ _assert (topic =~ /^[a-z_]{1,32}$/), 'bad topic name'
80
+ end
81
+
82
+ def _send_event(event, topic, callback)
83
+ _assert_valid_url(callback)
84
+ _assert_valid_topic(topic)
85
+ data = { type: event, url: callback }.to_json
86
+ response = _conn.post("/topics/#{topic}") do |r|
87
+ r.headers['Content-Type'] = 'application/json'
88
+ r.body = data
89
+ end
90
+ fail "event rejected (#{response.status})" unless response.success?
91
+ end
92
+
93
+ def _assert(condition, message)
94
+ condition or raise ArgumentError.new(message)
95
+ end
96
+
97
+ def _conn
98
+ @_conn ||= Faraday.new(@_url) do |f|
99
+ f.use Faraday::Request::BasicAuthentication, @_uuid, 'x'
100
+ f.adapter :net_http_persistent
101
+ f.options.timeout = @_timeout
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,49 @@
1
+ require 'sinatra'
2
+ require 'rack/auth/basic'
3
+ require 'base64'
4
+ require 'json'
5
+
6
+ module Routemaster
7
+ class Receiver
8
+ def initialize(app, options = {})
9
+ @app = app
10
+ @path = options[:path]
11
+ @uuid = options[:uuid]
12
+ @handler = options[:handler]
13
+ end
14
+
15
+ def call(env)
16
+ catch :forward do
17
+ throw :forward unless _intercept_endpoint?(env)
18
+ return [401, {}, []] unless _has_auth?(env)
19
+ return [403, {}, []] unless _valid_auth?(env)
20
+ return [400, {}, []] unless payload = _extract_payload(env)
21
+
22
+ @handler.on_events(payload)
23
+ return [204, {}, []]
24
+ end
25
+ @app.call(env)
26
+ end
27
+
28
+ private
29
+
30
+ def _intercept_endpoint?(env)
31
+ env['PATH_INFO'] == @path && env['REQUEST_METHOD'] == 'POST'
32
+ end
33
+
34
+ def _has_auth?(env)
35
+ env.has_key?('HTTP_AUTHORIZATION')
36
+ end
37
+
38
+ def _valid_auth?(env)
39
+ Base64.
40
+ decode64(env['HTTP_AUTHORIZATION'].gsub(/^Basic /, '')).
41
+ split(':').first == @uuid
42
+ end
43
+
44
+ def _extract_payload(env)
45
+ return unless env['CONTENT_TYPE'] == 'application/json'
46
+ JSON.parse(env['rack.input'].read)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('..', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'routemaster/client/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'routemaster-client'
8
+ spec.version = Routemaster::Client::VERSION
9
+ spec.authors = ['Julien Letessier']
10
+ spec.email = ['julien.letessier@gmail.com']
11
+ spec.summary = %q{Client API for the Routemaster event bus}
12
+ spec.homepage = 'http://github.com/HouseTrip/routemaster_client'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.test_files = spec.files.grep(%r{^spec/})
17
+ spec.require_paths = %w(.)
18
+
19
+ spec.add_development_dependency 'bundler', '~> 1.5'
20
+ spec.add_development_dependency 'rake'
21
+ spec.add_development_dependency 'rspec'
22
+ spec.add_development_dependency 'guard-rspec'
23
+ spec.add_development_dependency 'webmock'
24
+ spec.add_development_dependency 'pry-nav'
25
+ spec.add_development_dependency 'rack-test'
26
+
27
+ spec.add_runtime_dependency 'faraday'
28
+ spec.add_runtime_dependency 'net-http-persistent'
29
+ spec.add_runtime_dependency 'sinatra'
30
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+ require 'spec/support/rack_test'
3
+ require 'routemaster/receiver'
4
+
5
+ describe Routemaster::Receiver do
6
+ let(:handler) { double 'handler', on_events: nil }
7
+ let(:app) { described_class.new(fake_app, options) }
8
+
9
+
10
+ def perform
11
+ post '/events', payload, 'CONTENT_TYPE' => 'application/json'
12
+ end
13
+
14
+ let(:options) do
15
+ {
16
+ path: '/events',
17
+ uuid: 'demo',
18
+ handler: handler
19
+ }
20
+ end
21
+
22
+ class FakeApp
23
+ def call(env)
24
+ [501, {}, 'fake app']
25
+ end
26
+ end
27
+
28
+ let(:fake_app) { FakeApp.new }
29
+
30
+ let(:payload) do
31
+ [{
32
+ topic: 'widgets', event: 'created', url: 'https://example.com/widgets/1', t: 1234
33
+ }, {
34
+ topic: 'widgets', event: 'created', url: 'https://example.com/widgets/2', t: 1234
35
+ }, {
36
+ topic: 'widgets', event: 'created', url: 'https://example.com/widgets/3', t: 1234
37
+ }].to_json
38
+ end
39
+
40
+
41
+ it 'passes with valid HTTP Basic' do
42
+ authorize 'demo', 'x'
43
+ perform
44
+ expect(last_response.status).to eq(204)
45
+ end
46
+
47
+ it 'fails without authentication' do
48
+ perform
49
+ expect(last_response.status).to eq(401)
50
+ end
51
+
52
+ it 'delegates to the next middleware for unknown paths' do
53
+ post '/foobar'
54
+ expect(last_response.status).to eq(501)
55
+ end
56
+
57
+ it 'delegates to the next middlex for non-POST' do
58
+ get '/events'
59
+ expect(last_response.status).to eq(501)
60
+ end
61
+
62
+ it 'calls the handler when receiving an avent' do
63
+ authorize 'demo', 'x'
64
+ expect(handler).to receive(:on_events).exactly(:once)
65
+ perform
66
+ end
67
+
68
+ it 'calls the handler multiple times' do
69
+ authorize 'demo', 'x'
70
+ expect(handler).to receive(:on_events).exactly(3).times
71
+ 3.times { perform }
72
+ end
73
+ end
@@ -0,0 +1,183 @@
1
+ require 'spec_helper'
2
+ require 'routemaster/client'
3
+ require 'webmock/rspec'
4
+
5
+ describe Routemaster::Client do
6
+ let(:options) do
7
+ { url: 'https://bus.example.com', uuid: 'john_doe' }
8
+ end
9
+
10
+ subject { described_class.new(options) }
11
+
12
+ before do
13
+ @stub_pulse = stub_request(:get, %r{^https://#{options[:uuid]}:x@bus.example.com/pulse$}).with(status: 200)
14
+ end
15
+
16
+ describe '#initialize' do
17
+ it 'passes with valid arguments' do
18
+ expect { subject }.not_to raise_error
19
+ end
20
+
21
+ it 'fails with a non-SSL URL' do
22
+ options[:url].sub!(/https/, 'http')
23
+ expect { subject }.to raise_error(ArgumentError)
24
+ end
25
+
26
+ it 'fails with a bad URL' do
27
+ options[:url].replace('foobar')
28
+ expect { subject }.to raise_error(ArgumentError)
29
+ end
30
+
31
+ it 'fails with a bad client id' do
32
+ options[:uuid].replace('123 $%')
33
+ expect { subject }.to raise_error(ArgumentError)
34
+ end
35
+
36
+ it 'fails it it cannot connect' do
37
+ stub_request(:any, %r{^https://#{options[:uuid]}:x@bus.example.com}).to_raise(Faraday::ConnectionFailed)
38
+ expect { subject }.to raise_error
39
+ end
40
+
41
+ it 'fails if it does not get a successful heartbeat from the app' do
42
+ @stub_pulse.to_return(status: 500)
43
+ expect { subject }.to raise_error
44
+ end
45
+
46
+ it 'fails if the timeout value is not an integer' do
47
+ options[:timeout] = 'timeout'
48
+ expect { subject }.to raise_error
49
+ end
50
+ end
51
+
52
+ shared_examples 'an event sender' do
53
+ let(:callback) { 'https://app.example.com/widgets/123' }
54
+ let(:topic) { 'widgets' }
55
+ let(:perform) { subject.send(event, topic, callback) }
56
+
57
+ before do
58
+ @stub = stub_request(
59
+ :post, "https://#{options[:uuid]}:x@bus.example.com/topics/widgets"
60
+ ).with(status: 200)
61
+ end
62
+
63
+ it 'sends the event' do
64
+ perform
65
+ expect(@stub).to have_been_requested
66
+ end
67
+
68
+ it 'sends a JSON payload' do
69
+ @stub.with do |req|
70
+ expect(req.headers['Content-Type']).to eq('application/json')
71
+ end
72
+ perform
73
+ end
74
+
75
+ it 'fails with a bad callback URL' do
76
+ callback.replace 'http.foo.bar'
77
+ expect { perform }.to raise_error
78
+ end
79
+
80
+ it 'fails with a non-SSL URL' do
81
+ callback.replace 'http://example.com'
82
+ expect { perform }.to raise_error
83
+ end
84
+
85
+ it 'fails with a bad topic name' do
86
+ topic.replace 'foo123$bar'
87
+ expect { perform }.to raise_error
88
+ end
89
+
90
+ it 'fails when an non-success HTTP status is returned' do
91
+ @stub.to_return(status: 500)
92
+ expect { perform }.to raise_error(RuntimeError)
93
+ end
94
+
95
+ it 'fails when the timeout is reached' do
96
+ @stub.to_timeout
97
+ expect { perform }.to raise_error(Faraday::TimeoutError)
98
+ end
99
+ end
100
+
101
+ describe '#created' do
102
+ let(:event) { 'created' }
103
+ it_behaves_like 'an event sender'
104
+ end
105
+
106
+ describe '#updated' do
107
+ let(:event) { 'updated' }
108
+ it_behaves_like 'an event sender'
109
+ end
110
+
111
+ describe '#deleted' do
112
+ let(:event) { 'deleted' }
113
+ it_behaves_like 'an event sender'
114
+ end
115
+
116
+ describe '#noop' do
117
+ let(:event) { 'noop' }
118
+ it_behaves_like 'an event sender'
119
+ end
120
+
121
+ describe '#subscribe' do
122
+ let(:perform) { subject.subscribe(subscribe_options) }
123
+ let(:subscribe_options) {{
124
+ topics: %w(widgets kitten),
125
+ callback: 'https://app.example.com/events',
126
+ timeout: 60_000,
127
+ max: 500
128
+ }}
129
+
130
+ before do
131
+ @stub = stub_request(
132
+ :post, %r{^https://#{options[:uuid]}:x@bus.example.com/subscription$}
133
+ ).with { |r|
134
+ r.headers['Content-Type'] == 'application/json' &&
135
+ JSON.parse(r.body).all? { |k,v| subscribe_options[k.to_sym] == v }
136
+ }
137
+ end
138
+
139
+ it 'passes with correct arguments' do
140
+ expect { perform }.not_to raise_error
141
+ expect(@stub).to have_been_requested
142
+ end
143
+
144
+ it 'fails with a bad callback' do
145
+ subscribe_options[:callback] = 'http://example.com'
146
+ expect { perform }.to raise_error(ArgumentError)
147
+ end
148
+
149
+ it 'fails with a bad timeout' do
150
+ subscribe_options[:timeout] = -5
151
+ expect { perform }.to raise_error(ArgumentError)
152
+ end
153
+
154
+ it 'fails with a bad max number of events' do
155
+ subscribe_options[:max] = 1_000_000
156
+ expect { perform }.to raise_error(ArgumentError)
157
+ end
158
+
159
+ it 'fails with a bad topic list' do
160
+ subscribe_options[:topics] = ['widgets', 'foo123$%bar']
161
+ expect { perform }.to raise_error(ArgumentError)
162
+ end
163
+
164
+ it 'fails on HTTP error' do
165
+ @stub.to_return(status: 500)
166
+ expect { perform }.to raise_error(RuntimeError)
167
+ end
168
+
169
+ it 'accepts a uuid' do
170
+ subscribe_options[:uuid] = 'hello'
171
+ expect { perform }.not_to raise_error
172
+ end
173
+ end
174
+
175
+ describe '#monitor_topics' do
176
+ it 'passes'
177
+ end
178
+
179
+ describe '#monitor_subscriptions' do
180
+ it 'passes'
181
+ end
182
+ end
183
+
@@ -0,0 +1,16 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.run_all_when_everything_filtered = true
9
+ config.raise_errors_for_deprecations!
10
+
11
+ # Run specs in random order to surface order dependencies. If you find an
12
+ # order dependency and want to debug it, you can fix the order by providing
13
+ # the seed, which is printed after each run.
14
+ # --seed 1234
15
+ config.order = 'random'
16
+ end
@@ -0,0 +1,9 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require 'rspec'
4
+ require 'rack/test'
5
+
6
+ RSpec.configure do |conf|
7
+ conf.include Rack::Test::Methods
8
+ end
9
+
metadata ADDED
@@ -0,0 +1,237 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: routemaster-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Julien Letessier
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-07-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.5'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.5'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: guard-rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: webmock
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: pry-nav
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rack-test
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: faraday
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :runtime
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: net-http-persistent
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :runtime
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ - !ruby/object:Gem::Dependency
159
+ name: sinatra
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :runtime
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ description:
175
+ email:
176
+ - julien.letessier@gmail.com
177
+ executables: []
178
+ extensions: []
179
+ extra_rdoc_files: []
180
+ files:
181
+ - ".gitignore"
182
+ - ".gitmodules"
183
+ - ".rspec"
184
+ - ".ruby-version"
185
+ - ".travis.yml"
186
+ - Gemfile
187
+ - Gemfile.lock
188
+ - Guardfile
189
+ - LICENSE.txt
190
+ - README.md
191
+ - Rakefile
192
+ - core_ext/silence_stream.rb
193
+ - routemaster-client.gemspec
194
+ - routemaster/client.rb
195
+ - routemaster/client/openssl.rb
196
+ - routemaster/client/version.rb
197
+ - routemaster/receiver.rb
198
+ - spec/receiver_spec.rb
199
+ - spec/routemaster/client_spec.rb
200
+ - spec/spec_helper.rb
201
+ - spec/support/rack_test.rb
202
+ homepage: http://github.com/HouseTrip/routemaster_client
203
+ licenses:
204
+ - MIT
205
+ post_install_message:
206
+ rdoc_options: []
207
+ require_paths:
208
+ - "."
209
+ required_ruby_version: !ruby/object:Gem::Requirement
210
+ none: false
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ segments:
216
+ - 0
217
+ hash: 4434098965730026885
218
+ required_rubygems_version: !ruby/object:Gem::Requirement
219
+ none: false
220
+ requirements:
221
+ - - ">="
222
+ - !ruby/object:Gem::Version
223
+ version: '0'
224
+ segments:
225
+ - 0
226
+ hash: 4434098965730026885
227
+ requirements: []
228
+ rubyforge_project:
229
+ rubygems_version: 1.8.23.2
230
+ signing_key:
231
+ specification_version: 3
232
+ summary: Client API for the Routemaster event bus
233
+ test_files:
234
+ - spec/receiver_spec.rb
235
+ - spec/routemaster/client_spec.rb
236
+ - spec/spec_helper.rb
237
+ - spec/support/rack_test.rb