adhearsion 2.0.0.alpha1 → 2.0.0.alpha2

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 (57) hide show
  1. data/.gitignore +2 -0
  2. data/.travis.yml +12 -0
  3. data/CHANGELOG.md +17 -0
  4. data/adhearsion.gemspec +4 -3
  5. data/features/app_generator.feature +3 -1
  6. data/features/cli.feature +7 -7
  7. data/features/support/env.rb +46 -0
  8. data/lib/adhearsion.rb +1 -2
  9. data/lib/adhearsion/call.rb +59 -19
  10. data/lib/adhearsion/call_controller.rb +20 -24
  11. data/lib/adhearsion/call_controller/dial.rb +18 -18
  12. data/lib/adhearsion/cli_commands.rb +26 -9
  13. data/lib/adhearsion/configuration.rb +39 -10
  14. data/lib/adhearsion/console.rb +61 -42
  15. data/lib/adhearsion/foundation/libc.rb +13 -0
  16. data/lib/adhearsion/generators/app/app_generator.rb +4 -1
  17. data/lib/adhearsion/generators/app/templates/{Gemfile → Gemfile.erb} +1 -1
  18. data/lib/adhearsion/generators/app/templates/Rakefile +3 -22
  19. data/lib/adhearsion/generators/app/templates/gitignore +7 -0
  20. data/lib/adhearsion/generators/app/templates/lib/simon_game.rb +1 -0
  21. data/lib/adhearsion/generators/app/templates/script/ahn +1 -0
  22. data/lib/adhearsion/initializer.rb +24 -12
  23. data/lib/adhearsion/linux_proc_name.rb +41 -0
  24. data/lib/adhearsion/outbound_call.rb +10 -5
  25. data/lib/adhearsion/plugin.rb +29 -132
  26. data/lib/adhearsion/process.rb +4 -1
  27. data/lib/adhearsion/punchblock_plugin.rb +14 -5
  28. data/lib/adhearsion/punchblock_plugin/initializer.rb +8 -1
  29. data/lib/adhearsion/router/route.rb +1 -3
  30. data/lib/adhearsion/tasks.rb +6 -12
  31. data/lib/adhearsion/tasks/configuration.rb +7 -24
  32. data/lib/adhearsion/tasks/environment.rb +12 -0
  33. data/lib/adhearsion/tasks/plugins.rb +9 -14
  34. data/lib/adhearsion/version.rb +1 -1
  35. data/spec/adhearsion/call_controller/dial_spec.rb +46 -22
  36. data/spec/adhearsion/call_controller_spec.rb +48 -13
  37. data/spec/adhearsion/call_spec.rb +144 -23
  38. data/spec/adhearsion/calls_spec.rb +8 -4
  39. data/spec/adhearsion/console_spec.rb +24 -0
  40. data/spec/adhearsion/initializer/logging_spec.rb +0 -3
  41. data/spec/adhearsion/initializer_spec.rb +52 -37
  42. data/spec/adhearsion/logging_spec.rb +0 -3
  43. data/spec/adhearsion/outbound_call_spec.rb +12 -2
  44. data/spec/adhearsion/plugin_spec.rb +74 -184
  45. data/spec/adhearsion/process_spec.rb +59 -26
  46. data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +3 -4
  47. data/spec/adhearsion/punchblock_plugin_spec.rb +11 -0
  48. data/spec/adhearsion/router/route_spec.rb +37 -6
  49. data/spec/adhearsion_spec.rb +31 -8
  50. data/spec/spec_helper.rb +14 -0
  51. data/spec/support/call_controller_test_helpers.rb +2 -2
  52. data/spec/support/logging_helpers.rb +2 -0
  53. metadata +85 -68
  54. data/lib/adhearsion/dialplan_controller.rb +0 -9
  55. data/lib/adhearsion/foundation/synchronized_hash.rb +0 -93
  56. data/lib/adhearsion/plugin/methods_container.rb +0 -6
  57. data/spec/adhearsion/dialplan_controller_spec.rb +0 -26
