adhearsion 2.1.3 → 2.2.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.
- data/CHANGELOG.md +87 -0
- data/Guardfile +1 -1
- data/Rakefile +1 -2
- data/adhearsion.gemspec +1 -1
- data/features/cli_create.feature +1 -0
- data/features/step_definitions/cli_steps.rb +0 -10
- data/lib/adhearsion.rb +11 -0
- data/lib/adhearsion/call.rb +27 -12
- data/lib/adhearsion/call_controller.rb +1 -1
- data/lib/adhearsion/call_controller/dial.rb +3 -1
- data/lib/adhearsion/call_controller/input.rb +4 -4
- data/lib/adhearsion/call_controller/menu_dsl.rb +1 -0
- data/lib/adhearsion/call_controller/menu_dsl/array_match_calculator.rb +26 -0
- data/lib/adhearsion/call_controller/menu_dsl/fixnum_match_calculator.rb +2 -14
- data/lib/adhearsion/call_controller/menu_dsl/string_match_calculator.rb +6 -6
- data/lib/adhearsion/call_controller/output.rb +20 -14
- data/lib/adhearsion/call_controller/output/abstract_player.rb +5 -5
- data/lib/adhearsion/call_controller/output/formatter.rb +68 -68
- data/lib/adhearsion/call_controller/record.rb +91 -28
- data/lib/adhearsion/call_controller/utility.rb +12 -5
- data/lib/adhearsion/calls.rb +1 -1
- data/lib/adhearsion/configuration.rb +4 -0
- data/lib/adhearsion/events.rb +2 -1
- data/lib/adhearsion/generators/app/app_generator.rb +2 -2
- data/lib/adhearsion/generators/app/templates/Gemfile.erb +4 -0
- data/lib/adhearsion/generators/app/templates/Rakefile +5 -0
- data/lib/adhearsion/generators/app/templates/lib/simon_game.rb +3 -0
- data/lib/adhearsion/generators/app/templates/rspec +2 -0
- data/lib/adhearsion/generators/app/templates/spec/call_controllers/simon_game_spec.rb +142 -0
- data/lib/adhearsion/generators/controller/templates/lib/controller.rb +1 -1
- data/lib/adhearsion/generators/controller/templates/spec/controller_spec.rb +2 -0
- data/lib/adhearsion/initializer.rb +3 -2
- data/lib/adhearsion/outbound_call.rb +5 -2
- data/lib/adhearsion/router/route.rb +13 -2
- data/lib/adhearsion/rspec.rb +1 -1
- data/lib/adhearsion/statistics.rb +138 -0
- data/lib/adhearsion/tasks/environment.rb +1 -1
- data/lib/adhearsion/version.rb +1 -1
- data/spec/adhearsion/call_controller/dial_spec.rb +26 -0
- data/spec/adhearsion/call_controller/input_spec.rb +13 -1
- data/spec/adhearsion/call_controller/menu_dsl/array_match_calculator_spec.rb +76 -0
- data/spec/adhearsion/call_controller/menu_dsl/fixnum_match_calculator_spec.rb +8 -6
- data/spec/adhearsion/call_controller/menu_dsl/range_match_calculator_spec.rb +4 -4
- data/spec/adhearsion/call_controller/menu_dsl/string_match_calculator_spec.rb +6 -6
- data/spec/adhearsion/call_controller/output/formatter_spec.rb +3 -5
- data/spec/adhearsion/call_controller/output_spec.rb +59 -25
- data/spec/adhearsion/call_controller/record_spec.rb +123 -2
- data/spec/adhearsion/call_controller/utility_spec.rb +31 -11
- data/spec/adhearsion/call_spec.rb +77 -36
- data/spec/adhearsion/calls_spec.rb +13 -0
- data/spec/adhearsion/initializer_spec.rb +7 -0
- data/spec/adhearsion/outbound_call_spec.rb +14 -0
- data/spec/adhearsion/punchblock_plugin_spec.rb +5 -2
- data/spec/adhearsion/router/openended_route_spec.rb +2 -1
- data/spec/adhearsion/router/route_spec.rb +9 -1
- data/spec/adhearsion/router/unaccepting_route_spec.rb +2 -1
- data/spec/adhearsion/statistics/dump_spec.rb +38 -0
- data/spec/adhearsion/statistics_spec.rb +61 -0
- data/spec/adhearsion_spec.rb +21 -1
- data/spec/spec_helper.rb +2 -0
- metadata +16 -7
@@ -17,12 +17,12 @@ module Adhearsion
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
def play_ssml_for(*args)
|
21
|
-
play_ssml Formatter.ssml_for(args)
|
22
|
-
end
|
23
|
-
|
24
20
|
def new_output(options)
|
25
|
-
|
21
|
+
defaults = {}
|
22
|
+
default_voice = Adhearsion.config.punchblock[:default_voice]
|
23
|
+
defaults[:voice] = default_voice if default_voice
|
24
|
+
|
25
|
+
Punchblock::Component::Output.new defaults.merge(options)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
@@ -3,90 +3,90 @@
|
|
3
3
|
module Adhearsion
|
4
4
|
class CallController
|
5
5
|
module Output
|
6
|
-
|
6
|
+
class Formatter
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
Formatter.ssml_for argument
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def detect_type(output)
|
23
|
-
case output
|
24
|
-
when Date, Time, DateTime
|
25
|
-
:time
|
26
|
-
when Numeric, /^\d+$/
|
27
|
-
:numeric
|
28
|
-
when /^\//, ->(string) { uri? string }
|
29
|
-
:audio
|
8
|
+
def ssml_for_collection(collection)
|
9
|
+
collection.inject RubySpeech::SSML::Speak.new do |doc, argument|
|
10
|
+
doc + case argument
|
11
|
+
when Hash
|
12
|
+
ssml_for argument.delete(:value), argument
|
13
|
+
when RubySpeech::SSML::Speak
|
14
|
+
argument
|
15
|
+
when lambda { |a| a.respond_to? :each }
|
16
|
+
ssml_for_collection argument
|
30
17
|
else
|
31
|
-
|
18
|
+
ssml_for argument
|
32
19
|
end
|
33
20
|
end
|
21
|
+
end
|
34
22
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
23
|
+
def detect_type(output)
|
24
|
+
case output
|
25
|
+
when Date, Time, DateTime
|
26
|
+
:time
|
27
|
+
when Numeric, /^\d+$/
|
28
|
+
:numeric
|
29
|
+
when /^\//, ->(string) { uri? string }
|
30
|
+
:audio
|
31
|
+
else
|
32
|
+
:text
|
42
33
|
end
|
34
|
+
end
|
43
35
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
return args[0] if args.size == 1 && args[0].is_a?(RubySpeech::SSML::Speak)
|
53
|
-
argument, options = args.flatten
|
54
|
-
options ||= {}
|
55
|
-
type = detect_type argument
|
56
|
-
send "ssml_for_#{type}", argument, options
|
57
|
-
end
|
36
|
+
def uri?(string)
|
37
|
+
uri = URI.parse string
|
38
|
+
!!uri.scheme
|
39
|
+
rescue URI::BadURIError
|
40
|
+
false
|
41
|
+
rescue URI::InvalidURIError
|
42
|
+
false
|
43
|
+
end
|
58
44
|
|
59
|
-
|
60
|
-
|
61
|
-
|
45
|
+
#
|
46
|
+
# Generates SSML for the argument and options passed, using automatic detection
|
47
|
+
# Directly returns the argument if it is already an SSML document
|
48
|
+
#
|
49
|
+
# @param [String, Hash, RubySpeech::SSML::Speak] the argument with options as accepted by the play_ methods, or an SSML document
|
50
|
+
# @return [RubySpeech::SSML::Speak] an SSML document
|
51
|
+
#
|
52
|
+
def ssml_for(*args)
|
53
|
+
return args[0] if args.size == 1 && args[0].is_a?(RubySpeech::SSML::Speak)
|
54
|
+
argument, options = args.flatten
|
55
|
+
options ||= {}
|
56
|
+
type = detect_type argument
|
57
|
+
send "ssml_for_#{type}", argument, options
|
58
|
+
end
|
62
59
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
when Time then 'time'
|
67
|
-
end
|
60
|
+
def ssml_for_text(argument, options = {})
|
61
|
+
RubySpeech::SSML.draw { argument }
|
62
|
+
end
|
68
63
|
|
69
|
-
|
70
|
-
|
64
|
+
def ssml_for_time(argument, options = {})
|
65
|
+
interpretation = case argument
|
66
|
+
when Date then 'date'
|
67
|
+
when Time then 'time'
|
68
|
+
end
|
71
69
|
|
72
|
-
|
70
|
+
format = options.delete :format
|
71
|
+
strftime = options.delete :strftime
|
73
72
|
|
74
|
-
|
75
|
-
|
76
|
-
|
73
|
+
time_to_say = strftime ? argument.strftime(strftime) : argument.to_s
|
74
|
+
|
75
|
+
RubySpeech::SSML.draw do
|
76
|
+
say_as(:interpret_as => interpretation, :format => format) { time_to_say }
|
77
77
|
end
|
78
|
+
end
|
78
79
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
80
|
+
def ssml_for_numeric(argument, options = {})
|
81
|
+
RubySpeech::SSML.draw do
|
82
|
+
say_as(:interpret_as => 'cardinal') { argument.to_s }
|
83
83
|
end
|
84
|
+
end
|
84
85
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
end
|
86
|
+
def ssml_for_audio(argument, options = {})
|
87
|
+
fallback = (options || {}).delete :fallback
|
88
|
+
RubySpeech::SSML.draw do
|
89
|
+
audio(:src => argument) { fallback }
|
90
90
|
end
|
91
91
|
end
|
92
92
|
end
|
@@ -5,6 +5,93 @@ module Adhearsion
|
|
5
5
|
module Record
|
6
6
|
RecordError = Class.new StandardError # Represents failure to record such as when a file cannot be written.
|
7
7
|
|
8
|
+
#
|
9
|
+
# Handle a recording
|
10
|
+
#
|
11
|
+
# @param [Adhearsion::CallController] controller on which to execute the recording
|
12
|
+
# @param [Hash] options
|
13
|
+
# @option options [Boolean, Optional] :async Execute asynchronously. Defaults to false
|
14
|
+
# @option options [Boolean, Optional] :start_beep Indicates whether subsequent record will be preceded with a beep. Default is true.
|
15
|
+
# @option options [Boolean, Optional] :start_paused Whether subsequent record will start in PAUSE mode. Default is false.
|
16
|
+
# @option options [String, Optional] :max_duration Indicates the maximum duration (seconds) for a recording.
|
17
|
+
# @option options [String, Optional] :format File format used during recording.
|
18
|
+
# @option options [String, Optional] :initial_timeout Controls how long (seconds) the recognizer should wait after the end of the prompt for the caller to speak before sending a Recorder event.
|
19
|
+
# @option options [String, Optional] :final_timeout Controls the length (seconds) of a period of silence after callers have spoken to conclude they finished.
|
20
|
+
# @option options [Boolean, Optional] :interruptible Allows the recording to be terminated by any single DTMF key, default is false
|
21
|
+
#
|
22
|
+
class Recorder
|
23
|
+
attr_accessor :record_component, :stopper_component
|
24
|
+
|
25
|
+
def initialize(controller, options = {})
|
26
|
+
@controller = controller
|
27
|
+
|
28
|
+
options = prep_options options
|
29
|
+
|
30
|
+
@async = options.delete :async
|
31
|
+
|
32
|
+
@stopper_component = options.delete(:interruptible) ? setup_stopper : nil
|
33
|
+
@record_component = Punchblock::Component::Record.new options
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Execute the recorder
|
38
|
+
#
|
39
|
+
# @return nil
|
40
|
+
#
|
41
|
+
def run
|
42
|
+
execute_stopper
|
43
|
+
execute_recording
|
44
|
+
terminate_stopper
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Set a callback to be executed when recording completes
|
50
|
+
#
|
51
|
+
# @yield [Punchblock::Event::Complete] the complete Event for the recording
|
52
|
+
#
|
53
|
+
def handle_record_completion(&block)
|
54
|
+
@record_component.register_event_handler Punchblock::Event::Complete, &block
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def setup_stopper
|
60
|
+
@stopper_component = Punchblock::Component::Input.new :mode => :dtmf,
|
61
|
+
:grammar => {
|
62
|
+
:value => @controller.grammar_accept('0123456789#*')
|
63
|
+
}
|
64
|
+
@stopper_component.register_event_handler Punchblock::Event::Complete do |event|
|
65
|
+
@record_component.stop! unless @record_component.complete?
|
66
|
+
end
|
67
|
+
@stopper_component
|
68
|
+
end
|
69
|
+
|
70
|
+
def execute_stopper
|
71
|
+
@controller.write_and_await_response @stopper_component if @stopper_component
|
72
|
+
end
|
73
|
+
|
74
|
+
def execute_recording
|
75
|
+
if @async
|
76
|
+
@controller.write_and_await_response @record_component
|
77
|
+
else
|
78
|
+
@controller.execute_component_and_await_completion @record_component
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def terminate_stopper
|
83
|
+
@stopper_component.stop! if @stopper_component && !@stopper_component.complete?
|
84
|
+
end
|
85
|
+
|
86
|
+
def prep_options(opts)
|
87
|
+
opts.dup.tap do |options|
|
88
|
+
[:max_duration, :initial_timeout, :final_timeout].each do |k|
|
89
|
+
options[k] = options[k] * 1000 if options[k]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
8
95
|
#
|
9
96
|
# Start a recording
|
10
97
|
#
|
@@ -23,7 +110,6 @@ module Adhearsion
|
|
23
110
|
# @option options [Boolean, Optional] :start_paused Whether subsequent record will start in PAUSE mode. Default is false.
|
24
111
|
# @option options [String, Optional] :max_duration Indicates the maximum duration (seconds) for a recording.
|
25
112
|
# @option options [String, Optional] :format File format used during recording.
|
26
|
-
# @option options [String, Optional] :format File format used during recording.
|
27
113
|
# @option options [String, Optional] :initial_timeout Controls how long (seconds) the recognizer should wait after the end of the prompt for the caller to speak before sending a Recorder event.
|
28
114
|
# @option options [String, Optional] :final_timeout Controls the length (seconds) of a period of silence after callers have spoken to conclude they finished.
|
29
115
|
# @option options [Boolean, Optional] :interruptible Allows the recording to be terminated by any single DTMF key, default is false
|
@@ -31,37 +117,14 @@ module Adhearsion
|
|
31
117
|
# @return Punchblock::Component::Record
|
32
118
|
#
|
33
119
|
def record(options = {})
|
34
|
-
|
35
|
-
interruptible = options.delete :interruptible
|
36
|
-
interrupt_key = '0123456789#*'
|
37
|
-
stopper_component = nil
|
38
|
-
[:max_duration, :initial_timeout, :final_timeout].each do |k|
|
39
|
-
options[k] = options[k].to_i * 1000 if options[k]
|
40
|
-
end
|
120
|
+
recorder = Recorder.new self, options
|
41
121
|
|
42
|
-
|
43
|
-
component.register_event_handler Punchblock::Event::Complete do |event|
|
122
|
+
recorder.handle_record_completion do |event|
|
44
123
|
catching_standard_errors { yield event if block_given? }
|
45
124
|
end
|
46
125
|
|
47
|
-
|
48
|
-
|
49
|
-
:grammar => {
|
50
|
-
:value => grammar_accept(interrupt_key)
|
51
|
-
}
|
52
|
-
stopper_component.register_event_handler Punchblock::Event::Complete do |event|
|
53
|
-
component.stop! unless component.complete?
|
54
|
-
end
|
55
|
-
write_and_await_response stopper_component
|
56
|
-
end
|
57
|
-
|
58
|
-
if async
|
59
|
-
write_and_await_response component
|
60
|
-
else
|
61
|
-
execute_component_and_await_completion component
|
62
|
-
end
|
63
|
-
stopper_component.stop! if stopper_component && stopper_component.executing?
|
64
|
-
component
|
126
|
+
recorder.run
|
127
|
+
recorder.record_component
|
65
128
|
end
|
66
129
|
end
|
67
130
|
end
|
@@ -44,16 +44,23 @@ module Adhearsion
|
|
44
44
|
end
|
45
45
|
|
46
46
|
#
|
47
|
-
# Parses a
|
47
|
+
# Parses a DTMF tone string
|
48
48
|
#
|
49
49
|
# @param [String] the tone string to be parsed
|
50
|
-
# @return [String] the
|
50
|
+
# @return [String] the digits/*/# without any separation
|
51
51
|
#
|
52
52
|
# @private
|
53
53
|
#
|
54
|
-
def
|
55
|
-
return if
|
56
|
-
|
54
|
+
def parse_dtmf(dtmf)
|
55
|
+
return if dtmf.nil?
|
56
|
+
dtmf.split(' ').inject '' do |final, digit|
|
57
|
+
final << parse_dtmf_digit(digit)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @private
|
62
|
+
def parse_dtmf_digit(digit)
|
63
|
+
case tone = digit.split('-').last
|
57
64
|
when 'star'
|
58
65
|
'*'
|
59
66
|
when 'pound'
|
data/lib/adhearsion/calls.rb
CHANGED
@@ -45,10 +45,10 @@ module Adhearsion
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def call_died(call, reason)
|
48
|
-
return unless reason
|
49
48
|
catching_standard_errors do
|
50
49
|
call_id = key call
|
51
50
|
remove_inactive_call call
|
51
|
+
return unless reason
|
52
52
|
PunchblockPlugin.client.execute_command Punchblock::Command::Hangup.new, :async => true, :call_id => call_id
|
53
53
|
end
|
54
54
|
end
|
@@ -52,6 +52,10 @@ module Adhearsion
|
|
52
52
|
A log formatter to apply to all active outputters. If nil, the Adhearsion default formatter will be used.
|
53
53
|
__
|
54
54
|
}
|
55
|
+
|
56
|
+
after_hangup_lifetime 30, :transform => Proc.new { |v| v.to_i }, :desc => <<-__
|
57
|
+
Lifetime of a call after it has hung up
|
58
|
+
__
|
55
59
|
end
|
56
60
|
|
57
61
|
Loquacious::Configuration.for :platform, &block if block_given?
|
data/lib/adhearsion/events.rb
CHANGED
@@ -4,8 +4,8 @@ module Adhearsion
|
|
4
4
|
module Generators
|
5
5
|
class AppGenerator < Generator
|
6
6
|
|
7
|
-
BASEDIRS
|
8
|
-
EMPTYDIRS = %w( spec/
|
7
|
+
BASEDIRS = %w( config lib script spec )
|
8
|
+
EMPTYDIRS = %w( spec/support )
|
9
9
|
|
10
10
|
def setup_project
|
11
11
|
self.destination_root = @generator_name
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe SimonGame do
|
6
|
+
|
7
|
+
let(:example_response) { OpenStruct.new(:response => "5") }
|
8
|
+
let(:example_number) { "5" }
|
9
|
+
let(:long_response) { OpenStruct.new(:response => "55555") }
|
10
|
+
let(:long_number) { "55555" }
|
11
|
+
|
12
|
+
let(:mock_call) { mock 'Call' }
|
13
|
+
subject { SimonGame.new(mock_call) }
|
14
|
+
|
15
|
+
describe "#random_number" do
|
16
|
+
|
17
|
+
before { subject.stub!(:rand).and_return(example_number) }
|
18
|
+
|
19
|
+
it "generates a random number" do
|
20
|
+
subject.random_number.should eq example_number
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#update_number" do
|
25
|
+
|
26
|
+
before { subject.number = "123" }
|
27
|
+
before { subject.stub!(:random_number).and_return "4" }
|
28
|
+
|
29
|
+
it "adds a digit to the end of the number" do
|
30
|
+
subject.update_number
|
31
|
+
subject.number.should eq "1234"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#collect_attempt" do
|
36
|
+
|
37
|
+
context "when the @number is 1 digit long" do
|
38
|
+
|
39
|
+
before { subject.number = "3" }
|
40
|
+
|
41
|
+
it "asks for a 1 digits number" do
|
42
|
+
subject.should_receive(:ask).with("3", :limit => 1).and_return(example_response)
|
43
|
+
subject.collect_attempt
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when the @number is 5 digits long" do
|
48
|
+
|
49
|
+
before { subject.number = long_number }
|
50
|
+
|
51
|
+
it "asks for a 5 digits number" do
|
52
|
+
subject.should_receive(:ask).with(long_number, :limit => 5).and_return(long_response)
|
53
|
+
subject.collect_attempt
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "sets @attempt" do
|
58
|
+
|
59
|
+
before { subject.number = "12345" }
|
60
|
+
|
61
|
+
it "based on the user's response" do
|
62
|
+
subject.should_receive(:ask).with("12345", :limit => 5).and_return(long_response)
|
63
|
+
subject.collect_attempt
|
64
|
+
subject.attempt.should eq long_number
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#attempt_correct?" do
|
70
|
+
|
71
|
+
before { subject.number = "7" }
|
72
|
+
|
73
|
+
context "with a good attempt" do
|
74
|
+
|
75
|
+
before { subject.attempt = "7" }
|
76
|
+
|
77
|
+
it "returns true" do
|
78
|
+
subject.attempt_correct?.should be_true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "with a bad attempt" do
|
83
|
+
|
84
|
+
before { subject.attempt = "9" }
|
85
|
+
|
86
|
+
it "returns true" do
|
87
|
+
subject.attempt_correct?.should be_false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#verify_attempt" do
|
93
|
+
context "when the user is a good guesser" do
|
94
|
+
|
95
|
+
before { subject.stub!(:attempt_correct?).and_return true }
|
96
|
+
|
97
|
+
it "congradulates them" do
|
98
|
+
subject.should_receive(:speak).with('good')
|
99
|
+
subject.verify_attempt
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context "when the user guesses wrong" do
|
104
|
+
|
105
|
+
before { subject.number = "12345" }
|
106
|
+
before { subject.attempt = "12346" }
|
107
|
+
|
108
|
+
it "congradulates them" do
|
109
|
+
subject.should_receive(:speak).with('4 times wrong, try again smarty')
|
110
|
+
subject.verify_attempt
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "#reset" do
|
116
|
+
|
117
|
+
before { subject.reset }
|
118
|
+
|
119
|
+
it "sets @number" do
|
120
|
+
subject.number.should eq ''
|
121
|
+
end
|
122
|
+
|
123
|
+
it "sets @attempt" do
|
124
|
+
subject.attempt.should eq ''
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "#run" do
|
129
|
+
it "loops the loop" do
|
130
|
+
subject.should_receive :answer
|
131
|
+
subject.should_receive :reset
|
132
|
+
|
133
|
+
subject.should_receive :update_number
|
134
|
+
subject.should_receive :collect_attempt
|
135
|
+
subject.should_receive(:verify_attempt).and_throw :rspec_loop_stop
|
136
|
+
|
137
|
+
catch :rspec_loop_stop do
|
138
|
+
subject.run
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|