punchblock 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # develop
2
2
 
3
+ # v0.8.0 - 2012-01-06
4
+ * Feature: Expose Blather's connection timeout config when creating a Connection::XMPP
5
+ * Bugfix: Remove some deprecated Tropo extension components
6
+ * Bugfix: Remove reconnection logic since it really belongs in the consumer
7
+ * Feature: Raise a DisconnectedError when a disconnection is detected
8
+
3
9
  # v0.7.2 - 2011-12-28
4
10
  * Feature: Allow sending commands to mixers easily
5
11
  * Feature: Allow configuration of Rayo XMPP domains (root, call and mixer)
data/lib/punchblock.rb CHANGED
@@ -16,6 +16,7 @@ module Punchblock
16
16
  autoload :CommandNode
17
17
  autoload :Component
18
18
  autoload :Connection
19
+ autoload :DisconnectedError
19
20
  autoload :HasHeaders
20
21
  autoload :Header
21
22
  autoload :MediaContainer
@@ -53,7 +54,7 @@ module Punchblock
53
54
  RAYO_NAMESPACES[:"#{ns}_complete"] = [BASE_RAYO_NAMESPACE, ns.to_s, 'complete', RAYO_VERSION].compact.join(':')
54
55
  end
55
56
 
56
- [:ask, :conference, :say, :transfer].each do |ns|
57
+ [:conference].each do |ns|
57
58
  RAYO_NAMESPACES[ns] = [BASE_TROPO_NAMESPACE, ns.to_s, RAYO_VERSION].compact.join(':')
58
59
  RAYO_NAMESPACES[:"#{ns}_complete"] = [BASE_TROPO_NAMESPACE, ns.to_s, 'complete', RAYO_VERSION].compact.join(':')
59
60
  end
@@ -3,10 +3,7 @@ module Punchblock
3
3
  module Tropo
4
4
  extend ActiveSupport::Autoload
5
5
 
6
- autoload :Ask
7
6
  autoload :Conference
8
- autoload :Say
9
- autoload :Transfer
10
7
  end
11
8
  end # Command
12
9
  end # Punchblock
@@ -18,14 +18,14 @@ module Punchblock
18
18
  # @option options [String] :username client JID
19
19
  # @option options [String] :password XMPP password
20
20
  # @option options [String] :rayo_domain the domain on which Rayo is running
21
- # @option options [Boolean, Optional] :auto_reconnect whether or not to auto reconnect
22
21
  # @option options [Numeric, Optional] :write_timeout for which to wait on a command response
22
+ # @option options [Numeric, Optional] :connection_timeout for which to wait on a connection being established
23
23
  # @option options [Numeric, nil, Optional] :ping_period interval in seconds on which to ping the server. Nil or false to disable
24
24
  #
25
25
  def initialize(options = {})
26
26
  raise ArgumentError unless (@username = options[:username]) && options[:password]
27
27
 
28
- setup *[:username, :password, :host, :port, :certs].map { |key| options.delete key }
28
+ setup *[:username, :password, :host, :port, :certs, :connection_timeout].map { |key| options.delete key }
29
29
 
30
30
  @root_domain = Blather::JID.new(options[:root_domain] || options[:rayo_domain] || @username).domain
31
31
  @calls_domain = options[:calls_domain] || "calls.#{@root_domain}"
@@ -33,9 +33,6 @@ module Punchblock
33
33
 
34
34
  @callmap = {} # This hash maps call IDs to their XMPP domain.
35
35
 
36
- @auto_reconnect = !!options[:auto_reconnect]
37
- @reconnect_attempts = 0
38
-
39
36
  @ping_period = options.has_key?(:ping_period) ? options[:ping_period] : 60
40
37
 
41
38
  Blather.logger = pb_logger
@@ -59,7 +56,7 @@ module Punchblock
59
56
  def prep_command_for_execution(command, options = {})
60
57
  command.connection = self
61
58
  command.call_id ||= options[:call_id]
62
- command.mixer_name ||= options[:mixer_name]
59
+ command.mixer_name ||= options[:mixer_name]
63
60
  command.component_id ||= options[:component_id]