@@ -1,26 +1,9 @@
1
- require 'adhearsion/punchblock_plugin'
2
-
3
- begin
4
- Adhearsion.config # load default config vlaues
5
- require "#{Dir.pwd}/config/adhearsion.rb"
6
- rescue Exception => ex
7
- STDERR.puts "\nError while loading application configuration file: #{ex}"
8
- end
9
-
10
- namespace :adhearsion do
11
-
12
- namespace :config do
13
-
14
- desc "Show configuration values in STDOUT; it accepts a parameter: [nil|platform|<plugin-name>|all]"
15
- task :show, :name do |t, args|
16
- name = args.name.nil? ? :all : args.name.to_sym
17
- puts Adhearsion.config.description name, :show_values => true
18
- end
19
-
20
- desc "Show configuration description in STDOUT; it accepts a parameter: [nil|platform|<plugin-name>|all]"
21
- task :desc, :name do |t, args|
22
- name = args.name.nil? ? :all : args.name.to_sym
23
- puts Adhearsion.config.description name, :show_values => false
24
- end
1
+ namespace :config do
2
+ desc "Show configuration values; accepts a parameter: [nil|platform|<plugin-name>|all]"
3
+ task :show, :name, :needs => :environment do |t, args|
4
+ name = args.name.nil? ? :all : args.name.to_sym
5
+ puts "\nAdhearsion.config do |config|\n\n"
6
+ puts Adhearsion.config.description name, :show_values => true
7
+ puts "end"
25
8
  end
26
9
  end
@@ -0,0 +1,12 @@
1
+ task :environment do
2
+ require 'adhearsion/punchblock_plugin'
3
+
4
+ begin
5
+ Adhearsion.config # load default config vlaues
6
+ initializer = Adhearsion::Initializer.new
7
+ initializer.load_lib_folder
8
+ initializer.load_config
9
+ rescue Exception => ex
10
+ STDERR.puts "\nError while loading application configuration file: #{ex}"
11
+ end
12
+ end
@@ -1,17 +1,12 @@
1
- require 'adhearsion/punchblock_plugin'
2
-
3
- namespace :adhearsion do
4
-
5
- desc "List the configured plugins"
6
- task :plugins do |t,args|
7
- if Adhearsion::Plugin.subclasses.length > 0
8
- puts "You have #{Adhearsion::Plugin.subclasses.length} plugin(s) in your Adhearsion application:\n"
9
- Adhearsion::Plugin.subclasses.each do |plugin|
10
- puts "* #{plugin.plugin_name}: #{plugin.name}"
11
- end
12
- else
13
- puts "There is no Adhearsion plugin used in your application"
1
+ desc "List the configured plugins"
2
+ task :plugins => :environment do |t,args|
3
+ if Adhearsion::Plugin.subclasses.length > 0
4
+ puts "You have #{Adhearsion::Plugin.subclasses.length} plugin(s) in your Adhearsion application:\n"
5
+ Adhearsion::Plugin.subclasses.each do |plugin|
6
+ puts "* #{plugin.plugin_name}: #{plugin.name}"
14
7
  end
15
- puts "\n"
8
+ else
9
+ puts "There is no Adhearsion plugin used in your application"
16
10
  end
11
+ puts "\n"
17
12
  end
@@ -1,5 +1,5 @@
1
1
  module Adhearsion #:nodoc:
2
- VERSION = '2.0.0.alpha1'
2
+ VERSION = '2.0.0.alpha2'
3
3
 
4
4
  class PkgVersion
5
5
  include Comparable
@@ -6,17 +6,17 @@ module Adhearsion
6
6
  include CallControllerTestHelpers
7
7
 
8
8
  let(:to) { 'sip:foo@bar.com' }
9
-
10
- let(:other_call_id) { rand }
9
+ let(:other_call_id) { rand }
11
10
  let(:other_mock_call) { flexmock OutboundCall.new, :id => other_call_id }
12
11
 
13
- let(:mock_end) { flexmock Punchblock::Event::End.new, :reason => :hangup }
12
+ let(:second_to) { 'sip:baz@bar.com' }
13
+ let(:second_other_call_id) { rand }
14
+ let(:second_other_mock_call) { flexmock OutboundCall.new, :id => second_other_call_id }
15
+
16
+ let(:mock_end) { flexmock Punchblock::Event::End.new, :reason => :hangup }
14
17
  let(:mock_answered) { Punchblock::Event::Answered.new }
15
18
 
