alchemy-flux 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe "performance of AlchemyFlux" do
4
+
5
+ after(:each) do
6
+ # This will stop EventMachine and disconnect RabbitMQ, reseting each test
7
+ AlchemyFlux::Service.stop
8
+ end
9
+
10
+ it 'should handle multiple messages at the same time' do
11
+ service_a = AlchemyFlux::Service.new("fluxa.service", {threadpool_size: 500}) do |message|
12
+ {'body' => "hola #{message['body']['name']}"}
13
+ end
14
+
15
+ service_b = AlchemyFlux::Service.new("fluxb.service")
16
+
17
+ service_a.start
18
+ service_b.start
19
+
20
+ calls = 400
21
+
22
+ responses = Queue.new
23
+ st = Time.now()
24
+ (1..calls).each do
25
+ service_b.send_message_to_service("fluxa.service", {'body' => {'name' => "Bob"}}) do |response|
26
+ responses << response
27
+ end
28
+ end
29
+
30
+ (1..calls).each do
31
+ resp = responses.pop
32
+ expect(resp['body']).to eq "hola Bob"
33
+ end
34
+ et = Time.now()
35
+ puts "Time for #{calls} async calls is #{(et-st)*1000}ms total; #{((et-st)*1000)/calls}ms per call"
36
+ service_a.stop
37
+ service_b.stop
38
+ end
39
+
40
+ it 'should handle multiple messages at the same time' do
41
+ service_a = AlchemyFlux::Service.new("fluxa.service", {threadpool_size: 500}) do |message|
42
+ {'body' => "hola #{message['body']['name']}"}
43
+ end
44
+
45
+ service_b = AlchemyFlux::Service.new("fluxb.service")
46
+
47
+ service_a.start
48
+ service_b.start
49
+
50
+ calls = 400
51
+
52
+ st = Time.now()
53
+ (1..calls).each do
54
+ resp = service_b.send_message_to_service("fluxa.service", {'body' => {'name' => "Bob"}})
55
+ expect(resp['body']).to eq "hola Bob"
56
+ end
57
+
58
+ et = Time.now()
59
+ puts "Time for #{calls} sync calls is #{(et-st)*1000}ms total; #{((et-st)*1000)/calls}ms per call"
60
+ service_a.stop
61
+ service_b.stop
62
+ end
63
+
64
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::Handler::AlchemyFlux do
4
+
5
+ after(:each) do
6
+ # This will stop EventMachine and disconnect RabbitMQ, reseting each test
7
+ AlchemyFlux::Service.stop
8
+ end
9
+
10
+ describe "#start" do
11
+
12
+ it 'should be able to start with a simple rack app' do
13
+ ENV['SERVICE_NAME'] = 'rack.service'
14
+ app = Proc.new do |env|
15
+ ['200', {}, ['hi Bob']]
16
+ end
17
+
18
+ service_a = AlchemyFlux::Service.new("fluxa.service")
19
+ Rack::Handler::AlchemyFlux.start app
20
+ service_a.start
21
+
22
+ response = service_a.send_message_to_service("rack.service", {})
23
+ expect(response['body']).to eq "hi Bob"
24
+ service_a.stop
25
+ Rack::Handler::AlchemyFlux.stop
26
+ end
27
+
28
+ it 'should register resources with RESOURCE_PATHS env variable' do
29
+ ENV['SERVICE_NAME'] = 'rack.service'
30
+ ENV['RESOURCE_PATHS'] = '/alice,/bob'
31
+ app = Proc.new do |env|
32
+ ['200', {}, ["hi #{env['PATH_INFO']}"]]
33
+ end
34
+
35
+ service_a = AlchemyFlux::Service.new("fluxa.service", :timeout => 100)
36
+ Rack::Handler::AlchemyFlux.start app
37
+
38
+ service_a.start
39
+
40
+ response = service_a.send_message_to_resource({'path' => '/alice'})
41
+ expect(response['body']).to eq "hi /alice"
42
+
43
+ response = service_a.send_message_to_resource({'path' => '/bob'})
44
+ expect(response['body']).to eq "hi /bob"
45
+
46
+ service_a.stop
47
+ Rack::Handler::AlchemyFlux.stop
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+
3
+ describe AlchemyFlux::Service do
4
+
5
+ after(:each) do
6
+ # This will stop EventMachine and disconnect RabbitMQ, reseting each test
7
+ AlchemyFlux::Service.stop
8
+ end
9
+
10
+ describe "#send_message_to_resource" do
11
+
12
+ it 'should be able to send messages to resource via path' do
13
+ resource_path = "/v1/fluxy_#{AlchemyFlux::Service.generateUUID()}"
14
+ service_a = AlchemyFlux::Service.new("fluxa.service", resource_paths: [resource_path]) do |message|
15
+ {'body' => "hi #{message['body']['name']}"}
16
+ end
17
+
18
+ service_b = AlchemyFlux::Service.new("fluxb.service")
19
+
20
+ service_a.start
21
+ service_b.start
22
+
23
+ response = service_b.send_message_to_resource({'path' => resource_path, 'body' => {'name' => "Bob"}})
24
+ expect(response['body']).to eq "hi Bob"
25
+
26
+ response = service_b.send_message_to_resource({'path' => "#{resource_path}/id", 'body' => {'name' => "Alice"}})
27
+ expect(response['body']).to eq "hi Alice"
28
+ service_a.stop
29
+ service_b.stop
30
+ end
31
+
32
+ it 'should be able to register multiple resources' do
33
+ resource_path1 = "/v1/fluxy_#{AlchemyFlux::Service.generateUUID()}"
34
+ resource_path2 = "/v1/fluxy_#{AlchemyFlux::Service.generateUUID()}"
35
+ service_a = AlchemyFlux::Service.new("fluxa.service", resource_paths: [resource_path1, resource_path2]) do |message|
36
+ {'body' => "hi #{message['body']['name']}"}
37
+ end
38
+
39
+ service_b = AlchemyFlux::Service.new("fluxb.service")
40
+
41
+ service_a.start
42
+ service_b.start
43
+
44
+ response = service_b.send_message_to_resource({'path' => resource_path1, 'body' => {'name' => "Bob"}})
45
+ expect(response['body']).to eq "hi Bob"
46
+
47
+ response = service_b.send_message_to_resource({'path' => resource_path2, 'body' => {'name' => "Alice"}})
48
+ expect(response['body']).to eq "hi Alice"
49
+ service_a.stop
50
+ service_b.stop
51
+ end
52
+
53
+
54
+ describe 'unhappy path' do
55
+ it 'should return error on a message to non existant service' do
56
+ service_b = AlchemyFlux::Service.new("fluxb.service")
57
+
58
+ service_b.start
59
+
60
+ expect(service_b.send_message_to_resource({'path' => '/v1/unregistered_resource'})).to eq AlchemyFlux::MessageNotDeliveredError
61
+ expect(service_b.transactions.length).to eq 0
62
+
63
+ service_b.stop
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,448 @@
1
+ require 'spec_helper'
2
+
3
+ describe AlchemyFlux::Service do
4
+ def thread_count
5
+ Thread.list.count
6
+ end
7
+
8
+ after(:each) do
9
+ # This will stop EventMachine and disconnect RabbitMQ, reseting each test
10
+ AlchemyFlux::Service.stop
11
+ end
12
+
13
+ describe '#initialize' do
14
+ it 'be initializable' do
15
+ AlchemyFlux::Service.new("test.service")
16
+ end
17
+ end
18
+
19
+ describe '#Service.start' do
20
+ it 'should create the EM thread' do
21
+ init_thread_count = thread_count
22
+ AlchemyFlux::Service.start
23
+ expect(thread_count).to eq init_thread_count + 1
24
+ AlchemyFlux::Service.stop
25
+ expect(thread_count).to eq init_thread_count
26
+ end
27
+
28
+ it 'should raise an error if amqp uri is broken' do
29
+ expect{AlchemyFlux::Service.start('bad_uri')}.to raise_error(ArgumentError)
30
+ expect{AlchemyFlux::Service.start('amqp://localhosty')}.to raise_error(EventMachine::ConnectionError)
31
+ end
32
+
33
+ it 'should start the Service connection on instance start' do
34
+ init_thread_count = thread_count
35
+ service = AlchemyFlux::Service.new("testflux.service")
36
+ service.start
37
+ expect(thread_count).to eq init_thread_count + 1
38
+ AlchemyFlux::Service.stop
39
+ expect(thread_count).to eq init_thread_count
40
+ end
41
+
42
+ it 'should raise an error if amqp uri is broken on instance start' do
43
+ service_bad_uri = AlchemyFlux::Service.new("testflux.service", ampq_uri: 'bad_uri')
44
+ service_bad_server = AlchemyFlux::Service.new("testflux.service", ampq_uri: 'amqp://localhosty')
45
+ expect{service_bad_uri.start}.to raise_error(ArgumentError)
46
+ expect{service_bad_server.start}.to raise_error(EventMachine::ConnectionError)
47
+ end
48
+ end
49
+
50
+ describe '#start' do
51
+
52
+ it 'should start a service and increase thread count by 1' do
53
+ init_thread_count = thread_count
54
+ service = AlchemyFlux::Service.new("testflux.service")
55
+ expect(service.state).to eq :stopped
56
+ service.start
57
+ expect(thread_count).to eq init_thread_count + 1
58
+ expect(service.state).to eq :started
59
+ #stop should not decrease because EM might still be running
60
+ service.stop
61
+ expect(service.state).to eq :stopped
62
+ expect(thread_count).to eq init_thread_count + 1
63
+ end
64
+
65
+ it 'start and stop multiple services with no extra threads' do
66
+ init_thread_count = thread_count
67
+
68
+ service_a = AlchemyFlux::Service.new("testfluxa.service")
69
+ service_b = AlchemyFlux::Service.new("testfluxb.service")
70
+
71
+ service_a.start
72
+ service_b.start
73
+
74
+ expect(thread_count).to eq init_thread_count + 1
75
+
76
+ service_a.stop
77
+ service_b.stop
78
+
79
+ expect(thread_count).to eq init_thread_count + 1
80
+ end
81
+
82
+ it 'should stop receiving messages after it has stopped' do
83
+ received_message = false
84
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
85
+ received_message = true
86
+ {}
87
+ end
88
+
89
+ service_b = AlchemyFlux::Service.new("fluxb.service", timeout: 100)
90
+
91
+ service_a.start
92
+ service_b.start
93
+
94
+ service_a.stop
95
+ expect(service_b.send_message_to_service("fluxa.service", {})).to eq AlchemyFlux::TimeoutError
96
+ service_b.stop
97
+ end
98
+
99
+ it 'should process incoming messages then stop' do
100
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
101
+ sleep(0.05)
102
+ {'body' => "hello"}
103
+ end
104
+
105
+ service_b = AlchemyFlux::Service.new("fluxb.service", timeout: 200)
106
+
107
+ service_a.start
108
+ service_b.start
109
+
110
+ response_queue = Queue.new
111
+ service_b.send_message_to_service("fluxa.service", {}) do |response|
112
+ response_queue << response
113
+ end
114
+ sleep(0.05)
115
+ service_a.stop
116
+
117
+ response = response_queue.pop
118
+
119
+ expect(response['body']).to eq "hello"
120
+ service_b.stop
121
+ end
122
+
123
+ it 'should process outgoing messages then stop' do
124
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
125
+ sleep(0.05)
126
+ {'body' => "hello"}
127
+ end
128
+
129
+ service_b = AlchemyFlux::Service.new("fluxb.service", timeout: 200)
130
+
131
+ service_a.start
132
+ service_b.start
133
+
134
+ response_queue = Queue.new
135
+ service_b.send_message_to_service("fluxa.service", {}) do |response|
136
+ response_queue << response
137
+ end
138
+ sleep(0.01)
139
+ service_b.stop
140
+
141
+ response = response_queue.pop
142
+
143
+ expect(response['body']).to eq "hello"
144
+ service_b.stop
145
+ end
146
+
147
+ it 'should stop receiving messages while stopping' do
148
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
149
+ sleep(0.8);
150
+ {'body' => "hello"}
151
+ end
152
+
153
+ service_b = AlchemyFlux::Service.new("fluxb.service")
154
+
155
+ service_a.start
156
+ service_b.start
157
+
158
+ response_queue = Queue.new
159
+ service_b.send_message_to_service("fluxa.service", {}) do |response|
160
+ response_queue << response
161
+ end
162
+ sleep(0.1)
163
+ Thread.new do service_a.stop end
164
+ sleep(0.3)
165
+ expect(service_b.send_message_to_service("fluxa.service", {})).to eq AlchemyFlux::TimeoutError
166
+
167
+ response = response_queue.pop
168
+
169
+ expect(response['body']).to eq "hello"
170
+ service_b.stop
171
+ end
172
+
173
+ it 'should have default service function' do
174
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
175
+ {'body' => "hi #{message['body']['name']}"}
176
+ end
177
+
178
+ service_b = AlchemyFlux::Service.new("fluxb.service")
179
+
180
+ service_a.start
181
+ service_b.start
182
+
183
+ response = service_b.send_message_to_service("fluxa.service", {'body' => {'name' => "Bob"}})
184
+ expect(response['body']).to eq "hi Bob"
185
+
186
+ response = service_a.send_message_to_service("fluxb.service", {'body' => {'name' => "Bob"}})
187
+ expect(response['body']).to be_empty
188
+
189
+
190
+ service_a.stop
191
+ service_b.stop
192
+ end
193
+ end
194
+
195
+
196
+ describe "#send_message_to_service" do
197
+
198
+ it 'should send and receive messages between services' do
199
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
200
+ {'body' => "hi #{message['body']['name']}"}
201
+ end
202
+
203
+ service_b = AlchemyFlux::Service.new("fluxb.service")
204
+
205
+ service_a.start
206
+ service_b.start
207
+
208
+ response = service_b.send_message_to_service("fluxa.service", {'body' => {'name' => "Bob"}})
209
+ expect(response['body']).to eq "hi Bob"
210
+ service_a.stop
211
+ service_b.stop
212
+ end
213
+
214
+
215
+ it 'can send and receive messages JSON messages' do
216
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
217
+ body = JSON.parse(message['body'])
218
+ {'body' => "hi #{body['name']}"}
219
+ end
220
+
221
+ service_b = AlchemyFlux::Service.new("fluxb.service")
222
+
223
+ service_a.start
224
+ service_b.start
225
+
226
+ response = service_b.send_message_to_service("fluxa.service", {'body' => '{"name" : "Bob"}'})
227
+ expect(response['body']).to eq "hi Bob"
228
+ service_a.stop
229
+ service_b.stop
230
+ end
231
+
232
+ it 'should be able to send messages within the service call' do
233
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
234
+ resp = service_a.send_message_to_service("fluxb.service", {})
235
+ {'body' => "hi #{resp['body']}"}
236
+ end
237
+
238
+ service_b = AlchemyFlux::Service.new("fluxb.service") do |message|
239
+ {'body' => 'Bob'}
240
+ end
241
+
242
+ service_a.start
243
+ service_b.start
244
+
245
+ response = service_b.send_message_to_service("fluxa.service", {})
246
+ expect(response['body']).to eq "hi Bob"
247
+ service_a.stop
248
+ service_b.stop
249
+ end
250
+
251
+ it 'should be able to send messages to itself' do
252
+ first = true
253
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
254
+ if first
255
+ first = !first
256
+ resp = service_a.send_message_to_service("fluxa.service", {})
257
+ {'body' => "hi #{resp['body']}"}
258
+ else
259
+ {'body' => 'Bob'}
260
+ end
261
+ end
262
+
263
+ service_b = AlchemyFlux::Service.new("fluxb.service")
264
+
265
+ service_a.start
266
+ service_b.start
267
+
268
+ response = service_b.send_message_to_service("fluxa.service", {})
269
+ expect(response['body']).to eq "hi Bob"
270
+ service_a.stop
271
+ service_b.stop
272
+ end
273
+
274
+ it 'should generate transaction id if none is given' do
275
+ interaction_id = nil
276
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
277
+ expect(message['headers']).to have_key 'x-interaction-id'
278
+ expect(message['headers']['x-interaction-id']).not_to be_nil
279
+ interaction_id = message['headers']['x-interaction-id']
280
+ { 'body' => "here" }
281
+ end
282
+
283
+ service_b = AlchemyFlux::Service.new("fluxb.service")
284
+
285
+ service_a.start
286
+ service_b.start
287
+
288
+ response = service_b.send_message_to_service("fluxa.service", {})
289
+ expect(response['body']).to eq "here"
290
+ expect(response['headers']).to have_key 'x-interaction-id'
291
+ expect(response['headers']['x-interaction-id']).not_to be_nil
292
+ expect(response['headers']['x-interaction-id']).to eq interaction_id
293
+ service_a.stop
294
+ service_b.stop
295
+ end
296
+
297
+ it 'should use given transaction id if one is provided' do
298
+ interaction_id = 'a123'
299
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
300
+ expect(message['headers']['x-interaction-id']).to eq interaction_id
301
+ { 'body' => "here" }
302
+ end
303
+
304
+ service_b = AlchemyFlux::Service.new("fluxb.service")
305
+
306
+ service_a.start
307
+ service_b.start
308
+
309
+ response = service_b.send_message_to_service("fluxa.service", {
310
+ 'headers' => {'x-interaction-id' => interaction_id}
311
+ })
312
+ expect(response['body']).to eq "here"
313
+ expect(response['headers']['x-interaction-id']).to eq interaction_id
314
+ service_a.stop
315
+ service_b.stop
316
+ end
317
+
318
+ it 'should add to transactions and processing messages, and remove once complete' do
319
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
320
+ sleep(0.1)
321
+ {'body' => 'here'}
322
+ end
323
+
324
+ service_b = AlchemyFlux::Service.new("fluxb.service", timeout: 200)
325
+
326
+ service_a.start
327
+ service_b.start
328
+
329
+ expect(service_b.transactions.length).to eq 0
330
+ expect(service_a.processing_messages).to eq 0
331
+
332
+ response = Queue.new
333
+ service_b.send_message_to_service("fluxa.service", {}) do |resp|
334
+ response << resp
335
+ end
336
+ sleep(0.05)
337
+ expect(service_b.transactions.length).to eq 1
338
+ expect(service_a.processing_messages).to eq 1
339
+
340
+ expect(response.pop['body']).to eq 'here'
341
+ expect(service_b.transactions.length).to eq 0
342
+ expect(service_a.processing_messages).to eq 0
343
+
344
+ service_a.stop
345
+ service_b.stop
346
+ end
347
+
348
+
349
+ it 'should send and receive messages between services asynchronously' do
350
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
351
+ {'body' => "hola #{message['body']['name']}"}
352
+ end
353
+
354
+ service_b = AlchemyFlux::Service.new("fluxb.service")
355
+
356
+ service_a.start
357
+ service_b.start
358
+
359
+ block = Queue.new
360
+ service_b.send_message_to_service("fluxa.service", {'body' => {'name' => "Bob"}}) do |response|
361
+ block << response
362
+ end
363
+
364
+ response = block.pop
365
+ expect(response['body']).to eq "hola Bob"
366
+
367
+ service_a.stop
368
+ service_b.stop
369
+ end
370
+
371
+ describe 'unhappy path' do
372
+ it 'should be able to be nacked by the service_fn' do
373
+ called = 0
374
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
375
+ called += 1
376
+ raise AlchemyFlux::NAckError if called == 1
377
+ { 'body' => "hola #{message['body']['name']}"}
378
+ end
379
+
380
+ service_b = AlchemyFlux::Service.new("fluxb.service")
381
+
382
+ service_a.start
383
+ service_b.start
384
+
385
+ block = Queue.new
386
+ service_b.send_message_to_service("fluxa.service", {'body' => {'name' => "Bob"}}) do |response|
387
+ block << response
388
+ end
389
+ response = block.pop
390
+ expect(response['body']).to eq "hola Bob"
391
+ expect(called).to eq 2
392
+ service_a.stop
393
+ service_b.stop
394
+ end
395
+
396
+ it 'should timeout if a message takes too long' do
397
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
398
+ sleep(0.1)
399
+ {}
400
+ end
401
+
402
+ service_b = AlchemyFlux::Service.new("fluxb.service", timeout: 100)
403
+
404
+ service_a.start
405
+ service_b.start
406
+
407
+ response = service_b.send_message_to_service("fluxa.service", {'body' => {'name' => "Bob"}})
408
+
409
+ expect(response).to eq AlchemyFlux::TimeoutError
410
+ expect(service_b.transactions.length).to eq 0
411
+
412
+ service_a.stop
413
+ service_b.stop
414
+ end
415
+
416
+ it 'should 500 if service_fn raises an error' do
417
+ service_a = AlchemyFlux::Service.new("fluxa.service") do |message|
418
+ raise Error.new
419
+ end
420
+
421
+ service_b = AlchemyFlux::Service.new("fluxb.service")
422
+
423
+ service_a.start
424
+ service_b.start
425
+
426
+ response = service_b.send_message_to_service("fluxa.service", {'body' => {'name' => "Bob"}})
427
+
428
+ expect(response['status_code']).to eq 500
429
+ expect(service_b.transactions.length).to eq 0
430
+
431
+ service_a.stop
432
+ service_b.stop
433
+ end
434
+
435
+ it 'should return error on a message to non existant service' do
436
+ service_b = AlchemyFlux::Service.new("fluxb.service")
437
+
438
+ service_b.start
439
+
440
+ expect(service_b.send_message_to_service("not_a_servoces.service", {})).to eq AlchemyFlux::MessageNotDeliveredError
441
+ expect(service_b.transactions.length).to eq 0
442
+
443
+ service_b.stop
444
+ end
445
+
446
+ end
447
+ end
448
+ end