adhearsion 2.4.0 → 2.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 (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