ruby_mailman 0.9.0

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