adhearsion 2.1.3 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|