64
61
  create_iq(jid_for_command(command)).tap do |iq|
65
62
  pb_logger.debug "Sending IQ ID #{iq.id} #{command.inspect} to #{jid}"
@@ -78,13 +75,17 @@ module Punchblock
78
75
  def connect
79
76
  begin
80
77
  EM.run { client.run }
78
+ rescue Blather::Stream::ConnectionFailed, Blather::Stream::ConnectionTimeout => e
79
+ raise DisconnectedError.new(e.class.to_s, e.message)
81
80
  rescue => e
81
+ # Preserve Punchblock native exceptions
82
+ raise e if e.class.to_s =~ /^Punchblock/
83
+ # Wrap everything else
82
84
  raise ProtocolError.new(e.class.to_s, e.message)
83
85
  end
84
86
  end
85
87
 
86
88
  def stop
87
- @reconnect_attempts = nil
88
89
  client.close
89
90
  end
90
91
 
@@ -155,20 +156,12 @@ module Punchblock
155
156
  when_ready do
156
157
  event_handler.call Connected.new
157
158
  pb_logger.info "Connected to XMPP as #{@username}"
158
- @reconnect_attempts = 0
159
159
  @rayo_ping = EM::PeriodicTimer.new(@ping_period) { ping_rayo } if @ping_period
160
160
  end
161
161
 
162
162
  disconnected do
163
163
  @rayo_ping.cancel if @rayo_ping
164
- if @auto_reconnect && @reconnect_attempts
165
- timer = 30 * 2 ** @reconnect_attempts
166
- pb_logger.warn "XMPP disconnected. Tried to reconnect #{@reconnect_attempts} times. Reconnecting in #{timer}s."
167
- sleep timer
168
- pb_logger.info "Trying to reconnect..."
169
- @reconnect_attempts += 1
170
- connect
171
- end
164
+ raise DisconnectedError
172
165
  end
173
166
 
174
167
  # Read/handle presence requests. This is how we get events.
@@ -0,0 +1,16 @@
1
+ module Punchblock
2
+ ##
3
+ # This exception may be raised if the connection to the server is interrupted.
4
+ class DisconnectedError < StandardError
5
+ attr_accessor :cause, :message
6
+
7
+ def initialize(cause = nil, message = nil)
8
+ @cause, @message = cause, message
9
+ end
10
+
11
+ def to_s
12
+ "#<#{self.class}: cause=#{cause.inspect} message=#{message.inspect}"
13
+ end
14
+ alias :inspect :to_s
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module Punchblock
2
- VERSION = "0.7.2"
2
+ VERSION = "0.8.0"
3
3
  end
data/punchblock.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
23
23
  s.required_rubygems_version = Gem::Requirement.new(">= 1.3.7") if s.respond_to? :required_rubygems_version=
24
24
 
25
25
  s.add_runtime_dependency %q<niceogiri>, [">= 0.0.4"]
26
- s.add_runtime_dependency %q<blather>, [">= 0.5.9"]
26
+ s.add_runtime_dependency %q<blather>, [">= 0.5.12"]
27
27
  s.add_runtime_dependency %q<activesupport>, [">= 2.1.0"]
28
28
  s.add_runtime_dependency %q<state_machine>, [">= 1.0.1"]
29
29
  s.add_runtime_dependency %q<future-resource>, [">= 0.0.2"]
@@ -73,8 +73,8 @@ module Punchblock
73
73
  offer = Event::Offer.new
74
74
  offer.call_id = '9f00061'
75
75
  offer.to = 'sip:whatever@127.0.0.1'
76
- say = <<-MSG
77
- <say xmlns='urn:xmpp:tropo:say:1' voice='allison'>
76
+ output = <<-MSG
77
+ <output xmlns='urn:xmpp:tropo:say:1'>
78
78
  <audio url='http://acme.com/greeting.mp3'>
79
79
  Thanks for calling ACME company
80
80
  </audio>
@@ -82,16 +82,16 @@ module Punchblock
82
82
  Your package was shipped on
