adhearsion 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -3
  3. data/CHANGELOG.md +29 -0
  4. data/Gemfile +0 -2
  5. data/Guardfile +2 -2
  6. data/README.markdown +3 -6
  7. data/Rakefile +1 -1
  8. data/adhearsion.gemspec +7 -2
  9. data/features/cli_create.feature +85 -7
  10. data/features/plugin_generator.feature +4 -0
  11. data/features/step_definitions/app_generator_steps.rb +8 -1
  12. data/lib/adhearsion.rb +6 -3
  13. data/lib/adhearsion/call.rb +101 -30
  14. data/lib/adhearsion/call_controller.rb +40 -12
  15. data/lib/adhearsion/call_controller/dial.rb +119 -36
  16. data/lib/adhearsion/call_controller/input.rb +11 -5
  17. data/lib/adhearsion/call_controller/output.rb +47 -33
  18. data/lib/adhearsion/call_controller/output/async_player.rb +3 -2
  19. data/lib/adhearsion/call_controller/output/formatter.rb +7 -2
  20. data/lib/adhearsion/call_controller/output/player.rb +2 -2
  21. data/lib/adhearsion/call_controller/record.rb +16 -13
  22. data/lib/adhearsion/call_controller/utility.rb +3 -3
  23. data/lib/adhearsion/calls.rb +21 -8
  24. data/lib/adhearsion/cli_commands/ahn_command.rb +1 -0
  25. data/lib/adhearsion/configuration.rb +2 -2
  26. data/lib/adhearsion/console.rb +3 -2
  27. data/lib/adhearsion/generators.rb +7 -9
  28. data/lib/adhearsion/generators/app/app_generator.rb +12 -2
  29. data/lib/adhearsion/generators/app/templates/Gemfile.erb +7 -9
  30. data/lib/adhearsion/generators/app/templates/README.md +0 -19
  31. data/lib/adhearsion/generators/app/templates/adhearsion.erb +37 -0
  32. data/lib/adhearsion/generators/app/templates/config/environment.rb +6 -1
  33. data/lib/adhearsion/generators/app/templates/events.erb +18 -0
  34. data/lib/adhearsion/generators/app/templates/routes.erb +19 -0
  35. data/lib/adhearsion/generators/app/templates/{lib/simon_game.rb → simon_game.rb} +0 -0
  36. data/lib/adhearsion/generators/app/templates/{spec/call_controllers/simon_game_spec.rb → simon_game_spec.rb} +0 -0
  37. data/lib/adhearsion/generators/controller/controller_generator.rb +2 -2
  38. data/lib/adhearsion/generators/controller/templates/lib/{controller.rb → controller.rb.erb} +0 -0
  39. data/lib/adhearsion/generators/controller/templates/spec/{controller_spec.rb → controller_spec.rb.erb} +0 -0
  40. data/lib/adhearsion/generators/plugin/plugin_generator.rb +16 -15
  41. data/lib/adhearsion/generators/plugin/templates/gitignore +17 -0
  42. data/lib/adhearsion/generators/plugin/templates/spec/plugin-template/controller_methods_spec.rb.tt +1 -1
  43. data/lib/adhearsion/initializer.rb +14 -2
  44. data/lib/adhearsion/logging.rb +1 -0
  45. data/lib/adhearsion/outbound_call.rb +3 -7
  46. data/lib/adhearsion/punchblock_plugin/initializer.rb +3 -2
  47. data/lib/adhearsion/router/openended_route.rb +1 -1
  48. data/lib/adhearsion/router/route.rb +4 -3
  49. data/lib/adhearsion/version.rb +1 -1
  50. data/spec/adhearsion/call_controller/dial_spec.rb +811 -79
  51. data/spec/adhearsion/call_controller/output/formatter_spec.rb +13 -1
  52. data/spec/adhearsion/call_controller/output_spec.rb +35 -1
  53. data/spec/adhearsion/call_controller_spec.rb +174 -18
  54. data/spec/adhearsion/call_spec.rb +423 -39
  55. data/spec/adhearsion/calls_spec.rb +19 -3
  56. data/spec/adhearsion/outbound_call_spec.rb +88 -45
  57. data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +3 -3
  58. data/spec/adhearsion/router/route_spec.rb +2 -2
  59. data/spec/spec_helper.rb +2 -0
  60. metadata +92 -77
  61. data/features/app_generator.feature +0 -49
  62. data/lib/adhearsion/generators/app/templates/config/adhearsion.rb +0 -71
  63. data/lib/adhearsion/generators/plugin/templates/.gitignore +0 -9
@@ -24,15 +24,17 @@ module Adhearsion
24
24
  end
25
25
 
26
26
  describe "#ssml_for_collection" do
27
- let(:collection) { ["/foo/bar.wav", 1, Time.now] }
27
+ let(:collection) { ["/foo/bar.wav", 1, Time.now, '123*'] }
28
28
  let :ssml do
29
29
  file = collection[0]
30
30
  n = collection[1].to_s
31
31
  t = collection[2].to_s
32
+ c = collection[3].to_s
32
33
  RubySpeech::SSML.draw do
33
34
  audio :src => file
34
35
  say_as(:interpret_as => 'cardinal') { n }
35
36
  say_as(:interpret_as => 'time') { t }
37
+ say_as(:interpret_as => 'characters') { c }
36
38
  end
37
39
  end
38
40
 
@@ -78,6 +80,16 @@ module Adhearsion
78
80
  subject.detect_type(number).should be :numeric
79
81
  end
80
82
 
