ruby_mailman 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 841cc56a61b4a16b14dcf1126c8a6f1228021042
4
+ data.tar.gz: 82eea3a9156d4c42b3ed1f6ff6d68dd9a8c58532
5
+ SHA512:
6
+ metadata.gz: 596d35d65745351ede2e99fdfdb281becbb4fc703c752a8c75db9911b549da031eecba15ffd8345c90210ac716ae4fbe3914fa4a05809a04e1ffd467ba5c8d24
7
+ data.tar.gz: 464957a5983d49b6f84912775e26102eeed8e205a850076e2650452c90fb36e1df6f460082d6845aa83139535aff422b8736e2628c90a5b27bed64aabf666c4f
data/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ vendor/protobufs
24
+ vendor/bundle
25
+ lib/*pb.rb
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ruby_mailman.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1 @@
1
+ © Regents of the University of Minnesota. All rights reserved.
data/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # Ruby Mailman
2
+
3
+ - A ruby library for interacting with Central Service
4
+ - Sending Messages
5
+ - Subscribing to Channels
6
+ - Follows the spec outlined in https://github.umn.edu/umnapi/services_interface
7
+
8
+ ## Sending Messages
9
+
10
+ Central Services will expect objects to be serialized [protobuf objects](https://github.umn.edu/umnapi/protobufs). If you send other types of objects in your messages it probably will not work.
11
+
12
+ ```ruby
13
+
14
+ module Interfaces
15
+ class Auth < ::Protobuf::Message; end
16
+
17
+ class Auth
18
+ optional :string, :email, 2
19
+ optional :string, :public_key, 3
20
+ optional :string, :private_key, 4
21
+ end
22
+
23
+ end
24
+
25
+
26
+ auth = Interfaces::Auth.new
27
+ message = auth.encode
28
+
29
+ RubyMailman::Mailman.deliver(:create, message)
30
+ RubyMailman::Mailman.deliver(:update, message)
31
+ RubyMailman::Mailman.deliver(:destroy, message)
32
+
33
+ RubyMailman::Mailman.create(message)
34
+ RubyMailman::Mailman.update(message)
35
+ RubyMailman::Mailman.destroy(message)
36
+ ```
37
+
38
+ ### Responses
39
+
40
+ Central Services has 3 types of response:
41
+ - success ('200')
42
+ - retry ('409')
43
+ - failure ('500')
44
+
45
+ #### Success
46
+
47
+ ```ruby
48
+ response = Mailman.deliver(:create, obj)
49
+ response.success?
50
+ #=> true
51
+ response.retry?
52
+ #=> false
53
+ response.fail?
54
+ #=> false
55
+ response.body
56
+ #=> '200'
57
+ ```
58
+
59
+ #### Retry
60
+
61
+ ```ruby
62
+ response = Mailman.deliver(:create, obj)
63
+ response.success?
64
+ #=> false
65
+ response.retry?
66
+ #=> true
67
+ response.fail?
68
+ #=> false
69
+ response.body
70
+ #=> '409'
71
+ ```
72
+
73
+ #### Failure
74
+
75
+ ```ruby
76
+ response = Mailman.deliver(:create, obj)
77
+ response.success?
78
+ #=> false
79
+ response.retry?
80
+ #=> false
81
+ response.fail?
82
+ #=> true
83
+ response.body
84
+ #=> '500'
85
+ ```
86
+
87
+ ## Subscribing
88
+
89
+ You need to write some code that will handle the responses you'll get from subscription. There are a couple of ways to do this.
90
+
91
+ #### Listener Class
92
+
93
+ It just has to implement the `call` message with an arity of two. The channel will be the first parameter, the message will be the second. Do whatever you want within the method.
94
+
95
+ ```ruby
96
+ class MyListener
97
+ def call(channel, message)
98
+ # your code to handle the new message
99
+ end
100
+ end
101
+
102
+
103
+ RubyMailman::Subscription.subscribe(channel: :key, listener: MyListener.new)
104
+ #=> #<RubyMailman::Subscription:0x007fa2d3835978 ...>
105
+ ```
106
+
107
+ #### Listener Lambda
108
+
109
+ Since lambdas respond to `call`, you can do
110
+
111
+ ```ruby
112
+ my_listener = lambda{ |channel, message| #your code }
113
+ RubyMailman::Subscription.subscribe(channel: :key, listener: my_listener)
114
+ #=> #<RubyMailman::Subscription:0x007fa2d3835978 ...>
115
+ ```
116
+
117
+ ### Subscription Messages
118
+
119
+ Your listener will receive `call` with the channel and the message. The message is an instance of RubyMailman::Subscription::Message and responds to:
120
+
121
+ - channel: the channel again
122
+ - action: `create`, `update` or `destroy`
123
+ - content: A serialized protobuff object
124
+
125
+ This is a very specific message, since it's really only designed to do one thing -- tell your service about new/changed/destroyed objects. Let's look at an example of a service that care about Auth objects, and it locally persists those Auth objects as LocalAuth:
126
+
127
+ ```ruby
128
+ class AuthListener
129
+ def call(channel, message)
130
+ a = Interfaces::Auth.new
131
+ a.decode(message.content)
132
+
133
+ AuthLogger.log("Message received on #{message.channel} telling me to #{message.action} the object #{a.to_s}")
134
+
135
+ case message.action
136
+ when 'create'
137
+ LocalAuth.new(a).save #We'll leave the implementation of LocalAuth to your imagination.
138
+ when 'update'
139
+ LocalAuth.replace(a)
140
+ when 'destroy'
141
+ LocalAuth.destroy(a)
142
+ else
143
+ raise ArgumentError
144
+ end
145
+ end
146
+ end
147
+
148
+ RubyMailman::Subscription.new(channel: Auth, listener: AuthListener.new)
149
+ ```
150
+
151
+ ## Setup
152
+
153
+ - Add `gem ruby_mailman` to your service
154
+ - `bundle install --path ./vendor/bundle`
155
+
156
+ ## Local Setup and Running Tests
157
+
158
+ - Clone the repo
159
+ - `bundle install --path ./vendor/bundle`
160
+ - `bundle exec rake`
161
+
162
+ ## Development
163
+
164
+ To work on the gem:
165
+
166
+ - Fork this repo
167
+ - Submit your changes through a pull request
168
+ - Tests are required for any pull requests
169
+
170
+ ## License
171
+
172
+ © Regents of the University of Minnesota. All rights reserved.
173
+
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task :default => :spec
@@ -0,0 +1,24 @@
1
+ class CentralServiceInterface
2
+ def self.deliver(action,object,cs_client = CSTransportClient)
3
+ self.new(cs_client).deliver(action,object)
4
+ end
5
+
6
+ def self.subscribe(channel, listener, cs_client = CSTransportClient)
7
+ self.new(cs_client).subscribe(channel, listener)
8
+ end
9
+
10
+ def initialize(cs_client)
11
+ self.central_service_client = cs_client
12
+ end
13
+
14
+ def deliver(action,object)
15
+ central_service_client.request(action,object)
16
+ end
17
+
18
+ def subscribe(channel, listener)
19
+ central_service_client.subscribe(channel, listener)
20
+ end
21
+
22
+ private
23
+ attr_accessor :central_service_client
24
+ end
@@ -0,0 +1,33 @@
1
+ module RubyMailman
2
+ class Mailman
3
+ def self.deliver(action, obj, cs = CentralServiceInterface)
4
+ self.new(action, obj, cs).deliver
5
+ end
6
+
7
+ def self.create(obj, cs = CentralServiceInterface)
8
+ self.deliver(:create, obj, cs)
9
+ end
10
+
11
+ def self.update(obj, cs = CentralServiceInterface)
12
+ self.deliver(:update, obj, cs)
13
+ end
14
+
15
+ def self.destroy(obj, cs = CentralServiceInterface)
16
+ self.deliver(:destroy, obj, cs)
17
+ end
18
+
19
+ def initialize(action, object, central_service)
20
+ self.action = action
21
+ self.object = object
22
+ self.central_service = central_service
23
+ end
24
+
25
+ def deliver
26
+ Response.build(central_service.deliver(action.to_s, object))
27
+ end
28
+
29
+ private
30
+
31
+ attr_accessor :action, :object, :central_service
32
+ end
33
+ end
@@ -0,0 +1,56 @@
1
+ module RubyMailman
2
+ class Response
3
+ attr_reader :body
4
+ def self.build(raw_response)
5
+ #Work with both a string "200" and an array ["200"]
6
+ raw_response = Array(raw_response).join
7
+ case raw_response
8
+ when "200"
9
+ SuccessResponse.new(raw_response)
10
+ when "409"
11
+ RetryResponse.new(raw_response)
12
+ when "500"
13
+ FailResponse.new(raw_response)
14
+ else
15
+ self.new(raw_response)
16
+ end
17
+ end
18
+
19
+ def success?
20
+ false
21
+ end
22
+
23
+ def retry?
24
+ false
25
+ end
26
+
27
+ def fail?
28
+ false
29
+ end
30
+
31
+ def initialize(body)
32
+ self.body = body
33
+ end
34
+
35
+ private
36
+ attr_writer :body
37
+ end
38
+
39
+ class SuccessResponse < Response
40
+ def success?
41
+ true
42
+ end
43
+ end
44
+
45
+ class RetryResponse < Response
46
+ def retry?
47
+ true
48
+ end
49
+ end
50
+
51
+ class FailResponse < Response
52
+ def fail?
53
+ true
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,15 @@
1
+ module RubyMailman
2
+ class Subscription
3
+ class Message
4
+ attr_reader :channel, :action, :content
5
+ def initialize(channel, raw_message)
6
+ self.channel = channel
7
+ self.action = raw_message[0]
8
+ self.content = raw_message[1]
9
+ end
10
+
11
+ private
12
+ attr_writer :channel, :action, :content
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ module RubyMailman
2
+ class Subscription
3
+ def self.subscribe(args, cs = CentralServiceInterface)
4
+ channel = args.fetch(:channel) do
5
+ raise ArgumentError, "Subscribtion requries a channel"
6
+ end
7
+
8
+ listener = args.fetch(:listener) do
9
+ raise ArgumentError, "Subscribtion requries a listener"
10
+ end
11
+
12
+ self.new(channel, listener, cs)
13
+ end
14
+
15
+ def initialize(channel, listener, cs)
16
+ self.channel = channel
17
+ self.listener = listener
18
+ self.central_service = cs
19
+ central_service.subscribe(channel, listener)
20
+ end
21
+
22
+ private
23
+ attr_accessor :channel, :listener, :central_service
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module RubyMailman
2
+ VERSION = "0.9.0"
3
+ end
@@ -0,0 +1,50 @@
1
+ require 'ffi-rzmq'
2
+
3
+ class ZMQConfiguration
4
+ def self.server
5
+ ENV['0MQServerAddress'] || "tcp://localhost:6666"
6
+ end
7
+ end
8
+
9
+ class ZMQRequestClient
10
+ def self.run(action, object)
11
+ context = ZMQ::Context.new
12
+ connection = context.socket(ZMQ::REQ)
13
+ connection.connect(ZMQConfiguration.server)
14
+ connection.send_strings([action, object])
15
+ receiver = ""
16
+ connection.recv_string(receiver)
17
+ receiver
18
+ end
19
+ end
20
+
21
+ class ZMQSubscriptionClient
22
+ def self.run(channel, listener, message_builder = RubyMailman::Subscription::Message)
23
+ context = ZMQ::Context.new
24
+ connection = context.socket(ZMQ::SUB)
25
+ connection.connect(ZMQConfiguration.server)
26
+ connection.setsockopt(ZMQ::SUBSCRIBE, channel)
27
+
28
+ Thread.new do
29
+ loop do
30
+ received_channel = ''
31
+ connection.recv_string(received_channel)
32
+ message = []
33
+ connection.recv_strings(message)
34
+ listener.call(received_channel, message_builder.new(received_channel, message))
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ class ZMQClient
41
+ attr_reader :connection
42
+
43
+ def self.request(action, object, concrete_client = ZMQRequestClient)
44
+ concrete_client.run(action, object)
45
+ end
46
+
47
+ def self.subscribe(channel, listener, concrete_client = ZMQSubscriptionClient)
48
+ concrete_client.run(channel, listener)
49
+ end
50
+ end
@@ -0,0 +1,8 @@
1
+ require "ruby_mailman/version"
2
+ require "ruby_mailman/mailman"
3
+ require "ruby_mailman/subscription"
4
+ require "ruby_mailman/subscription/message"
5
+ require "ruby_mailman/response"
6
+ require "ruby_mailman/central_service_interface"
7
+ require "ruby_mailman/zmq_client"
8
+ CSTransportClient = ZMQClient
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ruby_mailman/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ruby_mailman"
8
+ spec.version = RubyMailman::VERSION
9
+ spec.authors = ["Ian Whitney"]
10
+ spec.email = ["whit0694@umn.edu"]
11
+ spec.summary = %q{A ruby library for connecting to Central Service}
12
+ spec.description = %q{Ruby implementation of the Central Service interface. Meant for use with UMN API Services written in Ruby.}
13
+ spec.homepage = "https://github.umn.edu/umnapi/ruby_mailman/"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.6"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "protobuf"
23
+ spec.add_development_dependency "rspec", "~> 3.0"
24
+ spec.add_development_dependency "ffi-rzmq"
25
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ require_relative '../../lib/ruby_mailman'
4
+ require_relative '../models/auth.pb'
5
+ require_relative "../zmq_servers/reply"
6
+
7
+ RSpec.describe "Making requests" do
8
+
9
+ let(:auth) { Interfaces::Auth.new }
10
+
11
+ describe "main methods work" do
12
+ before(:example) do
13
+ @server_configuration = {
14
+ port: 6666,
15
+ reply: -> { "#{rand}" }
16
+ }
17
+ @pid = TestServer::Reply.run_as_process(@server_configuration)
18
+ end
19
+
20
+ after(:example) do
21
+ Process.kill("KILL", @pid)
22
+ end
23
+
24
+ it "returns non nil when using the .deliver method" do
25
+ expect(RubyMailman::Mailman.deliver(:create, auth.encode)).not_to be_nil
26
+ expect(RubyMailman::Mailman.deliver(:update, auth.encode)).not_to be_nil
27
+ expect(RubyMailman::Mailman.deliver(:destroy, auth.encode)).not_to be_nil
28
+ end
29
+
30
+ it "returns non nil when using the named method" do
31
+ expect(RubyMailman::Mailman.create(auth.encode)).not_to be_nil
32
+ expect(RubyMailman::Mailman.update(auth.encode)).not_to be_nil
33
+ expect(RubyMailman::Mailman.destroy(auth.encode)).not_to be_nil
34
+ end
35
+ end
36
+
37
+ describe "responses behave as expected" do
38
+ describe "When the server repsonds with '200'" do
39
+ before(:example) do
40
+ @server_configuration = {
41
+ port: 6666,
42
+ reply: -> { "200" }
43
+ }
44
+ @pid = TestServer::Reply.run_as_process(@server_configuration)
45
+ end
46
+
47
+ after(:example) do
48
+ Process.kill("KILL", @pid)
49
+ end
50
+
51
+ it "Requests get a sucessful response" do
52
+ expect(RubyMailman::Mailman.deliver(:create, auth.encode).success?).to be_truthy
53
+ expect(RubyMailman::Mailman.deliver(:create, auth.encode).retry?).to be_falsey
54
+ expect(RubyMailman::Mailman.deliver(:create, auth.encode).fail?).to be_falsey
55
+ expect(RubyMailman::Mailman.deliver(:create, auth.encode).body).to eq("200")
56
+ end
57
+ end
58
+
59
+ describe "When the server repsonds with '409'" do
60
+ before(:example) do
61
+ @server_configuration = {
62
+ port: 6666,
63
+ reply: -> { "409" }
64
+ }
65
+ @pid = TestServer::Reply.run_as_process(@server_configuration)
66
+ end
67
+
68
+ after(:example) do
69
+ Process.kill("KILL", @pid)
70
+ end
71
+
72
+ it "Requests get a retry response" do
73
+ expect(RubyMailman::Mailman.deliver(:create, auth.encode).success?).to be_falsey
74
+ expect(RubyMailman::Mailman.deliver(:create, auth.encode).retry?).to be_truthy
75
+ expect(RubyMailman::Mailman.deliver(:create, auth.encode).fail?).to be_falsey
76
+ expect(RubyMailman::Mailman.deliver(:create, auth.encode).body).to eq("409")
77
+ end
78
+ end
79
+
80
+ describe "When the server repsonds with '500'" do
81
+ before(:example) do
82
+ @server_configuration = {
83
+ port: 6666,
84
+ reply: -> { "500" }
85
+ }
86
+ @pid = TestServer::Reply.run_as_process(@server_configuration)
87
+ end
88
+
89
+ after(:example) do
90
+ Process.kill("KILL", @pid)
91
+ end
92
+
93
+ it "Requests get a failure response" do
94
+ expect(RubyMailman::Mailman.deliver(:create, auth.encode).success?).to be_falsey
95
+ expect(RubyMailman::Mailman.deliver(:create, auth.encode).retry?).to be_falsey
96
+ expect(RubyMailman::Mailman.deliver(:create, auth.encode).fail?).to be_truthy
97
+ expect(RubyMailman::Mailman.deliver(:create, auth.encode).body).to eq("500")
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
+ require 'protobuf'
4
+
5
+ require_relative '../models/auth.pb'
6
+ require_relative '../../lib/ruby_mailman'
7
+ require_relative "../zmq_servers/publish"
8
+
9
+ class MyListener
10
+ attr_reader :channel, :message
11
+ def call(channel, message)
12
+ @channel = channel
13
+ @message = message
14
+ end
15
+ end
16
+
17
+ RSpec.describe "Subscribing to channels" do
18
+ let(:listener) { MyListener.new }
19
+
20
+ before(:example) do
21
+ @message_action = ['create','update','destroy'].sample
22
+ auth = Interfaces::Auth.new
23
+ auth.email = rand.to_s
24
+ auth.public_key = SecureRandom.uuid
25
+ auth.private_key = SecureRandom.uuid
26
+ @message_content = auth.encode
27
+
28
+ @server_configuration = {
29
+ channel: "mychannel",
30
+ port: 6666,
31
+ message_parts: [@message_action, @message_content],
32
+ publish_delay: 0.05
33
+ }
34
+ @pid = TestServer::Publish.run_as_process(@server_configuration)
35
+ @test_delay = @server_configuration[:publish_delay] * 2
36
+
37
+ #wait so the publisher process can get rolling
38
+ sleep(@test_delay)
39
+ end
40
+
41
+ after(:example) do
42
+ Process.kill("KILL", @pid)
43
+ end
44
+
45
+ it "returns a RubyMailman::Subscription instance when subscribing" do
46
+ subscription_return = RubyMailman::Subscription.subscribe(channel: @server_configuration[:channel], listener: listener)
47
+ expect(subscription_return).to be_a(RubyMailman::Subscription)
48
+ end
49
+
50
+ it "calls the listener when a new message is published" do
51
+ expect(listener).to receive(:call).at_least(:once)
52
+ RubyMailman::Subscription.subscribe(channel: @server_configuration[:channel], listener: listener)
53
+ sleep(@test_delay)
54
+ end
55
+
56
+ it "calls the listener with the channel name and a RubyMailman::Subscription::Message instance" do
57
+ RubyMailman::Subscription.subscribe(channel: @server_configuration[:channel], listener: listener)
58
+ sleep(@test_delay)
59
+
60
+ expect(listener.channel).to eq(@server_configuration[:channel])
61
+ expect(listener.message).to be_a(RubyMailman::Subscription::Message)
62
+ end
63
+
64
+ it "The message instance has the channel, action and content we expect" do
65
+ RubyMailman::Subscription.subscribe(channel: @server_configuration[:channel], listener: listener)
66
+ sleep(@test_delay)
67
+
68
+ expect(listener.message.channel).to eq(@server_configuration[:channel])
69
+ expect(listener.message.action).to eq(@message_action)
70
+ expect(listener.message.content).to eq(@message_content)
71
+ end
72
+ end
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+ require_relative "../../../lib/ruby_mailman"
3
+
4
+ RSpec.describe CentralServiceInterface do
5
+ let (:client_class_double) { class_double(ZMQClient) }
6
+
7
+ describe "self.deliver" do
8
+ before do
9
+ @action = "action#{rand}"
10
+ @object = Object.new
11
+ end
12
+
13
+ it "uses the provided client to make a request" do
14
+ expect(client_class_double).to receive(:request).with(@action, @object) { true }
15
+ CentralServiceInterface.deliver(@action,@object,client_class_double)
16
+ end
17
+ end
18
+
19
+ describe "self.subscribe" do
20
+ before do
21
+ @channel = "channel#{rand}"
22
+ @listener = Object.new
23
+ end
24
+
25
+ it "uses the provided client to subscribe" do
26
+ expect(client_class_double).to receive(:subscribe).with(@channel, @listener) { true }
27
+ CentralServiceInterface.subscribe(@channel, @listener, client_class_double)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+ require_relative "../../../lib/ruby_mailman"
3
+
4
+ Mailman = RubyMailman::Mailman
5
+
6
+ describe Mailman do
7
+ let (:cs_interface_double) { double(CentralServiceInterface) }
8
+ let (:object_double) { Object.new }
9
+
10
+ before do
11
+ allow(RubyMailman::Response).to receive(:build) { Object.new }
12
+ end
13
+
14
+ describe "notifies of creation" do
15
+ it "has the CentralServiceInterface do the work" do
16
+ expect(cs_interface_double).to receive(:deliver).with("create", object_double)
17
+ Mailman.deliver(:create, object_double, cs_interface_double)
18
+ end
19
+
20
+ it "supports the .deliver syntax" do
21
+ expect(cs_interface_double).to receive(:deliver).with("create", object_double)
22
+ Mailman.deliver(:create, object_double, cs_interface_double)
23
+ end
24
+
25
+ it "works with the .create syntax" do
26
+ expect(cs_interface_double).to receive(:deliver).with("create", object_double)
27
+ Mailman.create(object_double, cs_interface_double)
28
+ end
29
+ end
30
+
31
+ describe "notifies of update" do
32
+ it "has the CentralServiceInterface do the work" do
33
+ expect(cs_interface_double).to receive(:deliver).with("update", object_double)
34
+ Mailman.deliver(:update, object_double, cs_interface_double)
35
+ end
36
+
37
+ it "supports the .deliver syntax" do
38
+ expect(cs_interface_double).to receive(:deliver).with("update", object_double)
39
+ Mailman.deliver(:update, object_double, cs_interface_double)
40
+ end
41
+
42
+ it "works with the .update syntax" do
43
+ expect(cs_interface_double).to receive(:deliver).with("update", object_double)
44
+ Mailman.update(object_double, cs_interface_double)
45
+ end
46
+ end
47
+
48
+ describe "notifies of destruction" do
49
+ it "has the CentralServiceInterface do the work" do
50
+ expect(cs_interface_double).to receive(:deliver).with("destroy", object_double)
51
+ Mailman.deliver(:destroy, object_double, cs_interface_double)
52
+ end
53
+
54
+ it "supports the .deliver syntax" do
55
+ expect(cs_interface_double).to receive(:deliver).with("destroy", object_double)
56
+ Mailman.deliver(:destroy, object_double, cs_interface_double)
57
+ end
58
+
59
+ it "works with the .destroy syntax" do
60
+ expect(cs_interface_double).to receive(:deliver).with("destroy", object_double)
61
+ Mailman.destroy(object_double, cs_interface_double)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+ require_relative "../../../lib/ruby_mailman"
3
+
4
+ Response = RubyMailman::Response
5
+ SuccessResponse = RubyMailman::SuccessResponse
6
+ RetryResponse = RubyMailman::RetryResponse
7
+ FailResponse = RubyMailman::FailResponse
8
+
9
+ describe Response do
10
+ describe "build" do
11
+ it "creates and returns a SuccessResponse if the raw_response is '200'" do
12
+ x = Object.new
13
+ expect(SuccessResponse).to receive(:new).with('200') { x }
14
+ expect(Response.build('200')).to eq(x)
15
+ end
16
+
17
+ it "returns a SuccessResponse if the raw_response is '409'" do
18
+ x = Object.new
19
+ expect(RetryResponse).to receive(:new).with('409') { x }
20
+ expect(Response.build('409')).to eq(x)
21
+ end
22
+
23
+ it "returns a FailResponse if the raw_response is '500'" do
24
+ x = Object.new
25
+ expect(FailResponse).to receive(:new).with('500') { x }
26
+ expect(Response.build('500')).to eq(x)
27
+ end
28
+
29
+ it "returns a undecorated response with any other raw_response" do
30
+ x = Object.new
31
+ raw_response = rand(100).to_s
32
+ expect(Response).to receive(:new).with(raw_response) { x }
33
+ expect(Response.build(raw_response)).to eq(x)
34
+ end
35
+
36
+ it "has a body equal to the raw_response" do
37
+ ['200', '409', '500', rand(100).to_s].each do |code|
38
+ expect(Response.build(code).body).to eq(code)
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "success?" do
44
+ it "is false" do
45
+ expect(Response.new('').success?).to be_falsey
46
+ end
47
+ end
48
+
49
+ describe "retry?" do
50
+ it "is false" do
51
+ expect(Response.new('').retry?).to be_falsey
52
+ end
53
+ end
54
+
55
+ describe "fail?" do
56
+ it "is false" do
57
+ expect(Response.new('').fail?).to be_falsey
58
+ end
59
+ end
60
+
61
+ describe SuccessResponse do
62
+ describe "success?" do
63
+ it "is false" do
64
+ expect(SuccessResponse.new('').success?).to be_truthy
65
+ end
66
+ end
67
+
68
+ describe "retry?" do
69
+ it "is false" do
70
+ expect(SuccessResponse.new('').retry?).to be_falsey
71
+ end
72
+ end
73
+
74
+ describe "fail?" do
75
+ it "is false" do
76
+ expect(SuccessResponse.new('').fail?).to be_falsey
77
+ end
78
+ end
79
+ end
80
+
81
+ describe FailResponse do
82
+ describe "success?" do
83
+ it "is false" do
84
+ expect(FailResponse.new('').success?).to be_falsey
85
+ end
86
+ end
87
+
88
+ describe "retry?" do
89
+ it "is false" do
90
+ expect(FailResponse.new('').retry?).to be_falsey
91
+ end
92
+ end
93
+
94
+ describe "fail?" do
95
+ it "is false" do
96
+ expect(FailResponse.new('').fail?).to be_truthy
97
+ end
98
+ end
99
+ end
100
+
101
+ describe RetryResponse do
102
+ describe "success?" do
103
+ it "is false" do
104
+ expect(RetryResponse.new('').success?).to be_falsey
105
+ end
106
+ end
107
+
108
+ describe "retry?" do
109
+ it "is false" do
110
+ expect(RetryResponse.new('').retry?).to be_truthy
111
+ end
112
+ end
113
+
114
+ describe "fail?" do
115
+ it "is false" do
116
+ expect(RetryResponse.new('').fail?).to be_falsey
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require_relative "../../../lib/ruby_mailman"
3
+
4
+ Subscription = RubyMailman::Subscription
5
+
6
+ describe Subscription do
7
+ describe "subscribe" do
8
+ let(:args) { {channel: 'test', listener: Object.new } }
9
+ let(:cs_mock) { double(CentralServiceInterface) }
10
+
11
+ before do
12
+ allow(cs_mock).to receive(:subscribe) { true }
13
+ end
14
+
15
+ it "implements the method" do
16
+ expect(Subscription.respond_to?(:subscribe)).to be_truthy
17
+ end
18
+
19
+ it "requires a channel" do
20
+ args.delete(:channel)
21
+ expect { Subscription.subscribe(args,cs_mock) }.to raise_error(ArgumentError)
22
+ end
23
+
24
+ it "requires a listener" do
25
+ args.delete(:listener)
26
+ expect { Subscription.subscribe(args,cs_mock) }.to raise_error(ArgumentError)
27
+ end
28
+
29
+ it "uses the Central Service Interface subscription method" do
30
+ expect(cs_mock).to receive(:subscribe).with(args[:channel], args[:listener])
31
+ Subscription.subscribe(args, cs_mock)
32
+ end
33
+
34
+ it "returns a Subscription instance" do
35
+ subscription_double = instance_double(Subscription)
36
+ expect(Subscription).to receive(:new) { subscription_double }
37
+ expect(Subscription.subscribe(args, cs_mock)).to eq(subscription_double)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,88 @@
1
+ require "spec_helper"
2
+ require_relative "../../../lib/ruby_mailman"
3
+ require_relative "../../zmq_servers/reply"
4
+ require_relative "../../zmq_servers/publish"
5
+
6
+ RSpec.describe ZMQClient do
7
+ describe "request" do
8
+ let (:request_client) { class_double(ZMQRequestClient) }
9
+ let (:action) { "action#{rand}" }
10
+ let (:object) { Object.new }
11
+
12
+ it "uses the provided client to make a request" do
13
+ expect(request_client).to receive(:run).with(action, object) { true }
14
+ ZMQClient.request(action, object, request_client)
15
+ end
16
+ end
17
+
18
+ describe "subscribe" do
19
+ let (:subscription_client) { class_double(ZMQSubscriptionClient) }
20
+ let (:channel) { "channel#{rand}" }
21
+ let (:listener) { Object.new }
22
+
23
+ it "uses the provided client to make a request" do
24
+ expect(subscription_client).to receive(:run).with(channel, listener) { true }
25
+ ZMQClient.subscribe(channel, listener, subscription_client)
26
+ end
27
+ end
28
+ end
29
+
30
+ RSpec.describe ZMQRequestClient do
31
+ describe "run" do
32
+ before(:example) do
33
+ reply_randomizer = rand
34
+ @server_configuration = {
35
+ port: 6666,
36
+ reply: -> {"reply #{reply_randomizer}"},
37
+ publish_delay: 0.03
38
+ }
39
+ @pid = TestServer::Reply.run_as_process(@server_configuration)
40
+ @test_delay = @server_configuration[:publish_delay] * 2
41
+ #wait so the publisher process can get rolling
42
+ sleep(@test_delay)
43
+ end
44
+
45
+ it "returns a reply" do
46
+ action = 'action'
47
+ object = 'object'
48
+ reply = ZMQRequestClient.run(action,object)
49
+ expect(reply).to eq(@server_configuration[:reply].call)
50
+ end
51
+
52
+ after(:example) do
53
+ Process.kill("KILL", @pid)
54
+ end
55
+ end
56
+ end
57
+
58
+ RSpec.describe ZMQSubscriptionClient do
59
+ describe "run" do
60
+ let (:message_builder) { class_double(RubyMailman::Subscription::Message) }
61
+ let (:message) { instance_double(RubyMailman::Subscription::Message) }
62
+
63
+ before(:example) do
64
+ @server_configuration = {
65
+ channel: "mychannel",
66
+ port: 6666,
67
+ message_parts: ["published message", rand(20).to_s],
68
+ publish_delay: 0.03
69
+ }
70
+ @pid = TestServer::Publish.run_as_process(@server_configuration)
71
+ @test_delay = @server_configuration[:publish_delay] * 2
72
+ #wait so the publisher process can get rolling
73
+ sleep(@test_delay)
74
+ end
75
+
76
+ it "Creates a Message and then calls the listener" do
77
+ listener = lambda{|channel, message| rand}
78
+ expect(message_builder).to receive(:new).at_least(:once).with(@server_configuration[:channel],@server_configuration[:message_parts]) { message }
79
+ expect(listener).to receive(:call).at_least(:once).with(@server_configuration[:channel], message)
80
+ ZMQSubscriptionClient.run(@server_configuration[:channel], listener, message_builder)
81
+ sleep(@test_delay)
82
+ end
83
+
84
+ after(:example) do
85
+ Process.kill("KILL", @pid)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,24 @@
1
+ ##
2
+ # This file is auto-generated. DO NOT EDIT!
3
+ #
4
+ require 'protobuf/message'
5
+
6
+ module Interfaces
7
+
8
+ ##
9
+ # Message Classes
10
+ #
11
+ class Auth < ::Protobuf::Message; end
12
+
13
+
14
+ ##
15
+ # Message Fields
16
+ #
17
+ class Auth
18
+ optional :string, :email, 2
19
+ optional :string, :public_key, 3
20
+ optional :string, :private_key, 4
21
+ end
22
+
23
+ end
24
+
@@ -0,0 +1 @@
1
+ require "rspec/core"
@@ -0,0 +1,27 @@
1
+ require 'ffi-rzmq'
2
+
3
+ module TestServer
4
+ class Publish
5
+ def self.run_as_process(configuration)
6
+ Kernel.fork do
7
+
8
+ port = configuration[:port] || 6666
9
+ channel = configuration[:channel]
10
+ message_parts = configuration[:message_parts]
11
+ publish_delay = configuration[:publish_delay]
12
+
13
+ context = ZMQ::Context.new
14
+ publisher = context.socket(ZMQ::PUB)
15
+ publisher.setsockopt(ZMQ::LINGER,1)
16
+ publisher.bind("tcp://*:#{port}")
17
+
18
+ loop do
19
+ full_message = [message_parts[0].to_s, message_parts[1].to_s]
20
+ publisher.send_string(channel, ZMQ::SNDMORE)
21
+ publisher.send_strings(full_message)
22
+ sleep(publish_delay)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ require 'ffi-rzmq'
2
+
3
+ module TestServer
4
+ class Reply
5
+ def self.run_as_process(configuration)
6
+ port = configuration[:port] || '6666'
7
+ reply = configuration[:reply]
8
+ Kernel.fork do
9
+ context = ZMQ::Context.new
10
+ socket = context.socket(ZMQ::REP)
11
+ socket.bind("tcp://*:#{port}")
12
+ loop do
13
+ request = []
14
+ socket.recv_strings(request)
15
+ socket.send_string(reply.call)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_mailman
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Ian Whitney
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: protobuf
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: ffi-rzmq
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Ruby implementation of the Central Service interface. Meant for use with
84
+ UMN API Services written in Ruby.
85
+ email:
86
+ - whit0694@umn.edu
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".ruby-version"
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - lib/ruby_mailman.rb
98
+ - lib/ruby_mailman/central_service_interface.rb
99
+ - lib/ruby_mailman/mailman.rb
100
+ - lib/ruby_mailman/response.rb
101
+ - lib/ruby_mailman/subscription.rb
102
+ - lib/ruby_mailman/subscription/message.rb
103
+ - lib/ruby_mailman/version.rb
104
+ - lib/ruby_mailman/zmq_client.rb
105
+ - ruby_mailman.gemspec
106
+ - spec/integration/request_spec.rb
107
+ - spec/integration/subscribe_spec.rb
108
+ - spec/lib/ruby_mailman/central_service_interface_spec.rb
109
+ - spec/lib/ruby_mailman/mailman_spec.rb
110
+ - spec/lib/ruby_mailman/response_spec.rb
111
+ - spec/lib/ruby_mailman/subscription_spec.rb
112
+ - spec/lib/ruby_mailman/zmq_client_spec.rb
113
+ - spec/models/auth.pb.rb
114
+ - spec/spec_helper.rb
115
+ - spec/zmq_servers/publish.rb
116
+ - spec/zmq_servers/reply.rb
117
+ homepage: https://github.umn.edu/umnapi/ruby_mailman/
118
+ licenses: []
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.2.2
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: A ruby library for connecting to Central Service
140
+ test_files:
141
+ - spec/integration/request_spec.rb
142
+ - spec/integration/subscribe_spec.rb
143
+ - spec/lib/ruby_mailman/central_service_interface_spec.rb
144
+ - spec/lib/ruby_mailman/mailman_spec.rb
145
+ - spec/lib/ruby_mailman/response_spec.rb
146
+ - spec/lib/ruby_mailman/subscription_spec.rb
147
+ - spec/lib/ruby_mailman/zmq_client_spec.rb
148
+ - spec/models/auth.pb.rb
149
+ - spec/spec_helper.rb
150
+ - spec/zmq_servers/publish.rb
151
+ - spec/zmq_servers/reply.rb