16
- #added for multiple dial testing
17
- let(:second_to) { 'sip:baz@bar.com' }
18
- let(:second_other_call_id) { rand }
19
- let(:second_other_mock_call) { flexmock OutboundCall.new, :id => second_other_call_id }
19
+ let(:latch) { CountDownLatch.new 1 }
20
20
 
21
21
  def mock_dial
22
22
  flexmock(OutboundCall).new_instances.should_receive(:dial).and_return true
@@ -25,10 +25,11 @@ module Adhearsion
25
25
  describe "#dial" do
26
26
  it "should create a new call and return it" do
27
27
  mock_dial
28
- Thread.new do
29
- subject.dial(to).should be_a OutboundCall
28
+ t = Thread.new do
29
+ subject.dial(to, {}, latch).should be_a OutboundCall
30
30
  end
31
- other_mock_call << mock_end
31
+ latch.countdown!
32
+ t.join
32
33
  end
33
34
 
34
35
  it "should dial the call to the correct endpoint" do
@@ -44,7 +45,7 @@ module Adhearsion
44
45
  end
45
46
 
46
47
  describe "without a block" do
47
- it "blocks the original dialplan until the new call ends" do
48
+ it "blocks the original controller until the new call ends" do
48
49
  other_mock_call
49
50
 
50
51
  flexmock(other_mock_call).should_receive(:dial).once
@@ -68,7 +69,7 @@ module Adhearsion
68
69
  other_mock_call
69
70
 
70
71
  flexmock(other_mock_call).should_receive(:dial).once
71
- flexmock(other_mock_call).should_receive(:join).once.with(call_id)
72
+ flexmock(other_mock_call).should_receive(:join).once.with(call)
72
73
  flexmock(OutboundCall).should_receive(:new).and_return other_mock_call
73
74
 
74
75
  latch = CountDownLatch.new 1
@@ -93,19 +94,19 @@ module Adhearsion
93
94
  second_other_mock_call
94
95
 
95
96
  flexmock(other_mock_call).should_receive(:dial).once
96
- flexmock(other_mock_call).should_receive(:join).once.with(call_id)
97
+ flexmock(other_mock_call).should_receive(:join).once.with(call)
97
98
  flexmock(other_mock_call).should_receive(:hangup!).never
98
99
 
99
100
  flexmock(second_other_mock_call).should_receive(:dial).once
100
101
  flexmock(second_other_mock_call).should_receive(:hangup!).once
101
102
 
102
-
103
103
  flexmock(OutboundCall).should_receive(:new).and_return other_mock_call, second_other_mock_call
104
104
  latch = CountDownLatch.new 1
105
105
 
106
- Thread.new do
107
- subject.dial [to, second_to]
106
+ t = Thread.new do
107
+ calls = subject.dial [to, second_to]
108
108
  latch.countdown!
109
+ calls
109
110
  end
110
111
 
111
112
  latch.wait(1).should be_false
@@ -113,20 +114,43 @@ module Adhearsion
113
114
  other_mock_call << mock_answered
114
115
  other_mock_call << mock_end
115
116
 
116
- latch.wait(1).should be_true
117
+ latch.wait(2).should be_true
118
+
119
+ t.join
120
+ calls = t.value
121
+ calls.should have(2).calls
122
+ calls.each { |c| c.should be_a OutboundCall }
117
123
  end
118
124
  end
119
125
 
120
126
  describe "with a timeout specified" do
121
- it "should abort the dial after the specified timeout"
122
- end
127
+ let(:timeout) { 3 }
123
128
 
124
- describe "with a from specified" do
125
- it "originates the call from the specified caller ID"
129
+ it "should abort the dial after the specified timeout" do
130
+ other_mock_call
131
+
132
+ flexmock(other_mock_call).should_receive(:dial).once
133
+ flexmock(OutboundCall).should_receive(:new).and_return other_mock_call
134
+
135
+ latch = CountDownLatch.new 1
136
+
137
+ value = nil
138
+ time = Time.now
139
+
140
+ Thread.new do
141
+ value = subject.dial to, :timeout => timeout
142
+ latch.countdown!
143
+ end
144
+
145
+ latch.wait
146
+ time = Time.now - time
147
+ time.to_i.should == timeout
148
+ value.should == false
149
+ end
126
150
  end
127
151
 
128
152
  describe "with a block" do
129
- it "uses the block as the dialplan for the new call"
153
+ it "uses the block as the controller for the new call"
130
154
 