83
+ it "detects a String of digits" do
84
+ number = '123'
85
+ subject.detect_type(number).should be :numeric
86
+ end
87
+
88
+ it "detects a String of characters" do
89
+ number = '123#'
90
+ subject.detect_type(number).should be :characters
91
+ end
92
+
81
93
  ["Foo", "Foo bar", "The answer: foo", "The answer could be foo/bar"].each do |string|
82
94
  it "detects '#{string}' as text" do
83
95
  subject.detect_type(string).should be :text
@@ -338,6 +338,13 @@ module Adhearsion
338
338
  let(:extra_options) do
339
339
  { renderer: :native }
340
340
  end
341
+
342
+ describe "with a nil argument" do
343
+ it "is a noop" do
344
+ subject.play nil
345
+ end
346
+ end
347
+
341
348
  describe "with a single string" do
342
349
  let(:audio_file) { "/foo/bar.wav" }
343
350
  let :ssml do
@@ -357,15 +364,17 @@ module Adhearsion
357
364
  end
358
365
 
359
366
  describe "with multiple arguments" do
360
- let(:args) { ["/foo/bar.wav", 1, Time.now] }
367
+ let(:args) { ["/foo/bar.wav", 1, Time.now, "123#"] }
361
368
  let :ssml do
362
369
  file = args[0]
363
370
  n = args[1].to_s
364
371
  t = args[2].to_s
372
+ c = args[3].to_s
365
373
  RubySpeech::SSML.draw do
366
374
  audio :src => file
367
375
  say_as(:interpret_as => 'cardinal') { n }
368
376
  say_as(:interpret_as => 'time') { t }
377
+ say_as(:interpret_as => 'characters') { c }
369
378
  end
370
379
  end
371
380
 
@@ -398,6 +407,12 @@ module Adhearsion
398
407
  expect_ssml_output ssml
399
408
  subject.play(args).should be true
400
409
  end
410
+
411
+ context "that is empty" do
412
+ it "is a noop" do
413
+ subject.play []
414
+ end
415
+ end
401
416
  end
402
417
 
403
418
  describe "with a number" do
@@ -496,6 +511,13 @@ module Adhearsion
496
511
  let(:extra_options) do
497
512
  { renderer: :native }
498
513
  end
514
+
515
+ describe "with a nil argument" do
516
+ it "is a noop" do
517
+ subject.play! nil
518
+ end
519
+ end
520
+
499
521
  describe "with a single string" do
500
522
  let(:audio_file) { "/foo/bar.wav" }
501
523
  let :ssml do
@@ -731,6 +753,12 @@ module Adhearsion
731
753
  end
732
754
 
733
755
  describe "#say" do
756
+ describe "with a nil argument" do
757
+ it "is a no-op" do
758
+ subject.say nil
759
+ end
760
+ end
761
+
734
762
  describe "with a RubySpeech document" do
735
763
  it 'plays the correct SSML' do
736
764
  ssml = RubySpeech::SSML.draw { string "Hello world" }
@@ -829,6 +857,12 @@ module Adhearsion
829
857
  end
830
858
 
831
859
  describe "#say!" do
860
+ describe "with a nil argument" do
861
+ it "is a noop" do
862
+ subject.say! nil
863
+ end
864
+ end
865
+
832
866
  describe "with a RubySpeech document" do
833
867
  it 'plays the correct SSML' do
834
868
  ssml = RubySpeech::SSML.draw { string "Hello world" }
@@ -28,6 +28,13 @@ module Adhearsion
28
28
  its(:logger) { should be call.logger }
29
29
  its(:variables) { should be call.variables }
30
30
 
31
+ describe "#send_message" do
32
+ it 'should send a message' do
33
+ call.should_receive(:send_message).with("Hello World!").once
34
+ subject.send_message "Hello World!"
35
+ end
36
+ end
37
+
31
38
  context "when the call is dead" do
32
39
  before { call.terminate }
33
40
 
@@ -49,6 +56,17 @@ module Adhearsion
49
56
  subject.execute!
50
57
  end
51
58
 
59
+ context "when trying to execute a command against a dead call" do
60
+ before do
61
+ subject.should_receive(:run).once.ordered.and_raise(Call::ExpiredError)
62
+ end
63
+
64
+ it "gracefully terminates " do
65
+ subject.logger.should_receive(:info).once.with(/Call was hung up/).ordered
66
+ subject.execute!
67
+ end
68
+ end
69
+
52
70
  it "catches standard errors, triggering an exception event" do
53
71
  subject.should_receive(:run).once.ordered.and_raise(StandardError)
54
72
  latch = CountDownLatch.new 1
@@ -165,26 +183,28 @@ module Adhearsion
165
183
  end
166
184
 
167
185
  describe "#pass" do
168
- class PassController < CallController
169
- after_call :foobar
170
-
171
- def run
172
- before
173
- pass SecondController, :foo => 'bar'
174
- after
175
- end
186
+ let(:pass_controller) do
187
+ Class.new CallController do
188
+ after_call :foobar
189
+
190
+ def run
191
+ before
192
+ pass SecondController, :foo => 'bar'
193
+ after
194
+ end
176
195
 
177
- def before
178
- end
196
+ def before
197
+ end
179
198
 
180
- def after
181
- end
199
+ def after
200
+ end
182
201
 
183
- def foobar
202
+ def foobar
203
+ end
184
204
  end
185
205
  end
186
206
 
187
- subject { PassController.new call }
207
+ subject { pass_controller.new call }
188
208
 
189
209
  before do
190
210
  call.wrapped_object.stub :write_and_await_response => nil
@@ -211,6 +231,142 @@ module Adhearsion
211
231
  end
212
232
  end
213
233
 