83
83
  </audio>
84
84
  <say-as interpret-as='date'>12/01/2011</say-as>
85
- </say>
85
+ </output>
86
86
  MSG
87
- Component::Tropo::Say
88
- say = RayoNode.import parse_stanza(say).root
87
+ Component::Output
88
+ output = RayoNode.import parse_stanza(output).root
89
89
  connection.expects(:write_to_stream).once.returns true
90
90
  iq = Blather::Stanza::Iq.new :set, '9f00061@call.rayo.net'
91
91
  connection.expects(:create_iq).returns iq
92
92
 
93
93
  write_thread = Thread.new do
94
- connection.write offer.call_id, say
94
+ connection.write offer.call_id, output
95
95
  end
96
96
 
97
97
  result = import_stanza <<-MSG
@@ -106,22 +106,22 @@ module Punchblock
106
106
 
107
107
  write_thread.join
108
108
 
109
- say.state_name.should == :executing
109
+ output.state_name.should == :executing
110
110
 
111
- connection.original_component_from_id('fgh4590').should == say
111
+ connection.original_component_from_id('fgh4590').should == output
112
112
 
113
113
  example_complete = import_stanza <<-MSG
114
114
  <presence to='16577@app.rayo.net/1' from='9f00061@call.rayo.net/fgh4590'>
115
115
  <complete xmlns='urn:xmpp:rayo:ext:1'>
116
- <success xmlns='urn:xmpp:tropo:say:complete:1' />
116
+ <success xmlns='urn:xmpp:rayo:output:complete:1' />
117
117
  </complete>
118
118
  </presence>
119
119
  MSG
120
120
 
121
121
  connection.__send__ :handle_presence, example_complete
122
- say.complete_event(0.5).source.should == say
122
+ output.complete_event(0.5).source.should == output
123
123
 
124
- say.component_id.should == 'fgh4590'
124
+ output.component_id.should == 'fgh4590'
125
125
  end
126
126
 
127
127
  it 'should send a "Chat" presence when ready' do
@@ -164,7 +164,7 @@ module Punchblock
164
164
  <<-MSG
165
165
  <presence to='16577@app.rayo.net/1' from='9f00061@call.rayo.net/fgh4590'>
166
166
  <complete xmlns='urn:xmpp:rayo:ext:1'>
167
- <success xmlns='urn:xmpp:tropo:say:complete:1' />
167
+ <success xmlns='urn:xmpp:rayo:output:complete:1' />
168
168
  </complete>
169
169
  </presence>
170
170
  MSG
@@ -11,7 +11,7 @@ module Punchblock
11
11
  let :stanza do
12
12
  <<-MESSAGE
13
13
  <complete xmlns='urn:xmpp:rayo:ext:1'>
14
- <success xmlns='urn:xmpp:tropo:say:complete:1' />
14
+ <success xmlns='urn:xmpp:rayo:output:complete:1' />
15
15
  </complete>
16
16
  MESSAGE
17
17
  end
@@ -22,7 +22,7 @@ module Punchblock
22
22
 
23
23
  it_should_behave_like 'event'
24
24
 
25
- its(:reason) { should be_instance_of Component::Tropo::Say::Complete::Success }
25
+ its(:reason) { should be_instance_of Component::Output::Complete::Success }
26
26
  end
27
27
  end
28
28
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: punchblock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.8.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,11 +11,11 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2011-12-28 00:00:00.000000000 Z
14
+ date: 2012-01-06 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: niceogiri
18
- requirement: &2164396920 !ruby/object:Gem::Requirement
18
+ requirement: &2152641860 !ruby/object:Gem::Requirement
19
19
  none: false
20
20
  requirements:
21
21
  - - ! '>='
@@ -23,21 +23,21 @@ dependencies:
23
23
  version: 0.0.4
24
24
  type: :runtime
25
25
  prerelease: false
26
- version_requirements: *2164396920
26
+ version_requirements: *2152641860
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: blather
29
- requirement: &2164396380 !ruby/object:Gem::Requirement
29
+ requirement: &2152641280 !ruby/object:Gem::Requirement
30
30
  none: false