131
155
  it "joins the new call to the existing call once the block returns"
132
156
 
@@ -1,5 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
+ class FinancialWizard < Adhearsion::CallController
4
+ end
5
+
3
6
  module Adhearsion
4
7
  describe CallController do
5
8
  include CallControllerTestHelpers
@@ -20,10 +23,6 @@ module Adhearsion
20
23
  end
21
24
  end
22
25
 
23
- it "should add plugin dialplan methods" do
24
- subject.should respond_to :foo
25
- end
26
-
27
26
  its(:logger) { should be call.logger }
28
27
  its(:variables) { should be call.variables }
29
28
 
@@ -78,6 +77,20 @@ module Adhearsion
78
77
  flexmock(Events).should_receive(:trigger).once.with(:exception, StandardError).ordered
79
78
  subject.execute!
80
79
  end
80
+
81
+ context "when a block is specified" do
82
+ let :block do
83
+ Proc.new { foo value }
84
+ end
85
+
86
+ its(:block) { should be block }
87
+
88
+ it "should execute the block in the context of the controller" do
89
+ flexmock subject, :value => :bar
90
+ subject.should_receive(:foo).once.with(:bar)
91
+ subject.run
92
+ end
93
+ end
81
94
  end
82
95
 
83
96
  class SecondController < CallController
@@ -285,45 +298,52 @@ module Adhearsion
285
298
 
286
299
  describe '#accept' do
287
300
  it "should delegate to the call" do
288
- flexmock(subject.call).should_receive(:accept).once.with(:foo)
301
+ flexmock(call).should_receive(:accept).once.with(:foo)
289
302
  subject.accept :foo
290
303
  end
291
304
  end
292
305
 
293
306
  describe '#answer' do
294
307
  it "should delegate to the call" do
295
- flexmock(subject.call).should_receive(:answer).once.with(:foo)
308
+ flexmock(call).should_receive(:answer).once.with(:foo)
296
309
  subject.answer :foo
297
310
  end
298
311
  end
299
312
 
300
313
  describe '#reject' do
301
314
  it "should delegate to the call" do
302
- flexmock(subject.call).should_receive(:reject).once.with(:foo, :bar)
315
+ flexmock(call).should_receive(:reject).once.with(:foo, :bar)
303
316
  subject.reject :foo, :bar
304
317
  end
305
318
  end
306
319
 
307
320
  describe '#hangup' do
308
321
  it "should delegate to the call" do
309
- flexmock(subject.call).should_receive(:hangup!).once.with(:foo)
322
+ flexmock(call).should_receive(:hangup!).once.with(:foo)
310
323
  subject.hangup :foo
311
324
  end
312
325
  end
313
326
 
314
327
  describe '#mute' do
315
- it 'should send a Mute message' do
316
- expect_message_waiting_for_response Punchblock::Command::Mute.new
328
+ it 'should delegate to the call' do
329
+ flexmock(call).should_receive(:mute).once
317
330
  subject.mute
318
331
  end
319
332
  end
320
333
 
321
334
  describe '#unmute' do
322
- it 'should send an Unmute message' do
323
- expect_message_waiting_for_response Punchblock::Command::Unmute.new
335
+ it 'should delegate to the call' do
336
+ flexmock(call).should_receive(:unmute).once
324
337
  subject.unmute
325
338
  end
326
339
  end
340
+
341
+ describe '#join' do
342
+ it 'should delegate to the call' do
343
+ flexmock(call).should_receive(:join).once.with(:foo)
344
+ subject.join :foo
345
+ end
346
+ end
327
347
  end
328
348
  end
329
349
 
@@ -383,7 +403,7 @@ describe ExampleCallController do
383
403
  subject.execute!
384
404
  end
385
405
 
386
- context "when the controller finishes without a hangup" do
406
+ describe "when the controller finishes without a hangup" do
387
407
  it "should execute the after_call callbacks" do
388
408
  subject[:skip_hangup] = true
389
409
  subject.should_receive(:join_to_conference).once.ordered
@@ -392,4 +412,19 @@ describe ExampleCallController do
392
412
  subject.execute!
393
413
  end
394
414
  end