234
+ describe "#hard_pass" do
235
+ let(:pass_controller) do
236
+ Class.new CallController do
237
+ def run
238
+ hard_pass SecondController, foo: 'bar'
239
+ end
240
+ end
241
+ end
242
+
243
+ subject { pass_controller.new call }
244
+
245
+ before do
246
+ call.wrapped_object.stub(:write_and_await_response).and_return do |command|
247
+ command.request!
248
+ command.execute!
249
+ end
250
+ call.stub register_controller: nil
251
+ SecondController.any_instance.should_receive(:md_check).once.with :foo => 'bar'
252
+ Events.should_receive(:trigger).with(:exception, Exception).never
253
+ end
254
+
255
+ it "should cease execution of the current controller, and instruct the call to execute another" do
256
+ call.should_receive(:answer).once.ordered
257
+
258
+ subject.exec
259
+ end
260
+
261
+ context "when components have been executed on the controller" do
262
+ let(:pass_controller) do
263
+ Class.new CallController do
264
+ attr_accessor :output1, :output2
265
+
266
+ def prep_output
267
+ @output1 = play! 'file://foo.wav'
268
+ @output2 = play! 'file://bar.wav'
269
+ end
270
+
271
+ def run
272
+ hard_pass SecondController, foo: 'bar'
273
+ end
274
+ end
275
+ end
276
+
277
+ before { subject.prep_output }
278
+
279
+ context "but not yet received a complete event" do
280
+ it "should terminate the components" do
281
+ subject.output1.should_receive(:stop!).once
282
+ subject.output2.should_receive(:stop!).once
283
+
284
+ subject.exec
285
+ end
286
+
287
+ context "and some fail to terminate" do
288
+ before { subject.output1.should_receive(:stop!).and_raise(Punchblock::Component::InvalidActionError) }
289
+
290
+ it "should terminate the others" do
291
+ subject.output2.should_receive(:stop!).once
292
+ subject.exec
293
+ end
294
+ end
295
+ end
296
+
297
+ context "when some have completed" do
298
+ before { subject.output1.trigger_event_handler Punchblock::Event::Complete.new }
299
+
300
+ it "should not terminate the completed components" do
301
+ subject.output1.should_receive(:stop!).never
302
+ subject.output2.should_receive(:stop!).once
303
+
304
+ subject.exec
305
+ end
306
+ end
307
+ end
308
+ end
309
+
310
+ describe '#stop_all_components' do
311
+ let(:stop_controller) do
312
+ Class.new CallController do
313
+ attr_accessor :output1, :output2
314
+
315
+ def prep_output
316
+ @output1 = play! 'file://foo.wav'
317
+ @output2 = play! 'file://bar.wav'
318
+ end
319
+
320
+ def run
321
+ stop_all_components
322
+ end
323
+ end
324
+ end
325
+
326
+ subject { stop_controller.new call }
327
+
328
+ context "when components have been executed on the controller" do
329
+ before do
330
+ call.wrapped_object.stub(:write_and_await_response).and_return do |command|
331
+ command.request!
332
+ command.execute!
333
+ end
334
+ call.stub register_controller: nil
335
+ Events.should_receive(:trigger).with(:exception, Exception).never
336
+ subject.prep_output
337
+ end
338
+
339
+ context "when they have not yet received a complete event" do
340
+ it "should terminate the components" do
341
+ subject.output1.should_receive(:stop!).once
342
+ subject.output2.should_receive(:stop!).once
343
+
344
+ subject.exec
345
+ end
346
+
347
+ context "and some fail to terminate" do
348
+ before { subject.output1.should_receive(:stop!).and_raise(Punchblock::Component::InvalidActionError) }
349
+
350
+ it "should terminate the others" do
351
+ subject.output2.should_receive(:stop!).once
352
+ subject.exec
353
+ end
354
+ end
355
+ end
356
+
357
+ context "when some have completed" do
358
+ before { subject.output1.trigger_event_handler Punchblock::Event::Complete.new }
359
+
360
+ it "should not terminate the completed components" do
361
+ subject.output1.should_receive(:stop!).never
362
+ subject.output2.should_receive(:stop!).once
363
+
364
+ subject.exec
365
+ end
366
+ end
367
+ end
368
+ end
369
+
214
370
  describe "#write_and_await_response" do
215
371
  let(:message) { Punchblock::Command::Accept.new }
216
372
 
@@ -249,7 +405,7 @@ module Adhearsion
249
405
  describe "#join" do
250
406
  it "delegates to the call, blocking first until it is allowed to execute, and unblocking when an unjoined event is received" do
251
407
  subject.should_receive(:block_until_resumed).once.ordered
252
- subject.call.should_receive(:join).once.with('call1', :foo => :bar).ordered.and_return Punchblock::Command::Join.new(:call_id => 'call1')
408
+ call.wrapped_object.should_receive(:write_and_await_response).once.ordered.with(Punchblock::Command::Join.new(call_uri: 'call1'))
253
409
  latch = CountDownLatch.new 1
254
410
  Thread.new do
255
411
  subject.join 'call1', :foo => :bar
@@ -265,7 +421,7 @@ module Adhearsion
265
421
  context "with a mixer" do
266
422
  it "delegates to the call, blocking first until it is allowed to execute, and unblocking when an unjoined event is received" do
267
423
  subject.should_receive(:block_until_resumed).once.ordered
268
- subject.call.should_receive(:join).once.with({:mixer_name => 'foobar', :foo => :bar}, {}).ordered.and_return Punchblock::Command::Join.new(:mixer_name => 'foobar')
424
+ call.wrapped_object.should_receive(:write_and_await_response).once.ordered.with(Punchblock::Command::Join.new(mixer_name: 'foobar'))
269
425
  latch = CountDownLatch.new 1
270
426
  Thread.new do