31
31
  requirements:
32
32
  - - ! '>='
33
33
  - !ruby/object:Gem::Version
34
- version: 0.5.9
34
+ version: 0.5.12
35
35
  type: :runtime
36
36
  prerelease: false
37
- version_requirements: *2164396380
37
+ version_requirements: *2152641280
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: activesupport
40
- requirement: &2164395740 !ruby/object:Gem::Requirement
40
+ requirement: &2152640660 !ruby/object:Gem::Requirement
41
41
  none: false
42
42
  requirements:
43
43
  - - ! '>='
@@ -45,10 +45,10 @@ dependencies:
45
45
  version: 2.1.0
46
46
  type: :runtime
47
47
  prerelease: false
48
- version_requirements: *2164395740
48
+ version_requirements: *2152640660
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: state_machine
51
- requirement: &2164395120 !ruby/object:Gem::Requirement
51
+ requirement: &2152639980 !ruby/object:Gem::Requirement
52
52
  none: false
53
53
  requirements:
54
54
  - - ! '>='
@@ -56,10 +56,10 @@ dependencies:
56
56
  version: 1.0.1
57
57
  type: :runtime
58
58
  prerelease: false
59
- version_requirements: *2164395120
59
+ version_requirements: *2152639980
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: future-resource
62
- requirement: &2164394580 !ruby/object:Gem::Requirement
62
+ requirement: &2152639220 !ruby/object:Gem::Requirement
63
63
  none: false
64
64
  requirements:
65
65
  - - ! '>='
@@ -67,10 +67,10 @@ dependencies:
67
67
  version: 0.0.2
68
68
  type: :runtime
69
69
  prerelease: false
70
- version_requirements: *2164394580
70
+ version_requirements: *2152639220
71
71
  - !ruby/object:Gem::Dependency
72
72
  name: has-guarded-handlers
73
- requirement: &2164393880 !ruby/object:Gem::Requirement
73
+ requirement: &2152638480 !ruby/object:Gem::Requirement
74
74
  none: false
75
75
  requirements:
76
76
  - - ! '>='
@@ -78,10 +78,10 @@ dependencies:
78
78
  version: 0.1.0
79
79
  type: :runtime
80
80
  prerelease: false
81
- version_requirements: *2164393880
81
+ version_requirements: *2152638480
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: celluloid
84
- requirement: &2164392120 !ruby/object:Gem::Requirement
84
+ requirement: &2152637500 !ruby/object:Gem::Requirement
85
85
  none: false
86
86
  requirements:
87
87
  - - ! '>='
@@ -89,10 +89,10 @@ dependencies:
89
89
  version: 0.6.0
90
90
  type: :runtime
91
91
  prerelease: false
92
- version_requirements: *2164392120
92
+ version_requirements: *2152637500
93
93
  - !ruby/object:Gem::Dependency
94
94
  name: ruby_ami
95
- requirement: &2164391380 !ruby/object:Gem::Requirement
95
+ requirement: &2152636520 !ruby/object:Gem::Requirement
96
96
  none: false
97
97
  requirements:
98
98
  - - ! '>='
@@ -100,10 +100,10 @@ dependencies:
100
100
  version: 0.1.3
101
101
  type: :runtime
102
102
  prerelease: false
103
- version_requirements: *2164391380
103
+ version_requirements: *2152636520
104
104
  - !ruby/object:Gem::Dependency
105
105
  name: ruby_speech
106
- requirement: &2164390440 !ruby/object:Gem::Requirement
106
+ requirement: &2152635380 !ruby/object:Gem::Requirement
107
107
  none: false
108
108
  requirements:
109
109
  - - ! '>='
@@ -111,10 +111,10 @@ dependencies:
111
111
  version: 0.3.4
112
112
  type: :runtime
113
113
  prerelease: false
114
- version_requirements: *2164390440
114
+ version_requirements: *2152635380
115
115
  - !ruby/object:Gem::Dependency
116
116
  name: bundler
