punchblock 0.8.4 → 0.9.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.
@@ -1,284 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Punchblock
4
- module Translator
5
- class Asterisk
6
- module Component
7
- module Asterisk
8
- describe Input do
9
- let(:connection) do
10
- mock_connection_with_event_handler do |event|
11
- command.add_event event
12
- end
13
- end
14
- let(:media_engine) { nil }
15
- let(:translator) { Punchblock::Translator::Asterisk.new mock('AMI'), connection, media_engine }
16
- let(:call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator }
17
- let(:command_options) { {} }
18
-
19
- let :command do
20
- Punchblock::Component::Input.new command_options
21
- end
22
-
23
- let :grammar do
24
- RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'pin' do
25
- rule id: 'digit' do
26
- one_of do
27
- 0.upto(9) { |d| item { d.to_s } }
28
- end
29
- end
30
-
31
- rule id: 'pin', scope: 'public' do
32
- item repeat: '2' do
33
- ruleref uri: '#digit'
34
- end
35
- end
36
- end
37
- end
38
-
39
- subject { Input.new command, call }
40
-
41
- describe '#execute' do
42
- before { command.request! }
43
-
44
- context 'with a media engine of :unimrcp' do
45
- pending
46
- let(:media_engine) { :unimrcp }
47
- end
48
-
49
- context 'with a media engine of :asterisk' do
50
- let(:media_engine) { :asterisk }
51
-
52
- let(:command_opts) { {} }
53
-
54
- let :command_options do
55
- { :mode => :dtmf, :grammar => { :value => grammar } }.merge(command_opts)
56
- end
57
-
58
- def ami_event_for_dtmf(digit, position)
59
- RubyAMI::Event.new('DTMF').tap do |e|
60
- e['Digit'] = digit.to_s
61
- e['Start'] = position == :start ? 'Yes' : 'No'
62
- e['End'] = position == :end ? 'Yes' : 'No'
63
- end
64
- end
65
-
66
- def send_ami_events_for_dtmf(digit)
67
- call.process_ami_event ami_event_for_dtmf(digit, :start)
68
- call.process_ami_event ami_event_for_dtmf(digit, :end)
69
- end
70
-
71
- let(:reason) { command.complete_event(5).reason }
72
-
73
- describe "receiving DTMF events" do
74
- before do
75
- subject.execute
76
- expected_event
77
- end
78
-
79
- context "when a match is found" do
80
- before do
81
- send_ami_events_for_dtmf 1
82
- send_ami_events_for_dtmf 2
83
- end
84
-
85
- let :expected_event do
86
- Punchblock::Component::Input::Complete::Success.new :mode => :dtmf,
87
- :confidence => 1,
88
- :utterance => '12',
89
- :interpretation => 'dtmf-1 dtmf-2',
90
- :component_id => subject.id,
91
- :call_id => call.id
92
- end
93
-
94
- it "should send a success complete event with the relevant data" do
95
- reason.should == expected_event
96
- end
97
- end
98
-
99
- context "when the match is invalid" do
100
- before do
101
- send_ami_events_for_dtmf 1
102
- send_ami_events_for_dtmf '#'
103
- end
104
-
105
- let :expected_event do
106
- Punchblock::Component::Input::Complete::NoMatch.new :component_id => subject.id,
107
- :call_id => call.id
108
- end
109
-
110
- it "should send a nomatch complete event" do
111
- reason.should == expected_event
112
- end
113
- end
114
- end
115
-
116
- describe 'grammar' do
117
- context 'unset' do
118
- let(:command_opts) { { :grammar => nil } }
119
- it "should return an error and not execute any actions" do
120
- subject.execute
121
- error = ProtocolError.new 'option error', 'A grammar document is required.'
122
- command.response(0.1).should == error
123
- end
124
- end
125
- end
126
-
127
- describe 'mode' do
128
- context 'unset' do
129
- let(:command_opts) { { :mode => nil } }
130
- it "should return an error and not execute any actions" do
131
- subject.execute
132
- error = ProtocolError.new 'option error', 'A mode value other than DTMF is unsupported on Asterisk.'
133
- command.response(0.1).should == error
134
- end
135
- end
136
-
137
- context 'any' do
138
- let(:command_opts) { { :mode => :any } }
139
- it "should return an error and not execute any actions" do
140
- subject.execute
141
- error = ProtocolError.new 'option error', 'A mode value other than DTMF is unsupported on Asterisk.'
142
- command.response(0.1).should == error
143
- end
144
- end
145
-
146
- context 'speech' do
147
- let(:command_opts) { { :mode => :speech } }
148
- it "should return an error and not execute any actions" do
149
- subject.execute
150
- error = ProtocolError.new 'option error', 'A mode value other than DTMF is unsupported on Asterisk.'
151
- command.response(0.1).should == error
152
- end
153
- end
154
- end
155
-
156
- describe 'terminator' do
157
- pending
158
- end
159
-
160
- describe 'recognizer' do
161
- pending
162
- end
163
-
164
- describe 'initial-timeout' do
165
- context 'a positive number' do
166
- let(:command_opts) { { :initial_timeout => 1000 } }
167
-
168
- it "should not cause a NoInput if first input is received in time" do
169
- subject.execute
170
- send_ami_events_for_dtmf 1
171
- sleep 1.5
172
- send_ami_events_for_dtmf 2
173
- reason.should be_a Punchblock::Component::Input::Complete::Success
174
- end
175
-
176
- it "should cause a NoInput complete event to be sent after the timeout" do
177
- subject.execute
178
- sleep 1.5
179
- send_ami_events_for_dtmf 1
180
- send_ami_events_for_dtmf 2
181
- reason.should be_a Punchblock::Component::Input::Complete::NoInput
182
- end
183
- end
184
-
185
- context '-1' do
186
- let(:command_opts) { { :initial_timeout => -1 } }
187
-
188
- it "should not start a timer" do
189
- subject.wrapped_object.expects(:begin_initial_timer).never
190
- subject.execute
191
- end
192
- end
193
-
194
- context 'unset' do
195
- let(:command_opts) { { :initial_timeout => nil } }
196
-
197
- it "should not start a timer" do
198
- subject.wrapped_object.expects(:begin_initial_timer).never
199
- subject.execute
200
- end
201
- end
202
-
203
- context 'a negative number other than -1' do
204
- let(:command_opts) { { :initial_timeout => -1000 } }
205
-
206
- it "should return an error and not execute any actions" do
207
- subject.execute
208
- error = ProtocolError.new 'option error', 'An initial timeout value that is negative (and not -1) is invalid.'
209
- command.response(0.1).should == error
210
- end
211
- end
212
- end
213
-
214
- describe 'inter-digit-timeout' do
215
- context 'a positive number' do
216
- let(:command_opts) { { :inter_digit_timeout => 1000 } }
217
-
218
- it "should not prevent a Match if input is received in time" do
219
- subject.execute
220
- sleep 1.5
221
- send_ami_events_for_dtmf 1
222
- sleep 0.5
223
- send_ami_events_for_dtmf 2
224
- reason.should be_a Punchblock::Component::Input::Complete::Success
225
- end
226
-
227
- it "should cause a NoMatch complete event to be sent after the timeout" do
228
- subject.execute
229
- sleep 1.5
230
- send_ami_events_for_dtmf 1
231
- sleep 1.5
232
- send_ami_events_for_dtmf 2
233
- reason.should be_a Punchblock::Component::Input::Complete::NoMatch
234
- end
235
- end
236
-
237
- context '-1' do
238
- let(:command_opts) { { :inter_digit_timeout => -1 } }
239
-
240
- it "should not start a timer" do
241
- subject.wrapped_object.expects(:begin_inter_digit_timer).never
242
- subject.execute
243
- end
244
- end
245
-
246
- context 'unset' do
247
- let(:command_opts) { { :inter_digit_timeout => nil } }
248
-
249
- it "should not start a timer" do
250
- subject.wrapped_object.expects(:begin_inter_digit_timer).never
251
- subject.execute
252
- end
253
- end
254
-
255
- context 'a negative number other than -1' do
256
- let(:command_opts) { { :inter_digit_timeout => -1000 } }
257
-
258
- it "should return an error and not execute any actions" do
259
- subject.execute
260
- error = ProtocolError.new 'option error', 'An inter-digit timeout value that is negative (and not -1) is invalid.'
261
- command.response(0.1).should == error
262
- end
263
- end
264
- end
265
-
266
- describe 'sensitivity' do
267
- pending
268
- end
269
-
270
- describe 'min-confidence' do
271
- pending
272
- end
273
-
274
- describe 'max-silence' do
275
- pending
276
- end
277
- end
278
- end
279
- end
280
- end
281
- end
282
- end
283
- end
284
- end
@@ -1,493 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Punchblock
4
- module Translator
5
- class Asterisk
6
- module Component
7
- module Asterisk
8
- describe Output do
9
- let(:connection) do
10
- mock_connection_with_event_handler do |event|
11
- command.add_event event
12
- end
13
- end
14
- let(:media_engine) { nil }
15
- let(:translator) { Punchblock::Translator::Asterisk.new mock('AMI'), connection, media_engine }
16
- let(:mock_call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator }
17
-
18
- let :command do
19
- Punchblock::Component::Output.new command_options
20
- end
21
-
22
- let :ssml_doc do
23
- RubySpeech::SSML.draw do
24
- say_as(:interpret_as => :cardinal) { 'FOO' }
25
- end
26
- end
27
-
28
- let :command_options do
29
- { :ssml => ssml_doc }
30
- end
31
-
32
- subject { Output.new command, mock_call }
33
-
34
- describe '#execute' do
35
- before { command.request! }
36
-
37
- context 'with a media engine of :unimrcp' do
38
- let(:media_engine) { :unimrcp }
39
-
40
- let(:audio_filename) { 'http://foo.com/bar.mp3' }
41
-
42
- let :ssml_doc do
43
- RubySpeech::SSML.draw do
44
- audio :src => audio_filename
45
- say_as(:interpret_as => :cardinal) { 'FOO' }
46
- end
47
- end
48
-
49
- let(:command_opts) { {} }
50
-
51
- let :command_options do
52
- { :ssml => ssml_doc }.merge(command_opts)
53
- end
54
-
55
- def expect_mrcpsynth_with_options(options)
56
- mock_call.expects(:send_agi_action!).once.with do |*args|
57
- args[0].should == 'EXEC MRCPSynth'
58
- args[2].should match options
59
- end
60
- end
61
-
62
- it "should execute MRCPSynth" do
63
- mock_call.expects(:send_agi_action!).once.with 'EXEC MRCPSynth', ssml_doc.to_s.squish.gsub(/["\\]/) { |m| "\\#{m}" }, ''
64
- subject.execute
65
- end
66
-
67
- it 'should send a complete event when MRCPSynth completes' do
68
- def mock_call.send_agi_action!(*args, &block)
69
- block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
70
- end
71
- subject.execute
72
- command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
73
- end
74
-
75
- describe 'ssml' do
76
- context 'unset' do
77
- let(:command_opts) { { :ssml => nil } }
78
- it "should return an error and not execute any actions" do
79
- subject.execute
80
- error = ProtocolError.new 'option error', 'An SSML document is required.'
81
- command.response(0.1).should == error
82
- end
83
- end
84
- end
85
-
86
- describe 'start-offset' do
87
- context 'unset' do
88
- let(:command_opts) { { :start_offset => nil } }
89
- it 'should not pass any options to MRCPSynth' do
90
- expect_mrcpsynth_with_options(//)
91
- subject.execute
92
- end
93
- end
94
-
95
- context 'set' do
96
- let(:command_opts) { { :start_offset => 10 } }
97
- it "should return an error and not execute any actions" do
98
- subject.execute
99
- error = ProtocolError.new 'option error', 'A start_offset value is unsupported on Asterisk.'
100
- command.response(0.1).should == error
101
- end
102
- end
103
- end
104
-
105
- describe 'start-paused' do
106
- context 'false' do
107
- let(:command_opts) { { :start_paused => false } }
108
- it 'should not pass any options to MRCPSynth' do
109
- expect_mrcpsynth_with_options(//)
110
- subject.execute
111
- end
112
- end
113
-
114
- context 'true' do
115
- let(:command_opts) { { :start_paused => true } }
116
- it "should return an error and not execute any actions" do
117
- subject.execute
118
- error = ProtocolError.new 'option error', 'A start_paused value is unsupported on Asterisk.'
119
- command.response(0.1).should == error
120
- end
121
- end
122
- end
123
-
124
- describe 'repeat-interval' do
125
- context 'unset' do
126
- let(:command_opts) { { :repeat_interval => nil } }
127
- it 'should not pass any options to MRCPSynth' do
128
- expect_mrcpsynth_with_options(//)
129
- subject.execute
130
- end
131
- end
132
-
133
- context 'set' do
134
- let(:command_opts) { { :repeat_interval => 10 } }
135
- it "should return an error and not execute any actions" do
136
- subject.execute
137
- error = ProtocolError.new 'option error', 'A repeat_interval value is unsupported on Asterisk.'
138
- command.response(0.1).should == error
139
- end
140
- end
141
- end
142
-
143
- describe 'repeat-times' do
144
- context 'unset' do
145
- let(:command_opts) { { :repeat_times => nil } }
146
- it 'should not pass any options to MRCPSynth' do
147
- expect_mrcpsynth_with_options(//)
148
- subject.execute
149
- end
150
- end
151
-
152
- context 'set' do
153
- let(:command_opts) { { :repeat_times => 2 } }
154
- it "should return an error and not execute any actions" do
155
- subject.execute
156
- error = ProtocolError.new 'option error', 'A repeat_times value is unsupported on Asterisk.'
157
- command.response(0.1).should == error
158
- end
159
- end
160
- end
161
-
162
- describe 'max-time' do
163
- context 'unset' do
164
- let(:command_opts) { { :max_time => nil } }
165
- it 'should not pass any options to MRCPSynth' do
166
- expect_mrcpsynth_with_options(//)
167
- subject.execute
168
- end
169
- end
170
-
171
- context 'set' do
172
- let(:command_opts) { { :max_time => 30 } }
173
- it "should return an error and not execute any actions" do
174
- subject.execute
175
- error = ProtocolError.new 'option error', 'A max_time value is unsupported on Asterisk.'
176
- command.response(0.1).should == error
177
- end
178
- end
179
- end
180
-
181
- describe 'voice' do
182
- context 'unset' do
183
- let(:command_opts) { { :voice => nil } }
184
- it 'should not pass the v option to MRCPSynth' do
185
- expect_mrcpsynth_with_options(//)
186
- subject.execute
187
- end
188
- end
189
-
190
- context 'set' do
191
- let(:command_opts) { { :voice => 'alison' } }
192
- it 'should pass the v option to MRCPSynth' do
193
- expect_mrcpsynth_with_options(/v=alison/)
194
- subject.execute
195
- end
196
- end
197
- end
198
-
199
- describe 'interrupt_on' do
200
- context "set to nil" do
201
- let(:command_opts) { { :interrupt_on => nil } }
202
- it "should not pass the i option to MRCPSynth" do
203
- expect_mrcpsynth_with_options(//)
204
- subject.execute
205
- end
206
- end
207
-
208
- context "set to :any" do
209
- let(:command_opts) { { :interrupt_on => :any } }
210
- it "should pass the i option to MRCPSynth" do
211
- expect_mrcpsynth_with_options(/i=any/)
212
- subject.execute
213
- end
214
- end
215
-
216
- context "set to :dtmf" do
217
- let(:command_opts) { { :interrupt_on => :dtmf } }
218
- it "should pass the i option to MRCPSynth" do
219
- expect_mrcpsynth_with_options(/i=any/)
220
- subject.execute
221
- end
222
- end
223
-
224
- context "set to :speech" do
225
- let(:command_opts) { { :interrupt_on => :speech } }
226
- it "should return an error and not execute any actions" do
227
- subject.execute
228
- error = ProtocolError.new 'option error', 'An interrupt-on value of speech is unsupported.'
229
- command.response(0.1).should == error
230
- end
231
- end
232
- end
233
- end
234
-
235
- context 'with a media engine of :asterisk' do
236
- let(:media_engine) { :asterisk }
237
-
238
- def expect_stream_file_with_options(options = nil)
239
- mock_call.expects(:send_agi_action!).once.with 'STREAM FILE', audio_filename, options do |*args|
240
- args[2].should == options
241
- subject.continue!
242
- true
243
- end
244
- end
245
-
246
- let(:audio_filename) { 'http://foo.com/bar.mp3' }
247
-
248
- let :ssml_doc do
249
- RubySpeech::SSML.draw do
250
- audio :src => audio_filename
251
- say_as(:interpret_as => :cardinal) { 'FOO' }
252
- end
253
- end
254
-
255
- let(:command_opts) { {} }
256
-
257
- let :command_options do
258
- { :ssml => ssml_doc }.merge(command_opts)
259
- end
260
-
261
- let :command do
262
- Punchblock::Component::Output.new command_options
263
- end
264
-
265
- describe 'ssml' do
266
- context 'unset' do
267
- let(:command_opts) { { :ssml => nil } }
268
- it "should return an error and not execute any actions" do
269
- subject.execute
270
- error = ProtocolError.new 'option error', 'An SSML document is required.'
271
- command.response(0.1).should == error
272
- end
273
- end
274
-
275
- context 'with a single audio SSML node' do
276
- let(:audio_filename) { 'http://foo.com/bar.mp3' }
277
- let :command_options do
278
- {
279
- :ssml => RubySpeech::SSML.draw { audio :src => audio_filename }
280
- }
281
- end
282
-
283
- it 'should playback the audio file using STREAM FILE' do
284
- expect_stream_file_with_options
285
- subject.execute
286
- end
287
-
288
- it 'should send a complete event when the file finishes playback' do
289
- def mock_call.send_agi_action!(*args, &block)
290
- block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
291
- end
292
- subject.execute
293
- command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
294
- end
295
- end
296
-
297
- context 'with multiple audio SSML nodes' do
298
- let(:audio_filename1) { 'http://foo.com/bar.mp3' }
299
- let(:audio_filename2) { 'http://foo.com/baz.mp3' }
300
- let :command_options do
301
- {
302
- :ssml => RubySpeech::SSML.draw do
303
- audio :src => audio_filename1
304
- audio :src => audio_filename2
305
- end
306
- }
307
- end
308
-
309
- it 'should playback each audio file using STREAM FILE' do
310
- latch = CountDownLatch.new 2
311
- mock_call.expects(:send_agi_action!).once.with 'STREAM FILE', audio_filename1, nil do
312
- subject.continue
313
- true
314
- latch.countdown!
315
- end
316
- mock_call.expects(:send_agi_action!).once.with 'STREAM FILE', audio_filename2, nil do
317
- subject.continue
318
- true
319
- latch.countdown!
320
- end
321
- subject.execute
322
- latch.wait 2
323
- sleep 2
324
- end
325
-
326
- it 'should send a complete event after the final file has finished playback' do
327
- def mock_call.send_agi_action!(*args, &block)
328
- block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
329
- end
330
- command.expects(:add_event).once.with do |e|
331
- e.reason.should be_a Punchblock::Component::Output::Complete::Success
332
- end
333
- subject.execute
334
- end
335
- end
336
- end
337
-
338
- describe 'start-offset' do
339
- context 'unset' do
340
- let(:command_opts) { { :start_offset => nil } }
341
- it 'should not pass any options to STREAM FILE' do
342
- expect_stream_file_with_options
343
- subject.execute
344
- end
345
- end
346
-
347
- context 'set' do
348
- let(:command_opts) { { :start_offset => 10 } }
349
- it "should return an error and not execute any actions" do
350
- subject.execute
351
- error = ProtocolError.new 'option error', 'A start_offset value is unsupported on Asterisk.'
352
- command.response(0.1).should == error
353
- end
354
- end
355
- end
356
-
357
- describe 'start-paused' do
358
- context 'false' do
359
- let(:command_opts) { { :start_paused => false } }
360
- it 'should not pass any options to STREAM FILE' do
361
- expect_stream_file_with_options
362
- subject.execute
363
- end
364
- end
365
-
366
- context 'true' do
367
- let(:command_opts) { { :start_paused => true } }
368
- it "should return an error and not execute any actions" do
369
- subject.execute
370
- error = ProtocolError.new 'option error', 'A start_paused value is unsupported on Asterisk.'
371
- command.response(0.1).should == error
372
- end
373
- end
374
- end
375
-
376
- describe 'repeat-interval' do
377
- context 'unset' do
378
- let(:command_opts) { { :repeat_interval => nil } }
379
- it 'should not pass any options to STREAM FILE' do
380
- expect_stream_file_with_options
381
- subject.execute
382
- end
383
- end
384
-
385
- context 'set' do
386
- let(:command_opts) { { :repeat_interval => 10 } }
387
- it "should return an error and not execute any actions" do
388
- subject.execute
389
- error = ProtocolError.new 'option error', 'A repeat_interval value is unsupported on Asterisk.'
390
- command.response(0.1).should == error
391
- end
392
- end
393
- end
394
-
395
- describe 'repeat-times' do
396
- context 'unset' do
397
- let(:command_opts) { { :repeat_times => nil } }
398
- it 'should not pass any options to STREAM FILE' do
399
- expect_stream_file_with_options
400
- subject.execute
401
- end
402
- end
403
-
404
- context 'set' do
405
- let(:command_opts) { { :repeat_times => 2 } }
406
- it "should return an error and not execute any actions" do
407
- subject.execute
408
- error = ProtocolError.new 'option error', 'A repeat_times value is unsupported on Asterisk.'
409
- command.response(0.1).should == error
410
- end
411
- end
412
- end
413
-
414
- describe 'max-time' do
415
- context 'unset' do
416
- let(:command_opts) { { :max_time => nil } }
417
- it 'should not pass any options to STREAM FILE' do
418
- expect_stream_file_with_options
419
- subject.execute
420
- end
421
- end
422
-
423
- context 'set' do
424
- let(:command_opts) { { :max_time => 30 } }
425
- it "should return an error and not execute any actions" do
426
- subject.execute
427
- error = ProtocolError.new 'option error', 'A max_time value is unsupported on Asterisk.'
428
- command.response(0.1).should == error
429
- end
430
- end
431
- end
432
-
433
- describe 'voice' do
434
- context 'unset' do
435
- let(:command_opts) { { :voice => nil } }
436
- it 'should not pass the v option to STREAM FILE' do
437
- expect_stream_file_with_options
438
- subject.execute
439
- end
440
- end
441
-
442
- context 'set' do
443
- let(:command_opts) { { :voice => 'alison' } }
444
- it "should return an error and not execute any actions" do
445
- subject.execute
446
- error = ProtocolError.new 'option error', 'A voice value is unsupported on Asterisk.'
447
- command.response(0.1).should == error
448
- end
449
- end
450
- end
451
-
452
- describe 'interrupt_on' do
453
- context "set to nil" do
454
- let(:command_opts) { { :interrupt_on => nil } }
455
- it "should not pass any digits to STREAM FILE" do
456
- expect_stream_file_with_options
457
- subject.execute
458
- end
459
- end
460
-
461
- context "set to :any" do
462
- let(:command_opts) { { :interrupt_on => :any } }
463
- it "should pass all digits to STREAM FILE" do
464
- expect_stream_file_with_options '0123456789*#'
465
- subject.execute
466
- end
467
- end
468
-
469
- context "set to :dtmf" do
470
- let(:command_opts) { { :interrupt_on => :dtmf } }
471
- it "should pass all digits to STREAM FILE" do
472
- expect_stream_file_with_options '0123456789*#'
473
- subject.execute
474
- end
475
- end
476
-
477
- context "set to :speech" do
478
- let(:command_opts) { { :interrupt_on => :speech } }
479
- it "should return an error and not execute any actions" do
480
- subject.execute
481
- error = ProtocolError.new 'option error', 'An interrupt-on value of speech is unsupported.'
482
- command.response(0.1).should == error
483
- end
484
- end
485
- end
486
- end
487
- end
488
- end
489
- end
490
- end
491
- end
492
- end
493
- end