271
427
  subject.join :mixer_name => 'foobar', :foo => :bar
@@ -282,7 +438,7 @@ module Adhearsion
282
438
  context "with :async => true" do
283
439
  it "delegates to the call, blocking first until it is allowed to execute, and unblocking when the joined event is received" do
284
440
  subject.should_receive(:block_until_resumed).once.ordered
285
- subject.call.should_receive(:join).once.with('call1', :foo => :bar).ordered.and_return Punchblock::Command::Join.new(:call_id => 'call1')
441
+ call.wrapped_object.should_receive(:write_and_await_response).once.ordered.with(Punchblock::Command::Join.new(call_uri: 'call1'))
286
442
  latch = CountDownLatch.new 1
287
443
  Thread.new do
288
444
  subject.join 'call1', :foo => :bar, :async => true
@@ -296,7 +452,7 @@ module Adhearsion
296
452
  context "with a mixer" do
297
453
  it "delegates to the call, blocking first until it is allowed to execute, and unblocking when the joined event is received" do
298
454
  subject.should_receive(:block_until_resumed).once.ordered
299
- subject.call.should_receive(:join).once.with({:mixer_name => 'foobar', :foo => :bar}, {}).ordered.and_return Punchblock::Command::Join.new(:mixer_name => 'foobar')
455
+ call.wrapped_object.should_receive(:write_and_await_response).once.ordered.with(Punchblock::Command::Join.new(mixer_name: 'foobar'))
300
456
  latch = CountDownLatch.new 1
301
457
  Thread.new do
302
458
  subject.join :mixer_name => 'foobar', :foo => :bar, :async => true
@@ -37,6 +37,20 @@ module Adhearsion
37
37
  Adhearsion.active_calls.clear
38
38
  end
39
39
 
40
+ it "should do recursion detection on inspect" do
41
+ subject[:foo] = subject
42
+ Timeout.timeout(0.2) do
43
+ expect(subject.inspect).to match('...')
44
+ end
45
+ end
46
+
47
+ it "should allow timers to be registered from outside" do
48
+ foo = :bar
49
+ subject.after(1) { foo = :baz }
50
+ sleep 1.1
51
+ foo.should == :baz
52
+ end
53
+
40
54
  it { should respond_to :<< }
41
55
 
42
56
  its(:end_reason) { should be == nil }
@@ -50,6 +64,8 @@ module Adhearsion
50
64
  its(:to) { should be == to }
51
65
  its(:from) { should be == from }
52
66
 
67
+ its(:auto_hangup) { should be_true }
68
+
53
69
  context "when the ID is nil" do
54
70
  let(:call_id) { nil }
55
71
 
@@ -141,28 +157,52 @@ module Adhearsion
141
157
  end
142
158
  end
143
159
 
144
- it 'allows the registration of event handlers which are called when messages are delivered' do
145
- event = double 'Event'
146
- event.should_receive(:foo?).and_return true
147
- response = double 'Response'
148
- response.should_receive(:call).once
149
- subject.register_event_handler(:foo?) { response.call }
150
- subject << event
160
+ context 'registered event handlers' do
161
+ let(:event) { double 'Event' }
162
+ let(:response) { double 'Response' }
163
+
164
+ it 'are called when messages are delivered' do
165
+ event.should_receive(:foo?).and_return true
166
+ response.should_receive(:call).once
167
+ subject.register_event_handler(:foo?) { response.call }
168
+ subject << event
169
+ end
170
+
171
+ context 'when a handler raises' do
172
+ it 'does not cause the call actor to crash' do
173
+ subject.register_event_handler { raise 'Boom' }
174
+ subject << event
175
+ subject.should be_alive
176
+ end
177
+
178
+ it "triggers an exception event" do
179
+ e = StandardError.new('Boom')
180
+ Events.should_receive(:trigger).once.with(:exception, [e, subject.logger])
181
+ subject.register_event_handler { raise e }
182
+ subject << event
183
+ end
184
+
185
+ it 'executes all handlers for each event' do
186
+ response.should_receive(:call).once
187
+ subject.register_event_handler { raise 'Boom' }
188
+ subject.register_event_handler { response.call }
189
+ subject << event
190
+ end
191
+ end
151
192
  end
152
193
 
153
194
  describe "event handlers" do
154
- before { pending }
155
195
  let(:response) { double 'Response' }
156
196
 
157
197
  describe "for joined events" do
158
198
  context "joined to another call" do
159
199
  let :event do
160
- Punchblock::Event::Joined.new call_uri: 'xmpp:foobar@rayo.net'
200
+ Punchblock::Event::Joined.new call_uri: 'footransport:foobar@rayo.net'
161
201
  end
162
202
 
163
203
  it "should trigger any on_joined callbacks set for the matching call ID" do
164
204
  response.should_receive(:call).once.with(event)
165
- subject.on_joined(:call_uri => 'xmpp:foobar@rayo.net') { |event| response.call event }
205
+ subject.on_joined(:call_uri => 'footransport:foobar@rayo.net') { |event| response.call event }
166
206
  subject << event
167
207
  end
168
208
 
@@ -175,14 +215,14 @@ module Adhearsion
175
215
  it "should trigger any on_joined callbacks set for the matching call" do
176
216
  response.should_receive(:call).once.with(event)
177
217
  call = Call.new
178
- call.wrapped_object.stub id: 'foobar', domain: 'rayo.net'
218
+ call.wrapped_object.stub id: 'foobar', domain: 'rayo.net', transport: 'footransport'
179
219
  subject.on_joined(call) { |event| response.call event }
180
220
  subject << event
181
221
  end
182
222
 
183
223
  it "should not trigger on_joined callbacks for other call IDs" do
