rack-rabbit 0.5.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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/EXAMPLES.md +212 -0
  4. data/Gemfile +14 -0
  5. data/Gemfile.lock +42 -0
  6. data/LICENSE +21 -0
  7. data/README.md +412 -0
  8. data/Rakefile +5 -0
  9. data/bin/rack-rabbit +96 -0
  10. data/bin/rr +99 -0
  11. data/lib/rack-rabbit.rb +63 -0
  12. data/lib/rack-rabbit/adapter.rb +85 -0
  13. data/lib/rack-rabbit/adapter/amqp.rb +114 -0
  14. data/lib/rack-rabbit/adapter/bunny.rb +87 -0
  15. data/lib/rack-rabbit/adapter/mock.rb +92 -0
  16. data/lib/rack-rabbit/client.rb +181 -0
  17. data/lib/rack-rabbit/config.rb +260 -0
  18. data/lib/rack-rabbit/handler.rb +44 -0
  19. data/lib/rack-rabbit/message.rb +95 -0
  20. data/lib/rack-rabbit/middleware/program_name.rb +34 -0
  21. data/lib/rack-rabbit/response.rb +43 -0
  22. data/lib/rack-rabbit/server.rb +263 -0
  23. data/lib/rack-rabbit/signals.rb +62 -0
  24. data/lib/rack-rabbit/subscriber.rb +77 -0
  25. data/lib/rack-rabbit/worker.rb +84 -0
  26. data/rack-rabbit.gemspec +26 -0
  27. data/test/apps/config.ru +7 -0
  28. data/test/apps/custom.conf +27 -0
  29. data/test/apps/custom.ru +7 -0
  30. data/test/apps/empty.conf +1 -0
  31. data/test/apps/error.ru +7 -0
  32. data/test/apps/mirror.ru +19 -0
  33. data/test/apps/sinatra.ru +37 -0
  34. data/test/apps/sleep.ru +21 -0
  35. data/test/test_case.rb +154 -0
  36. data/test/unit/middleware/test_program_name.rb +32 -0
  37. data/test/unit/test_client.rb +275 -0
  38. data/test/unit/test_config.rb +403 -0
  39. data/test/unit/test_handler.rb +92 -0
  40. data/test/unit/test_message.rb +213 -0
  41. data/test/unit/test_response.rb +59 -0
  42. data/test/unit/test_signals.rb +45 -0
  43. data/test/unit/test_subscriber.rb +140 -0
  44. metadata +91 -0