415
+
416
+ describe "providing hooks to include call functionality" do
417
+ let(:call) { Adhearsion::Call.new mock_offer(nil, :x_foo => 'bar') }
418
+
419
+ it "should allow mixing in a module globally on all CallController classes" do
420
+ Adhearsion::CallController.mixin TestBiscuit
421
+ Adhearsion::CallController.new(call).should respond_to :throwadogabone
422
+ end
423
+
424
+ it "should allow mixing in a module on a single CallController class" do
425
+ FinancialWizard.mixin MarmaladeIsBetterThanJam
426
+ FinancialWizard.new(call).should respond_to :sobittersweet
427
+ Adhearsion::CallController.new(call).should_not respond_to :sobittersweet
428
+ end
429
+ end
395
430
  end
@@ -2,10 +2,25 @@ require 'spec_helper'
2
2
 
3
3
  module Adhearsion
4
4
  describe Call do
5
+ let(:mock_client) { flexmock('Client').tap &:should_ignore_missing }
6
+
7
+ let(:call_id) { rand }
5
8
  let(:headers) { nil }
6
- let(:offer) { mock_offer nil, headers }
9
+ let(:to) { 'sip:you@there.com' }
10
+ let(:from) { 'sip:me@here.com' }
11
+ let :offer do
12
+ Punchblock::Event::Offer.new :call_id => call_id,
13
+ :to => to,
14
+ :from => from,
15
+ :headers => headers
16
+ end
17
+
7
18
  subject { Adhearsion::Call.new offer }
8
19
 
20
+ before do
21
+ flexmock(offer).should_receive(:client).and_return(mock_client)
22
+ end
23
+
9
24
  after do
10
25
  Adhearsion.active_calls.clear!
11
26
  end
@@ -18,10 +33,43 @@ module Adhearsion
18
33
  its(:commands) { should be_a Call::CommandRegistry }
19
34
  its(:commands) { should be_empty }
20
35
 
36
+ its(:id) { should == call_id }
37
+ its(:to) { should == to }
38
+ its(:from) { should == from }
39
+ its(:offer) { should be offer }
40
+ its(:client) { should be mock_client }
41
+
21
42
  describe "its variables" do
22
43
  context "with an offer with headers" do
23
44
  let(:headers) { {:x_foo => 'bar'} }
24
45
  its(:variables) { should == headers }
46
+
47
+ it "should be made available via []" do
48
+ subject[:x_foo].should == 'bar'
49
+ end
50
+
51
+ it "should be alterable using []=" do
52
+ subject[:x_foo] = 'baz'
53
+ subject[:x_foo].should == 'baz'
54
+ end
55
+
56
+ context "when receiving an event with headers" do
57
+ let(:event) { Punchblock::Event::End.new :headers => {:x_bar => 'foo'} }
58
+
59
+ it "should merge later headers" do
60
+ subject << event
61
+ subject.variables.should == {:x_foo => 'bar', :x_bar => 'foo'}
62
+ end
63
+ end
64
+
65
+ context "when sending a command with headers" do
66
+ let(:command) { Punchblock::Command::Accept.new :headers => {:x_bar => 'foo'} }
67
+
68
+ it "should merge later headers" do
69
+ subject.write_command command
70
+ subject.variables.should == {:x_foo => 'bar', :x_bar => 'foo'}
71
+ end
72
+ end
25
73
  end
26
74
 
27
75
  context "with an offer without headers" do
@@ -35,33 +83,16 @@ module Adhearsion
35
83
  end
36
84
  end
37
85
 
38
- it '#id should return the ID from the Offer' do
39
- offer = mock_offer
40
- Adhearsion::Call.new(offer).id.should == offer.call_id
41
- end
42
-
43
- it 'should store the original offer' do
44
- offer = mock_offer
45
- Adhearsion::Call.new(offer).offer.should == offer
46
- end
47
-
48
86
  describe 'without an offer' do
49
87
  it 'should not raise an exception' do
50
88
  lambda { Adhearsion::Call.new }.should_not raise_error
51
89
  end
52
90
  end
53
91
 
54
- it "should store the Punchblock client from the Offer" do
55
- offer = mock_offer
56
- client = flexmock('Client')
57
- offer.should_receive(:client).once.and_return(client)
58
- Adhearsion::Call.new(offer).client.should == client
59
- end
60
-
61
92
  it 'a hungup call removes itself from the active calls' do