184
224
  response.should_receive(:call).never
185
- subject.on_joined(:call_id => 'barfoo') { |event| response.call event }
225
+ subject.on_joined(:call_uri => 'barfoo') { |event| response.call event }
186
226
  subject << event
187
227
  end
188
228
 
@@ -212,7 +252,7 @@ module Adhearsion
212
252
 
213
253
  it "should not trigger any on_joined callbacks set for calls" do
214
254
  response.should_receive(:call).never
215
- subject.on_joined(:call_id => 'foobar') { |event| response.call event }
255
+ subject.on_joined(:call_uri => 'foobar') { |event| response.call event }
216
256
  subject << event
217
257
  end
218
258
 
@@ -225,7 +265,7 @@ module Adhearsion
225
265
  it "should not trigger any on_joined callbacks set for the matching call" do
226
266
  response.should_receive(:call).never
227
267
  call = Call.new
228
- call.stub :id => 'foobar'
268
+ call.wrapped_object.stub :id => 'foobar'
229
269
  subject.on_joined(call) { |event| response.call event }
230
270
  subject << event
231
271
  end
@@ -235,12 +275,12 @@ module Adhearsion
235
275
  describe "for unjoined events" do
236
276
  context "unjoined from another call" do
237
277
  let :event do
238
- Punchblock::Event::Unjoined.new call_uri: 'xmpp:foobar@rayo.net'
278
+ Punchblock::Event::Unjoined.new call_uri: 'footransport:foobar@rayo.net'
239
279
  end
240
280
 
241
281
  it "should trigger any on_unjoined callbacks set for the matching call ID" do
242
282
  response.should_receive(:call).once.with(event)
243
- subject.on_unjoined(:call_uri => 'xmpp:foobar@rayo.net') { |event| response.call event }
283
+ subject.on_unjoined(:call_uri => 'footransport:foobar@rayo.net') { |event| response.call event }
244
284
  subject << event
245
285
  end
246
286
 
@@ -253,14 +293,14 @@ module Adhearsion
253
293
  it "should trigger any on_unjoined callbacks set for the matching call" do
254
294
  response.should_receive(:call).once.with(event)
255
295
  call = Call.new
256
- call.wrapped_object.stub id: 'foobar', domain: 'rayo.net'
296
+ call.wrapped_object.stub id: 'foobar', domain: 'rayo.net', transport: 'footransport'
257
297
  subject.on_unjoined(call) { |event| response.call event }
258
298
  subject << event
259
299
  end
260
300
 
261
301
  it "should not trigger on_unjoined callbacks for other call IDs" do
262
302
  response.should_receive(:call).never
263
- subject.on_unjoined(:call_id => 'barfoo') { |event| response.call event }
303
+ subject.on_unjoined(:call_uri => 'barfoo') { |event| response.call event }
264
304
  subject << event
265
305
  end
266
306
 
@@ -290,7 +330,7 @@ module Adhearsion
290
330
 
291
331
  it "should not trigger any on_unjoined callbacks set for calls" do
292
332
  response.should_receive(:call).never
293
- subject.on_unjoined(:call_id => 'foobar') { |event| response.call event }
333
+ subject.on_unjoined(:call_uri => 'foobar') { |event| response.call event }
294
334
  subject << event
295
335
  end
296
336
 
@@ -303,7 +343,7 @@ module Adhearsion
303
343
  it "should not trigger any on_unjoined callbacks set for the matching call" do
304
344
  response.should_receive(:call).never
305
345
  call = Call.new
306
- call.stub :id => 'foobar'
346
+ call.wrapped_object.stub :id => 'foobar'
307
347
  subject.on_unjoined(call) { |event| response.call event }
308
348
  subject << event
309
349
  end
@@ -346,54 +386,121 @@ module Adhearsion
346
386
  end
347
387
 
348
388
  context "peer registry" do
349
- let(:other_call_id) { 'foobar' }
389
+ let(:other_call_uri) { 'xmpp:foobar@example.com' }
350
390
  let(:other_call) { Call.new }
351
391
 
352
- before { other_call.stub :id => other_call_id }
392
+ before { other_call.stub uri: other_call_uri }
353
393
 
354
394
  let :joined_event do
355
- Punchblock::Event::Joined.new call_uri: other_call_id
395
+ Punchblock::Event::Joined.new call_uri: other_call_uri
356
396
  end
357
397
 
358
398
  let :unjoined_event do
359
- Punchblock::Event::Unjoined.new call_uri: other_call_id
399
+ Punchblock::Event::Unjoined.new call_uri: other_call_uri
360
400
  end
361
401
 
362
402
  context "when we know about the joined call" do
363
403
  before { Adhearsion.active_calls << other_call }
404
+ after { Adhearsion.active_calls.remove_inactive_call other_call }
364
405
 
365
406
  it "should add the peer to its registry" do
366
407
  subject << joined_event
367
- subject.peers.should == {'foobar' => other_call}
408
+ subject.peers.should == {'xmpp:foobar@example.com' => other_call}
409
+ end
410
+
411
+ context "in a handler for the joined event" do
412
+ it "should have already populated the registry" do
413
+ peer = nil
414
+
415
+ subject.on_joined do |event|
416
+ peer = subject.peers.keys.first
417
+ end
418
+
419
+ subject << joined_event
420
+
421
+ peer.should == other_call_uri
422
+ end
423
+ end
424
+
425
+ context "when being unjoined from a previously joined call" do
426
+ before { subject << joined_event }
427
+
428
+ it "should remove the peer from its registry" do
429
+ subject.peers.should_not eql({})
430
+ subject << unjoined_event
431
+ subject.peers.should eql({})
432
+ end
433
+
434
+ context "in a handler for the unjoined event" do
435
+ it "should have already been removed the registry" do
436
+ peer_count = nil
437
+
438
+ subject.on_unjoined do |event|
439
+ peer_count = subject.peers.size
440
+ end
441
+
442
+ subject << unjoined_event
443
+
444
+ peer_count.should == 0
445
+ end
446
+ end
368
447
  end