@@ -0,0 +1,92 @@
1
+ require_relative '../test_case'
2
+
3
+ module RackRabbit
4
+ class TestHandler < TestCase
5
+
6
+ #--------------------------------------------------------------------------
7
+
8
+ def test_handle_message
9
+
10
+ handler = build_handler
11
+ message = build_message
12
+
13
+ response = handler.handle(message)
14
+
15
+ assert_equal(200, response.status)
16
+ assert_equal({}, response.headers)
17
+ assert_equal("Hello World", response.body)
18
+
19
+ end
20
+
21
+ #--------------------------------------------------------------------------
22
+
23
+ def test_handle_GET
24
+
25
+ handler = build_handler(:rack_file => MIRROR_RACK_APP)
26
+ message = build_message(:method => :GET, :path => "/my/path?foo=bar", :body => "hello")
27
+
28
+ response = handler.handle(message)
29
+ mirror = JSON.parse(response.body)
30
+
31
+ assert_equal(200, response.status)
32
+ assert_equal("GET", mirror["method"])
33
+ assert_equal("/my/path", mirror["path"])
34
+ assert_equal("bar", mirror["params"]["foo"])
35
+ assert_equal("hello", mirror["body"])
36
+
37
+ end
38
+
39
+ #--------------------------------------------------------------------------
40
+
41
+ def test_handle_POST
42
+
43
+ handler = build_handler(:rack_file => MIRROR_RACK_APP)
44
+ message = build_message(:method => :POST, :path => "/my/path?foo=bar", :body => "hello")
45
+
46
+ response = handler.handle(message)
47
+ mirror = JSON.parse(response.body)
48
+
49
+ assert_equal(200, response.status)
50
+ assert_equal("POST", mirror["method"])
51
+ assert_equal("/my/path", mirror["path"])
52
+ assert_equal("bar", mirror["params"]["foo"])
53
+ assert_equal("hello", mirror["body"])
54
+
55
+ end
56
+
57
+ #--------------------------------------------------------------------------
58
+
59
+ def test_handle_POST_form_data
60
+
61
+ handler = build_handler(:rack_file => MIRROR_RACK_APP)
62
+ message = build_message(:method => :POST, :path => "/my/path", :body => "foo=bar", :content_type => CONTENT::FORM_URLENCODED)
63
+
64
+ response = handler.handle(message)
65
+ mirror = JSON.parse(response.body)
66
+
67
+ assert_equal(200, response.status)
68
+ assert_equal("POST", mirror["method"])
69
+ assert_equal("/my/path", mirror["path"])
70
+ assert_equal("bar", mirror["params"]["foo"])
71
+ assert_equal("foo=bar", mirror["body"])
72
+
73
+ end
74
+
75
+ #--------------------------------------------------------------------------
76
+
77
+ def test_handle_message_that_causes_rack_app_to_raise_an_exception
78
+
79
+ handler = build_handler(:rack_file => ERROR_RACK_APP)
80
+ message = build_message
81
+ response = handler.handle(message)
82
+
83
+ assert_equal(500, response.status)
84
+ assert_equal("Internal Server Error", response.body)
85
+ assert_equal({}, response.headers)
86
+
87
+ end
88
+
89
+ #--------------------------------------------------------------------------
90
+
91
+ end # class TestHandler
92
+ end # module RackRabbit
@@ -0,0 +1,213 @@
1
+ require_relative '../test_case'
2
+
3
+ module RackRabbit
4
+ class TestMessage < TestCase
5
+
6
+ #--------------------------------------------------------------------------
7
+
8
+ def test_default_message
9
+
10
+ message = build_message
11
+
12
+ assert_equal(nil, message.delivery_tag)
13
+ assert_equal(nil, message.reply_to)
14
+ assert_equal(nil, message.correlation_id)
15
+ assert_equal(nil, message.body)
16
+ assert_equal({}, message.headers)
17
+ assert_equal(:GET, message.method)
18
+ assert_equal("", message.uri)
19
+ assert_equal(nil, message.status)
20
+ assert_equal(nil, message.path)
21
+ assert_equal(nil, message.query)
22
+ assert_equal(nil, message.content_type)
23
+ assert_equal(nil, message.content_encoding)
24
+ assert_equal(0, message.content_length)
25
+ assert_equal(false, message.should_reply?)
26
+ assert_equal(false, message.acknowledged?)
27
+ assert_equal(false, message.rejected?)
28
+
29
+ end
30
+
31
+ #--------------------------------------------------------------------------
32
+
33
+ def test_populated_message
34
+
35
+ headers = { "additional" => "header" }
36
+
37
+ message = build_message({
38
+ :delivery_tag => DELIVERY_TAG,
39
+ :reply_to => REPLY_TO,
40
+ :correlation_id => CORRELATION_ID,
41
+ :content_type => CONTENT::PLAIN_TEXT,
42
+ :content_encoding => CONTENT::UTF8,
43
+ :method => :POST,
44
+ :path => URI,
45
+ :status => 200,
46
+ :headers => headers,
47
+ :body => BODY
48
+ })
49
+
50
+ assert_equal(DELIVERY_TAG, message.delivery_tag)
51
+ assert_equal(REPLY_TO, message.reply_to)
52
+ assert_equal(CORRELATION_ID, message.correlation_id)
53
+ assert_equal(BODY, message.body)
54
+ assert_equal(headers, message.headers)
55
+ assert_equal(:POST, message.method)
56
+ assert_equal(URI, message.uri)
57
+ assert_equal(200, message.status)
58
+ assert_equal(PATH, message.path)
59
+ assert_equal(QUERY, message.query)
60
+ assert_equal(CONTENT::PLAIN_TEXT, message.content_type)
61
+ assert_equal(CONTENT::UTF8, message.content_encoding)
62
+ assert_equal(BODY.length, message.content_length)
63
+ assert_equal(true, message.should_reply?)
64
+ assert_equal(false, message.acknowledged?)
65
+ assert_equal(false, message.rejected?)
66
+
67
+ end
68
+
69
+ #--------------------------------------------------------------------------
70
+
71
+ def test_get_rack_env
72
+
73
+ config = build_config(:app_id => APP_ID)
74
+
75
+ message = build_message({
76
+ :method => :GET,
77
+ :path => URI,
78
+ :body => BODY,
79
+ :content_type => CONTENT::PLAIN_TEXT,
80
+ :content_encoding => CONTENT::UTF8,
81
+ :headers => { "additional" => "header" }
82
+ })
83
+
84
+ env = message.get_rack_env(config.rack_env)
85
+
86
+ assert_equal(message, env['rabbit.message'])
87
+ assert_equal(BODY, env['rack.input'].read)
88
+ assert_equal(:GET, env['REQUEST_METHOD'])
89
+ assert_equal(URI, env['REQUEST_PATH'])
90
+ assert_equal(PATH, env['PATH_INFO'])
91
+ assert_equal(QUERY, env['QUERY_STRING'])
92
+ assert_equal(CONTENT::PLAIN_TEXT_UTF8, env['CONTENT_TYPE'])
93
+ assert_equal(BODY.length, env['CONTENT_LENGTH'])
94
+ assert_equal(Rack::VERSION, env['rack.version'])
95
+ assert_equal(config.logger, env['rack.logger'])
96
+ assert_equal($stderr, env['rack.errors'])
97
+ assert_equal(false, env['rack.multithread'])
98
+ assert_equal(true, env['rack.multiprocess'])
99
+ assert_equal(false, env['rack.run_once'])
100
+ assert_equal('http', env['rack.url_scheme'])
101
+ assert_equal(APP_ID, env['SERVER_NAME'])
102
+ assert_equal("header", env["additional"])
103
+
104
+ end
105
+
106
+ #--------------------------------------------------------------------------
107
+
108
+ def test_get_rack_env_content_type_and_encoding_sensible_defaults
109
+
110
+ m1 = build_message({ :content_type => nil, :content_encoding => nil })
111
+ m2 = build_message({ :content_type => "TYPE", :content_encoding => nil })
112
+ m3 = build_message({ :content_type => nil, :content_encoding => "ENCODING" })
113
+ m4 = build_message({ :content_type => "TYPE", :content_encoding => "ENCODING" })
114
+
115
+ assert_equal("text/plain; charset=\"utf-8\"", m1.get_rack_env['CONTENT_TYPE'])
116
+ assert_equal( "TYPE; charset=\"utf-8\"", m2.get_rack_env['CONTENT_TYPE'])
117
+ assert_equal("text/plain; charset=\"ENCODING\"", m3.get_rack_env['CONTENT_TYPE'])
118
+ assert_equal( "TYPE; charset=\"ENCODING\"", m4.get_rack_env['CONTENT_TYPE'])
119
+
120
+ end
121
+
122
+ #--------------------------------------------------------------------------
123
+
124
+ def test_should_reply?
125
+ m1 = build_message(:reply_to => nil)
126
+ m2 = build_message(:reply_to => REPLY_TO)
127
+ assert_equal(false, m1.should_reply?)
128
+ assert_equal(true, m2.should_reply?)
129
+ end
130
+
131
+ #--------------------------------------------------------------------------
132
+
133
+ def test_get_reply_properties
134
+
135
+ config = build_config(:app_id => APP_ID)
136
+
137
+ message = build_message({
138
+ :reply_to => REPLY_TO,
139
+ :correlation_id => CORRELATION_ID,
140
+ :content_type => "request.content.type",
141
+ :content_encoding => "request.content.encoding",
142
+ :method => "request.method",
143
+ :path => "request.path",
144
+ :body => "request.body"
145
+ })
146
+
147
+ response = build_response(200, "response.body", {
148
+ :content_type => "response.content.type",
149
+ :content_encoding => "response.content.encoding",
150
+ "additional" => "header"
151
+ })
152
+
153
+ Timecop.freeze do
154
+
155
+ properties = message.get_reply_properties(response, config)
156
+
157
+ assert_equal(APP_ID, properties[:app_id])
158
+ assert_equal(REPLY_TO, properties[:routing_key])
159
+ assert_equal(CORRELATION_ID, properties[:correlation_id])
160
+ assert_equal(Time.now.to_i, properties[:timestamp])
161
+ assert_equal("response.content.type", properties[:content_type])
162
+ assert_equal("response.content.encoding", properties[:content_encoding])
163
+ assert_equal("header", properties[:headers]["additional"])
164
+
165
+ end
166
+
167
+ end
168
+
169
+ #--------------------------------------------------------------------------
170
+
171
+ def test_ack
172
+
173
+ message = build_message(:delivery_tag => DELIVERY_TAG)
174
+
175
+ assert_equal(false, message.acknowledged?)
176
+ assert_equal(false, message.rejected?)
177
+ assert_equal([], message.rabbit.acked_messages)
178
+ assert_equal([], message.rabbit.rejected_messages)
179
+
180
+ message.ack
181
+
182
+ assert_equal(true, message.acknowledged?)
183
+ assert_equal(false, message.rejected?)
184
+ assert_equal([DELIVERY_TAG], message.rabbit.acked_messages)
185
+ assert_equal([], message.rabbit.rejected_messages)
186
+
187
+ end
188
+
189
+ #--------------------------------------------------------------------------
190
+
191
+ def test_reject
192
+
193
+ message = build_message(:delivery_tag => DELIVERY_TAG)
194
+
195
+ assert_equal(false, message.acknowledged?)
196
+ assert_equal(false, message.rejected?)
197
+ assert_equal([], message.rabbit.acked_messages)
198
+ assert_equal([], message.rabbit.rejected_messages)
199
+
200
+ message.reject
201
+
202
+ assert_equal(false, message.acknowledged?)
203
+ assert_equal(true, message.rejected?)
204
+ assert_equal([], message.rabbit.acked_messages)
205
+ assert_equal([DELIVERY_TAG], message.rabbit.rejected_messages)
206
+
207
+ end
208
+
209
+ #--------------------------------------------------------------------------
210
+
211
+ end # class TestMessage
212
+ end # module RackRabbit
213
+
@@ -0,0 +1,59 @@
1
+ require_relative '../test_case'
2
+
3
+ module RackRabbit
4
+ class TestResponse < TestCase
5
+
6
+ #--------------------------------------------------------------------------
7
+
8
+ def test_response
9
+
10
+ response = build_response(200, BODY, :foo => "bar")
11
+
12
+ assert_equal(200, response.status)
13
+ assert_equal("bar", response.headers[:foo])
14
+ assert_equal(BODY, response.body)
15
+ assert_equal(true, response.succeeded?)
16
+ assert_equal(false, response.failed?)
17
+ assert_equal(BODY, response.to_s)
18
+
19
+ end
20
+
21
+ #--------------------------------------------------------------------------
22
+
23
+ def test_succeeded_and_failed
24
+
25
+ expected_success = [ 200, 201, 202 ]
26
+ expected_failure = [ 400, 404, 500 ]
27
+
28
+ expected_success.each do |status|
29
+ response = build_response(status, BODY)
30
+ assert_equal(true, response.succeeded?, "status #{status} should be considered a success")
31
+ assert_equal(false, response.failed?, "status #{status} should be considered a success")
32
+ end
33
+
34
+ expected_failure.each do |status|
35
+ response = build_response(status, BODY)
36
+ assert_equal(false, response.succeeded?, "status #{status} should be considered a failure")
37
+ assert_equal(true, response.failed?, "status #{status} should be considered a failure")
38
+ end
39
+
40
+ end
41
+
42
+ #--------------------------------------------------------------------------
43
+
44
+ def test_to_s
45
+ r1 = build_response(200, BODY)
46
+ r2 = build_response(400, BODY)
47
+ r3 = build_response(404, BODY)
48
+ r4 = build_response(500, BODY)
49
+ assert_equal(BODY, r1.to_s)
50
+ assert_equal("400 Bad Request", r2.to_s)
51
+ assert_equal("404 Not Found", r3.to_s)
52
+ assert_equal("500 Internal Server Error", r4.to_s)
53
+ end
54
+
55
+ #--------------------------------------------------------------------------
56
+
57
+ end # class TestResponse
58
+ end # module RackRabbit
59
+
@@ -0,0 +1,45 @@
1
+ require_relative '../test_case'
2
+
3
+ module RackRabbit
4
+ class TestSignals < TestCase
5
+
6
+ #--------------------------------------------------------------------------
7
+
8
+ def test_pop_is_fifo_queue
9
+ signals = Signals.new
10
+ signals.push(:TTIN)
11
+ signals.push(:TTOU)
12
+ signals.push(:QUIT)
13
+ assert_equal(:TTIN, signals.pop)
14
+ assert_equal(:TTOU, signals.pop)
15
+ assert_equal(:QUIT, signals.pop)
16
+ end
17
+
18
+ #--------------------------------------------------------------------------
19
+
20
+ def test_pop_blocks_when_queue_is_empty
21
+ signals = Signals.new
22
+ thread = Thread.new { sleep 0.1 ; signals.push :QUIT }
23
+ seconds = measure do
24
+ sig = signals.pop
25
+ assert_equal(:QUIT, sig)
26
+ end
27
+ assert_equal(true, seconds >= 0.1, 'verify we blocked for > 100ms')
28
+ thread.join
29
+ end
30
+
31
+ #--------------------------------------------------------------------------
32
+
33
+ def test_pop_blocking_can_be_timed_out
34
+ signals = Signals.new
35
+ seconds = measure do
36
+ sig = signals.pop(:timeout => 0.1)
37
+ assert_equal(:timeout, sig)
38
+ end
39
+ assert_equal(true, seconds >= 0.1, 'verify we blocked for > 100ms')
40
+ end
41
+
42
+ #--------------------------------------------------------------------------
43
+
44
+ end # class TestSignals
45
+ end # module RackRabbit
@@ -0,0 +1,140 @@
1
+ require_relative '../test_case'
2
+
3
+ module RackRabbit
4
+ class TestSubscriber < TestCase
5
+
6
+ #--------------------------------------------------------------------------
7
+
8
+ def test_subscribe_lifecycle
9
+
10
+ subscriber = build_subscriber
11
+ rabbit = subscriber.rabbit
12
+
13
+ assert_equal(false, rabbit.started?)
14
+ assert_equal(false, rabbit.connected?)
15
+
16
+ subscriber.subscribe
17
+ assert_equal(true, rabbit.started?)
18
+ assert_equal(true, rabbit.connected?)
19
+
20
+ subscriber.unsubscribe
21
+ assert_equal(false, rabbit.started?)
22
+ assert_equal(false, rabbit.connected?)
23
+
24
+ end
25
+
26
+ #--------------------------------------------------------------------------
27
+
28
+ def test_subscribe_options
29
+ options = { :queue => QUEUE, :exchange => EXCHANGE, :exchange_type => :fanout, :routing_key => ROUTE, :ack => true }
30
+ subscriber = build_subscriber(options)
31
+ rabbit = subscriber.rabbit
32
+ subscriber.subscribe
33
+ assert_equal(options, rabbit.subscribe_options, "subscription options should be set as expected")
34
+ end
35
+
36
+ #--------------------------------------------------------------------------
37
+
38
+ def test_subscribe_handles_message
39
+
40
+ subscriber = build_subscriber(:app_id => APP_ID)
41
+ message = build_message
42
+ rabbit = subscriber.rabbit
43
+
44
+ prime(subscriber, message)
45
+
46
+ assert_equal([], rabbit.subscribed_messages, "preconditions")
47
+
48
+ subscriber.subscribe
49
+
50
+ assert_equal([message], rabbit.subscribed_messages)
51
+ assert_equal([], rabbit.published_messages)
52
+ assert_equal([], rabbit.acked_messages)
53
+ assert_equal([], rabbit.rejected_messages)
54
+
55
+ end
56
+
57
+ #--------------------------------------------------------------------------
58
+
59
+ def test_handle_message_that_expects_a_reply
60
+
61
+ subscriber = build_subscriber(:app_id => APP_ID)
62
+ message = build_message(:delivery_tag => DELIVERY_TAG, :reply_to => REPLY_TO, :correlation_id => CORRELATION_ID)
63
+ rabbit = subscriber.rabbit
64
+
65
+ prime(subscriber, message)
66
+
67
+ subscriber.subscribe
68
+
69
+ assert_equal([message], rabbit.subscribed_messages)
70
+ assert_equal([], rabbit.acked_messages)
71
+ assert_equal([], rabbit.rejected_messages)
72
+ assert_equal(1, rabbit.published_messages.length)
73
+ assert_equal(APP_ID, rabbit.published_messages[0][:app_id])
74
+ assert_equal(REPLY_TO, rabbit.published_messages[0][:routing_key])
75
+ assert_equal(CORRELATION_ID, rabbit.published_messages[0][:correlation_id])
76
+ assert_equal(200, rabbit.published_messages[0][:headers][RackRabbit::HEADER::STATUS])
77
+ assert_equal("ok", rabbit.published_messages[0][:body])
78
+
79
+ end
80
+
81
+ #--------------------------------------------------------------------------
82
+
83
+ def test_successful_message_is_acked
84
+
85
+ rabbit = build_rabbit
86
+ subscriber = build_subscriber(:ack => true, :rabbit => rabbit)
87
+ message = build_message(:delivery_tag => DELIVERY_TAG, :rabbit => rabbit)
88
+
89
+ prime(subscriber, message)
90
+
91
+ subscriber.subscribe
92
+
93
+ assert_equal([message], rabbit.subscribed_messages)
94
+ assert_equal([], rabbit.published_messages)
95
+ assert_equal([DELIVERY_TAG], rabbit.acked_messages)
96
+ assert_equal([], rabbit.rejected_messages)
97
+
98
+ end
99
+
100
+ #--------------------------------------------------------------------------
101
+
102
+ def test_failed_message_is_rejected
103
+
104
+ rabbit = build_rabbit
105
+ subscriber = build_subscriber(:rack_file => ERROR_RACK_APP, :ack => true, :rabbit => rabbit)
106
+ message = build_message(:delivery_tag => DELIVERY_TAG, :rabbit => rabbit)
107
+ response = build_response(500, "uh oh")
108
+
109
+ prime(subscriber, [message, response])
110
+
111
+ subscriber.subscribe
112
+
113
+ assert_equal([message], rabbit.subscribed_messages)
114
+ assert_equal([], rabbit.published_messages)
115
+ assert_equal([], rabbit.acked_messages)
116
+ assert_equal([DELIVERY_TAG], rabbit.rejected_messages)
117
+
118
+ end
119
+
120
+ #==========================================================================
121
+ # PRIVATE IMPLEMTATION HELPERS
122
+ #==========================================================================
123
+
124
+ private
125
+
126
+ def prime(subscriber, *messages)
127
+ messages.each do |m|
128
+ m, r = m if m.is_a?(Array)
129
+ r ||= build_response(200, "ok")
130
+ subscriber.rabbit.prime(m)
131
+ subscriber.handler.expects(:handle).with(m).returns(r)
132
+ end
133
+ end
134
+
135
+ #--------------------------------------------------------------------------
136
+
137
+
138
+ end # class TestSubscriber
139
+ end # module RackRabbit
140
+