62
93
  size_before = Adhearsion.active_calls.size
63
94
 
64
- call = Adhearsion.active_calls.from_offer mock_offer
95
+ call = Adhearsion.active_calls.from_offer offer
65
96
  Adhearsion.active_calls.size.should > size_before
66
97
  call.hangup
67
98
  Adhearsion.active_calls.size.should == size_before
@@ -350,11 +381,101 @@ module Adhearsion
350
381
  end
351
382
 
352
383
  describe "#join" do
353
- let(:other_call_id) { rand }
384
+ def expect_join_with_options(options = {})
385
+ Punchblock::Command::Join.new(options).tap do |join|
386
+ expect_message_waiting_for_response join
387
+ end
388
+ end
389
+
390
+ context "with a call" do
391
+ let(:call_id) { rand.to_s }
392
+ let(:target) { flexmock Call.new, :id => call_id }
393
+
394
+ it "should send a join command joining to the provided call ID" do
395
+ expect_join_with_options :other_call_id => call_id
396
+ subject.join target
397
+ end
398
+
399
+ context "and direction/media options" do
400
+ it "should send a join command with the correct options" do
401
+ expect_join_with_options :other_call_id => call_id, :media => :bridge, :direction => :recv
402
+ subject.join target, :media => :bridge, :direction => :recv
403
+ end
404
+ end
405
+ end
406
+
407
+ context "with a call ID" do
408
+ let(:target) { rand.to_s }
409
+
410
+ it "should send a join command joining to the provided call ID" do
411
+ expect_join_with_options :other_call_id => target
412
+ subject.join target
413
+ end
414
+
415
+ context "and direction/media options" do
416
+ it "should send a join command with the correct options" do
417
+ expect_join_with_options :other_call_id => target, :media => :bridge, :direction => :recv
418
+ subject.join target, :media => :bridge, :direction => :recv
419
+ end
420
+ end
421
+ end
422
+
423
+ context "with a call ID as a hash key" do
424
+ let(:call_id) { rand.to_s }
425
+ let(:target) { { :call_id => call_id } }
426
+
427
+ it "should send a join command joining to the provided call ID" do
428
+ expect_join_with_options :other_call_id => call_id
429
+ subject.join target
430
+ end
431
+
432
+ context "and direction/media options" do
433
+ it "should send a join command with the correct options" do
434
+ expect_join_with_options :other_call_id => call_id, :media => :bridge, :direction => :recv
435
+ subject.join target.merge({:media => :bridge, :direction => :recv})
436
+ end
437
+ end
438
+ end
439
+
440
+ context "with a mixer name as a hash key" do
441
+ let(:mixer_name) { rand.to_s }
442
+ let(:target) { { :mixer_name => mixer_name } }
443
+
444
+ it "should send a join command joining to the provided call ID" do
445
+ expect_join_with_options :mixer_name => mixer_name
446
+ subject.join target
447
+ end
448
+
449
+ context "and direction/media options" do
450
+ it "should send a join command with the correct options" do
451
+ expect_join_with_options :mixer_name => mixer_name, :media => :bridge, :direction => :recv
452
+ subject.join target.merge({:media => :bridge, :direction => :recv})
453
+ end
454
+ end
455
+ end
456
+
457
+ context "with a call ID and a mixer name as hash keys" do
458
+ let(:call_id) { rand.to_s }
459
+ let(:mixer_name) { rand.to_s }
460
+ let(:target) { { :call_id => call_id, :mixer_name => mixer_name } }
461
+
462
+ it "should raise an ArgumentError" do
463
+ lambda { subject.join target }.should raise_error ArgumentError, /call ID and mixer name/
464
+ end
465
+ end
466
+ end
467
+
468
+ describe "#mute" do
469
+ it 'should send a Mute message' do
470
+ expect_message_waiting_for_response Punchblock::Command::Mute.new
471
+ subject.mute
472
+ end
473
+ end
354
474
 
355
- it "should mark the call inactive" do
356
- expect_message_waiting_for_response Punchblock::Command::Join.new :other_call_id => other_call_id
357
- subject.join other_call_id
475
+ describe "#unmute" do
476
+ it 'should send a Mute message' do
477
+ expect_message_waiting_for_response Punchblock::Command::Unmute.new
478
+ subject.unmute
358
479
  end
359
480
  end
360
481