369
448
  end
370
449
 
371
450
  context "when we don't know about the joined call" do
372
451
  it "should add a nil entry to its registry" do
373
452
  subject << joined_event
374
- subject.peers.should == {'foobar' => nil}
453
+ subject.peers.should == {'xmpp:foobar@example.com' => nil}
375
454
  end
376
- end
377
455
 
378
- it "should not return the same registry every call" do
379
- subject.peers.should_not be subject.peers
380
- end
456
+ context "in a handler for the joined event" do
457
+ it "should have already populated the registry" do
458
+ peer = nil
381
459
 
382
- context "when being unjoined from a previously joined call" do
383
- before { subject << joined_event }
460
+ subject.on_joined do |event|
461
+ peer = subject.peers.keys.first
462
+ end
463
+
464
+ subject << joined_event
384
465
 
385
- it "should remove the peer from its registry" do
386
- subject.peers.should_not eql({})
387
- subject << unjoined_event
388
- subject.peers.should eql({})
466
+ peer.should == other_call_uri
467
+ end
389
468
  end
469
+
470
+ context "when being unjoined from a previously joined call" do
471
+ before { subject << joined_event }
472
+
473
+ it "should remove the peer from its registry" do
474
+ subject.peers.should_not eql({})
475
+ subject << unjoined_event
476
+ subject.peers.should eql({})
477
+ end
478
+
479
+ context "in a handler for the unjoined event" do
480
+ it "should have already been removed the registry" do
481
+ peer_count = nil
482
+
483
+ subject.on_unjoined do |event|
484
+ peer_count = subject.peers.size
485
+ end
486
+
487
+ subject << unjoined_event
488
+
489
+ peer_count.should == 0
490
+ end
491
+ end
492
+ end
493
+ end
494
+
495
+ it "should not return the same registry every call" do
496
+ subject.peers.should_not be subject.peers
390
497
  end
391
498
  end
392
499
 
393
500
  describe "#<<" do
394
501
  describe "with a Punchblock End" do
395
502
  let :end_event do
396
- Punchblock::Event::End.new :reason => :hangup
503
+ Punchblock::Event::End.new :reason => :hangup, :platform_code => 'arbitrary_code'
397
504
  end
398
505
 
399
506
  it "should mark the call as ended" do
@@ -406,6 +513,11 @@ module Adhearsion
406
513
  subject.end_reason.should be == :hangup
407
514
  end
408
515
 
516
+ it "should set the end code" do
517
+ subject << end_event
518
+ subject.end_code.should be == 'arbitrary_code'
519
+ end
520
+
409
521
  it "should set the end time" do
410
522
  finish_time = Time.local(2008, 9, 1, 12, 1, 3)
411
523
  Timecop.freeze finish_time
@@ -654,6 +766,20 @@ module Adhearsion
654
766
  end
655
767
  end
656
768
 
769
+ describe "#send_message" do
770
+ it "should send a message through the Punchblock connection using the call ID and domain" do
771
+ subject.wrapped_object.should_receive(:client).once.and_return mock_client
772
+ mock_client.should_receive(:send_message).once.with(subject.id, subject.domain, "Hello World!", {})
773
+ subject.send_message "Hello World!"
774
+ end
775
+
776
+ it "should send a message with the given subject" do
777
+ subject.wrapped_object.should_receive(:client).once.and_return mock_client
778
+ mock_client.should_receive(:send_message).once.with(subject.id, subject.domain, nil, :subject => "Important Message")
779
+ subject.send_message nil, :subject => "Important Message"
780
+ end
781
+ end
782
+
657
783
  describe "basic control commands" do
658
784
  def expect_message_waiting_for_response(message = nil, fail = false, &block)
659
785
  expectation = subject.wrapped_object.should_receive(:write_and_await_response, &block).once
@@ -810,26 +936,156 @@ module Adhearsion
810
936
  subject.join target, :media => :bridge, :direction => :recv
811
937
  end
812
938
  end
939
+
940
+ it "should return the command" do
941
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
942
+ result = subject.join target, :media => :bridge, :direction => :recv
943
+ result[:command].should be_a Punchblock::Command::Join
944
+ result[:command].call_uri.should eql(uri)
945
+ result[:command].media.should eql(:bridge)
946
+ result[:command].direction.should eql(:recv)
947
+ end
948
+
949
+ it "should return something that can be blocked on until the join is complete" do
950
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
951
+ result = subject.join target, :media => :bridge, :direction => :recv
952
+
953
+ result[:joined_condition].wait(0.5).should be_false
954
+
955
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
956
+ result[:joined_condition].wait(0.5).should be_true
957
+ end
958
+
959
+ it "should return something that can be blocked on until the entities are unjoined" do
960
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
961
+ result = subject.join target, :media => :bridge, :direction => :recv
962
+
963
+ result[:unjoined_condition].wait(0.5).should be_false
964
+
965
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
966
+ result[:unjoined_condition].wait(0.5).should be_false
967
+
968
+ subject << Punchblock::Event::Unjoined.new(call_uri: uri)
969
+ result[:unjoined_condition].wait(0.5).should be_true
970
+ end
971
+
972
+ it "should unblock all conditions on call end if no joined/unjoined events are received" do
973
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
974
+ result = subject.join target, :media => :bridge, :direction => :recv
975
+
976
+ result[:joined_condition].wait(0.5).should be_false
977
+ result[:unjoined_condition].wait(0.5).should be_false
978
+
979
+ subject << Punchblock::Event::End.new
980
+ result[:joined_condition].wait(0.5).should be_true
981
+ result[:unjoined_condition].wait(0.5).should be_true
982
+ end
983
+
984
+ it "should not error on call end when joined/unjoined events are received correctly" do
985
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
986
+ result = subject.join target, :media => :bridge, :direction => :recv
987
+
988
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
989
+ subject << Punchblock::Event::Unjoined.new(call_uri: uri)
990
+
991
+ subject << Punchblock::Event::End.new
992
+ end
993
+
994
+ it "should not error if multiple joined events are received for the same join" do
995
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
996
+ result = subject.join target, :media => :bridge, :direction => :recv
997
+
998
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
999
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
1000
+
1001
+ subject.should be_alive
1002
+ end
813
1003
  end