117
- requirement: &2164389600 !ruby/object:Gem::Requirement
117
+ requirement: &2152634520 !ruby/object:Gem::Requirement
118
118
  none: false
119
119
  requirements:
120
120
  - - ~>
@@ -122,10 +122,10 @@ dependencies:
122
122
  version: 1.0.0
123
123
  type: :development
124
124
  prerelease: false
125
- version_requirements: *2164389600
125
+ version_requirements: *2152634520
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: rspec
128
- requirement: &2164353240 !ruby/object:Gem::Requirement
128
+ requirement: &2152633440 !ruby/object:Gem::Requirement
129
129
  none: false
130
130
  requirements:
131
131
  - - ! '>='
@@ -133,10 +133,10 @@ dependencies:
133
133
  version: 2.5.0
134
134
  type: :development
135
135
  prerelease: false
136
- version_requirements: *2164353240
136
+ version_requirements: *2152633440
137
137
  - !ruby/object:Gem::Dependency
138
138
  name: ci_reporter
139
- requirement: &2164352260 !ruby/object:Gem::Requirement
139
+ requirement: &2152632400 !ruby/object:Gem::Requirement
140
140
  none: false
141
141
  requirements:
142
142
  - - ! '>='
@@ -144,10 +144,10 @@ dependencies:
144
144
  version: 1.6.3
145
145
  type: :development
146
146
  prerelease: false
147
- version_requirements: *2164352260
147
+ version_requirements: *2152632400
148
148
  - !ruby/object:Gem::Dependency
149
149
  name: yard
150
- requirement: &2164351080 !ruby/object:Gem::Requirement
150
+ requirement: &2152631620 !ruby/object:Gem::Requirement
151
151
  none: false
152
152
  requirements:
153
153
  - - ~>
@@ -155,10 +155,10 @@ dependencies:
155
155
  version: 0.6.0
156
156
  type: :development
157
157
  prerelease: false
158
- version_requirements: *2164351080
158
+ version_requirements: *2152631620
159
159
  - !ruby/object:Gem::Dependency
160
160
  name: rcov
161
- requirement: &2164350380 !ruby/object:Gem::Requirement
161
+ requirement: &2152630600 !ruby/object:Gem::Requirement
162
162
  none: false
163
163
  requirements:
164
164
  - - ! '>='
@@ -166,10 +166,10 @@ dependencies:
166
166
  version: '0'
167
167
  type: :development
168
168
  prerelease: false
169
- version_requirements: *2164350380
169
+ version_requirements: *2152630600
170
170
  - !ruby/object:Gem::Dependency
171
171
  name: rake
172
- requirement: &2164348740 !ruby/object:Gem::Requirement
172
+ requirement: &2152629520 !ruby/object:Gem::Requirement
173
173
  none: false
174
174
  requirements:
175
175
  - - ! '>='
@@ -177,10 +177,10 @@ dependencies:
177
177
  version: '0'
178
178
  type: :development
179
179
  prerelease: false
180
- version_requirements: *2164348740
180
+ version_requirements: *2152629520
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: mocha
183
- requirement: &2164348020 !ruby/object:Gem::Requirement
183
+ requirement: &2152622020 !ruby/object:Gem::Requirement
184
184
  none: false
185
185
  requirements:
186
186
  - - ! '>='
@@ -188,10 +188,10 @@ dependencies:
188
188
  version: '0'
189
189
  type: :development
190
190
  prerelease: false
191
- version_requirements: *2164348020
191
+ version_requirements: *2152622020
192
192
  - !ruby/object:Gem::Dependency
193
193
  name: i18n
194
- requirement: &2164347400 !ruby/object:Gem::Requirement
194
+ requirement: &2152620880 !ruby/object:Gem::Requirement
195
195
  none: false
196
196
  requirements:
197
197
  - - ! '>='
@@ -199,10 +199,10 @@ dependencies:
199
199
  version: '0'
200
200
  type: :development
201
201
  prerelease: false
202
- version_requirements: *2164347400
202
+ version_requirements: *2152620880
203
203
  - !ruby/object:Gem::Dependency