814
1004
 
815
1005
  context "with a call ID" do
816
1006
  let(:target) { rand.to_s }
1007
+ let(:uri) { "footransport:#{target}@#{subject.domain}" }
817
1008
 
818
1009
  it "should send a join command joining to the provided call ID" do
819
- expect_join_with_options call_uri: "footransport:#{target}@#{subject.domain}"
1010
+ expect_join_with_options call_uri: uri
820
1011
  subject.join target
821
1012
  end
822
1013
 
823
1014
  context "and direction/media options" do
824
1015
  it "should send a join command with the correct options" do
825
- expect_join_with_options :call_uri => "footransport:#{target}@#{subject.domain}", :media => :bridge, :direction => :recv
1016
+ expect_join_with_options :call_uri => uri, :media => :bridge, :direction => :recv
826
1017
  subject.join target, :media => :bridge, :direction => :recv
827
1018
  end
828
1019
  end
1020
+
1021
+ it "should return the command" do
1022
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
1023
+ result = subject.join target, :media => :bridge, :direction => :recv
1024
+ result[:command].should be_a Punchblock::Command::Join
1025
+ result[:command].call_uri.should eql(uri)
1026
+ result[:command].media.should eql(:bridge)
1027
+ result[:command].direction.should eql(:recv)
1028
+ end
1029
+
1030
+ it "should return something that can be blocked on until the join is complete" do
1031
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
1032
+ result = subject.join target, :media => :bridge, :direction => :recv
1033
+
1034
+ result[:joined_condition].wait(0.5).should be_false
1035
+
1036
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
1037
+ result[:joined_condition].wait(0.5).should be_true
1038
+ end
1039
+
1040
+ it "should return something that can be blocked on until the entities are unjoined" do
1041
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
1042
+ result = subject.join target, :media => :bridge, :direction => :recv
1043
+
1044
+ result[:unjoined_condition].wait(0.5).should be_false
1045
+
1046
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
1047
+ result[:unjoined_condition].wait(0.5).should be_false
1048
+
1049
+ subject << Punchblock::Event::Unjoined.new(call_uri: uri)
1050
+ result[:unjoined_condition].wait(0.5).should be_true
1051
+ end
1052
+
1053
+ it "should unblock all conditions on call end if no joined/unjoined events are received" do
1054
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
1055
+ result = subject.join target, :media => :bridge, :direction => :recv
1056
+
1057
+ result[:joined_condition].wait(0.5).should be_false
1058
+ result[:unjoined_condition].wait(0.5).should be_false
1059
+
1060
+ subject << Punchblock::Event::End.new
1061
+ result[:joined_condition].wait(0.5).should be_true
1062
+ result[:unjoined_condition].wait(0.5).should be_true
1063
+ end
1064
+
1065
+ it "should not error on call end when joined/unjoined events are received correctly" do
1066
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
1067
+ result = subject.join target, :media => :bridge, :direction => :recv
1068
+
1069
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
1070
+ subject << Punchblock::Event::Unjoined.new(call_uri: uri)
1071
+
1072
+ subject << Punchblock::Event::End.new
1073
+ end
1074
+
1075
+ it "should not error if multiple joined events are received for the same join" do
1076
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
1077
+ result = subject.join target, :media => :bridge, :direction => :recv
1078
+
1079
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
1080
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
1081
+
1082
+ subject.should be_alive
1083
+ end
829
1084
  end
830
1085
 
831
1086
  context "with a call URI as a hash key" do
832
1087
  let(:call_id) { rand.to_s }
1088
+ let(:uri) { call_id }
833
1089
  let(:target) { { :call_uri => call_id } }
834
1090
 
835
1091
  it "should send a join command joining to the provided call ID" do
@@ -843,6 +1099,70 @@ module Adhearsion
843
1099
  subject.join target.merge({:media => :bridge, :direction => :recv})
844
1100
  end
845
1101
  end
1102
+
1103
+ it "should return the command" do
1104
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
1105
+ result = subject.join target.merge({:media => :bridge, :direction => :recv})
1106
+ result[:command].should be_a Punchblock::Command::Join
1107
+ result[:command].call_uri.should eql(uri)
1108
+ result[:command].media.should eql(:bridge)
1109
+ result[:command].direction.should eql(:recv)
1110
+ end
1111
+
1112
+ it "should return something that can be blocked on until the join is complete" do
1113
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
1114
+ result = subject.join target.merge({:media => :bridge, :direction => :recv})
1115
+
1116
+ result[:joined_condition].wait(0.5).should be_false
1117
+
1118
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
1119
+ result[:joined_condition].wait(0.5).should be_true
1120
+ end
1121
+
1122
+ it "should return something that can be blocked on until the entities are unjoined" do
1123
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
1124
+ result = subject.join target.merge({:media => :bridge, :direction => :recv})
1125
+
1126
+ result[:unjoined_condition].wait(0.5).should be_false
1127
+
1128
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
1129
+ result[:unjoined_condition].wait(0.5).should be_false
1130
+
1131
+ subject << Punchblock::Event::Unjoined.new(call_uri: uri)
1132
+ result[:unjoined_condition].wait(0.5).should be_true
1133
+ end
1134
+
1135
+ it "should unblock all conditions on call end if no joined/unjoined events are received" do
1136
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
1137
+ result = subject.join target.merge({:media => :bridge, :direction => :recv})
1138
+
1139
+ result[:joined_condition].wait(0.5).should be_false
1140
+ result[:unjoined_condition].wait(0.5).should be_false
1141
+
1142
+ subject << Punchblock::Event::End.new
1143
+ result[:joined_condition].wait(0.5).should be_true
1144
+ result[:unjoined_condition].wait(0.5).should be_true
1145
+ end
1146
+
1147
+ it "should not error on call end when joined/unjoined events are received correctly" do
1148
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
1149
+ result = subject.join target.merge({:media => :bridge, :direction => :recv})
1150
+
1151
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
1152
+ subject << Punchblock::Event::Unjoined.new(call_uri: uri)
1153
+
1154
+ subject << Punchblock::Event::End.new
1155
+ end
1156
+
1157
+ it "should not error if multiple joined events are received for the same join" do
1158
+ expect_join_with_options :call_id => uri, :media => :bridge, :direction => :recv
1159
+ result = subject.join target.merge({:media => :bridge, :direction => :recv})
1160
+
1161
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
1162
+ subject << Punchblock::Event::Joined.new(call_uri: uri)
1163
+
1164
+ subject.should be_alive
1165
+ end
846
1166
  end
847
1167
 
848
1168
  context "with a mixer name as a hash key" do
@@ -860,6 +1180,70 @@ module Adhearsion
860
1180
  subject.join target.merge({:media => :bridge, :direction => :recv})
861
1181
  end
862
1182
  end
1183
+
1184
+ it "should return the command" do
1185
+ expect_join_with_options :mixer_name => mixer_name, :media => :bridge, :direction => :recv
1186
+ result = subject.join target.merge({:media => :bridge, :direction => :recv})
1187
+ result[:command].should be_a Punchblock::Command::Join
1188
+ result[:command].mixer_name.should eql(mixer_name)
1189
+ result[:command].media.should eql(:bridge)
1190
+ result[:command].direction.should eql(:recv)
1191
+ end
1192
+
1193
+ it "should return something that can be blocked on until the join is complete" do
1194
+ expect_join_with_options :mixer_name => mixer_name, :media => :bridge, :direction => :recv
1195
+ result = subject.join target.merge({:media => :bridge, :direction => :recv})
1196
+
1197
+ result[:joined_condition].wait(0.5).should be_false
1198
+
1199
+ subject << Punchblock::Event::Joined.new(mixer_name: mixer_name)
1200
+ result[:joined_condition].wait(0.5).should be_true
1201
+ end
1202
+
1203
+ it "should return something that can be blocked on until the entities are unjoined" do
1204
+ expect_join_with_options :mixer_name => mixer_name, :media => :bridge, :direction => :recv
1205
+ result = subject.join target.merge({:media => :bridge, :direction => :recv})
1206
+
1207
+ result[:unjoined_condition].wait(0.5).should be_false
1208
+
1209
+ subject << Punchblock::Event::Joined.new(mixer_name: mixer_name)
1210
+ result[:unjoined_condition].wait(0.5).should be_false
1211
+
1212
+ subject << Punchblock::Event::Unjoined.new(mixer_name: mixer_name)
1213
+ result[:unjoined_condition].wait(0.5).should be_true
1214
+ end
1215
+
1216
+ it "should unblock all conditions on call end if no joined/unjoined events are received" do
1217
+ expect_join_with_options :mixer_name => mixer_name, :media => :bridge, :direction => :recv
1218
+ result = subject.join target.merge({:media => :bridge, :direction => :recv})
1219
+
1220
+ result[:joined_condition].wait(0.5).should be_false
1221
+ result[:unjoined_condition].wait(0.5).should be_false
1222
+
1223
+ subject << Punchblock::Event::End.new
1224
+ result[:joined_condition].wait(0.5).should be_true
1225
+ result[:unjoined_condition].wait(0.5).should be_true
1226
+ end
1227
+
1228
+ it "should not error on call end when joined/unjoined events are received correctly" do
1229
+ expect_join_with_options :mixer_name => mixer_name, :media => :bridge, :direction => :recv
1230
+ result = subject.join target.merge({:media => :bridge, :direction => :recv})
1231
+
1232
+ subject << Punchblock::Event::Joined.new(mixer_name: mixer_name)
1233
+ subject << Punchblock::Event::Unjoined.new(mixer_name: mixer_name)
1234
+
1235
+ subject << Punchblock::Event::End.new
1236
+ end
1237
+
1238
+ it "should not error if multiple joined events are received for the same join" do
1239
+ expect_join_with_options :mixer_name => mixer_name, :media => :bridge, :direction => :recv
1240
+ result = subject.join target.merge({:media => :bridge, :direction => :recv})
1241
+
1242
+ subject << Punchblock::Event::Joined.new(mixer_name: mixer_name)
1243
+ subject << Punchblock::Event::Joined.new(mixer_name: mixer_name)
1244
+
1245
+ subject.should be_alive
1246
+ end
863
1247
  end
864
1248
 
865
1249
  context "with a call ID and a mixer name as hash keys" do