204
204
  name: countdownlatch
205
- requirement: &2164346700 !ruby/object:Gem::Requirement
205
+ requirement: &2152619940 !ruby/object:Gem::Requirement
206
206
  none: false
207
207
  requirements:
208
208
  - - ! '>='
@@ -210,10 +210,10 @@ dependencies:
210
210
  version: '0'
211
211
  type: :development
212
212
  prerelease: false
213
- version_requirements: *2164346700
213
+ version_requirements: *2152619940
214
214
  - !ruby/object:Gem::Dependency
215
215
  name: guard-rspec
216
- requirement: &2164346200 !ruby/object:Gem::Requirement
216
+ requirement: &2152619460 !ruby/object:Gem::Requirement
217
217
  none: false
218
218
  requirements:
219
219
  - - ! '>='
@@ -221,7 +221,7 @@ dependencies:
221
221
  version: '0'
222
222
  type: :development
223
223
  prerelease: false
224
- version_requirements: *2164346200
224
+ version_requirements: *2152619460
225
225
  description: Like Rack is to Rails and Sinatra, Punchblock provides a consistent API
226
226
  on top of several underlying third-party call control protocols.
227
227
  email: punchblock@adhearsion.com
@@ -265,10 +265,7 @@ files:
265
265
  - lib/punchblock/component/record.rb
266
266
  - lib/punchblock/component/stop.rb
267
267
  - lib/punchblock/component/tropo.rb
268
- - lib/punchblock/component/tropo/ask.rb
269
268
  - lib/punchblock/component/tropo/conference.rb
270
- - lib/punchblock/component/tropo/say.rb
271
- - lib/punchblock/component/tropo/transfer.rb
272
269
  - lib/punchblock/connection.rb
273
270
  - lib/punchblock/connection/asterisk.rb
274
271
  - lib/punchblock/connection/connected.rb
@@ -277,6 +274,7 @@ files:
277
274
  - lib/punchblock/core_ext/blather/stanza.rb
278
275
  - lib/punchblock/core_ext/blather/stanza/presence.rb
279
276
  - lib/punchblock/core_ext/ruby.rb
277
+ - lib/punchblock/disconnected_error.rb
280
278
  - lib/punchblock/event.rb
281
279
  - lib/punchblock/event/answered.rb
282
280
  - lib/punchblock/event/asterisk.rb
@@ -326,10 +324,7 @@ files:
326
324
  - spec/punchblock/component/input_spec.rb
327
325
  - spec/punchblock/component/output_spec.rb
328
326
  - spec/punchblock/component/record_spec.rb
329
- - spec/punchblock/component/tropo/ask_spec.rb
330
327
  - spec/punchblock/component/tropo/conference_spec.rb
331
- - spec/punchblock/component/tropo/say_spec.rb
332
- - spec/punchblock/component/tropo/transfer_spec.rb
333
328
  - spec/punchblock/connection/asterisk_spec.rb
334
329
  - spec/punchblock/connection/xmpp_spec.rb
335
330
  - spec/punchblock/event/answered_spec.rb
@@ -366,7 +361,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
366
361
  version: '0'
367
362
  segments:
368
363
  - 0
369
- hash: 1241799790800462241
364
+ hash: -4143902595723607278
370
365
  required_rubygems_version: !ruby/object:Gem::Requirement
371
366
  none: false
372
367
  requirements:
@@ -399,10 +394,7 @@ test_files:
399
394
  - spec/punchblock/component/input_spec.rb
400
395
  - spec/punchblock/component/output_spec.rb
401
396
  - spec/punchblock/component/record_spec.rb
402
- - spec/punchblock/component/tropo/ask_spec.rb
403
397
  - spec/punchblock/component/tropo/conference_spec.rb
404
- - spec/punchblock/component/tropo/say_spec.rb
405
- - spec/punchblock/component/tropo/transfer_spec.rb
406
398
  - spec/punchblock/connection/asterisk_spec.rb
407
399
  - spec/punchblock/connection/xmpp_spec.rb
408
400
  - spec/punchblock/event/answered_spec.rb