tobacco 0.0.1

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/lib/tobacco.rb ADDED
@@ -0,0 +1,46 @@
1
+ # A convenience module to gain access to
2
+ # configuration options
3
+ #
4
+ require 'open-uri'
5
+
6
+ module Tobacco
7
+
8
+ # Default options in the event no configuration
9
+ # file is created
10
+ #
11
+ @base_path = '/tmp/published_content'
12
+ @published_host = 'http://localhost:3000'
13
+ @content_method = :content
14
+ @content_url_method = :content_url
15
+ @output_filepath_method = :output_filepath
16
+
17
+ class << self
18
+ attr_accessor :base_path,
19
+ :published_host,
20
+ :content_method,
21
+ :content_url_method,
22
+ :output_filepath_method
23
+ end
24
+
25
+ def self.configure
26
+ yield self
27
+ end
28
+
29
+ def self.log(msg)
30
+ msg = "LOGGING: #{msg}"
31
+
32
+ if defined? Rails
33
+ Rails.logger.info(msg)
34
+ else
35
+ puts msg
36
+ end
37
+ end
38
+ end
39
+
40
+ require 'tobacco/burnout.rb'
41
+ require 'tobacco/smoker'
42
+ require 'tobacco/roller'
43
+ require 'tobacco/inhaler'
44
+ require 'tobacco/exhaler'
45
+ require 'tobacco/error'
46
+
@@ -0,0 +1,18 @@
1
+ require 'tobacco'
2
+ require 'pry'
3
+ require 'vcr'
4
+
5
+ RSpec.configure do |config|
6
+ config.treat_symbols_as_metadata_keys_with_true_values = true
7
+ config.run_all_when_everything_filtered = true
8
+ config.filter_run :focus
9
+ config.order = 'random'
10
+
11
+ VCR.configure do |c|
12
+ c.cassette_library_dir = 'spec/support/vcr_cassettes'
13
+ c.hook_into :fakeweb
14
+ c.allow_http_connections_when_no_cassette = true
15
+ end
16
+
17
+ config.extend VCR::RSpec::Macros
18
+ end
@@ -0,0 +1,72 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: http://www.iana.org/domains/example/
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ accept:
11
+ - ! '*/*'
12
+ user-agent:
13
+ - Ruby
14
+ response:
15
+ status:
16
+ code: 200
17
+ message: OK
18
+ headers:
19
+ date:
20
+ - Sun, 30 Sep 2012 02:45:14 GMT
21
+ server:
22
+ - Apache/2.2.3 (CentOS)
23
+ last-modified:
24
+ - Wed, 09 Feb 2011 17:13:15 GMT
25
+ vary:
26
+ - Accept-Encoding
27
+ connection:
28
+ - close
29
+ transfer-encoding:
30
+ - chunked
31
+ content-type:
32
+ - text/html; charset=UTF-8
33
+ body:
34
+ encoding: US-ASCII
35
+ string: ! "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"
36
+ \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n\t<title>IANA
37
+ &mdash; Example domains</title>\n\t<!-- start common-head -->\n\t<meta http-equiv=\"Content-type\"
38
+ content=\"text/html; charset=utf-8\" />\n\t<link rel=\"stylesheet\" type=\"text/css\"
39
+ href=\"/_css/2008.1/reset-fonts-grids.css\" />\n\t<link rel=\"stylesheet\"
40
+ type=\"text/css\" media=\"screen\" href=\"/_css/2008.1/screen.css\" />\n\t<link
41
+ rel=\"stylesheet\" type=\"text/css\" media=\"print\" href=\"/_css/2008.1/print.css\"
42
+ />\n\t<link rel=\"shortcut icon\" type=\"image/ico\" href=\"/favicon.ico\"
43
+ />\n\t<script type=\"text/javascript\" src=\"/_js/prototype.js\"></script>\n\t<script
44
+ type=\"text/javascript\" src=\"/_js/corners.js\"></script>\n\t<script type=\"text/javascript\"
45
+ src=\"/_js/common.js\"></script>\n\t<!-- end common-head -->\n\n</head>\n<body>\n\t<!--
46
+ start common-bodyhead -->\n\t<div id=\"header-frame\">\n\t<div id=\"header\">\n\t<div
47
+ id=\"header-logo\"><a href=\"/\"><img src=\"/_img/iana-logo-pageheader.png\"
48
+ alt=\"Homepage\"/></a></div>\n\t<div id=\"header-nav\">\n\t<ul>\n\t<li><a
49
+ href=\"/domains/\">Domains</a></li>\n\t<li><a href=\"/numbers/\">Numbers</a></li>\n\t<li><a
50
+ href=\"/protocols/\">Protocols</a></li>\n\t<li><a href=\"/about/\">About IANA</a></li>\n\t</ul>\n\t</div>\n\t</div>\n\t</div>\n\n\t<div
51
+ id=\"body-container\">\n\t<div id=\"body\">\n\t<!-- end common-bodyhead -->\n\n\n\t<h1>Example
52
+ Domains</h1>\n\n\t<p>As described in <a href=\"/go/rfc2606\">RFC 2606</a>,\n\twe
53
+ maintain a number of domains such as EXAMPLE.COM and EXAMPLE.ORG\n\tfor documentation
54
+ purposes. These domains may be used as illustrative\n\texamples in documents
55
+ without prior coordination with us. They are \n\tnot available for registration.</p>\n\n\t
56
+ <!-- start common-bodytail -->\n\t</div>\n\t</div>\n\n\t<div id=\"footer-frame\">\n\t<div
57
+ id=\"footer\">\n\n\n\t<table width=100%>\n\t<tr>\n\t\t<td id=\"iana-footer-first\"><b><a
58
+ href=\"/about/\">About</a></b><br/>\n <a href=\"/about/presentations/\">Presentations</a><br/>\n
59
+ \ <a href=\"/about/performance/\">Performance</a><br/>\n\t\t<a
60
+ href=\"/reports/\">Reports</a><br/>\n </td>\n\n\t\t<td><b><a
61
+ href=\"/domains/\">Domains</a></b><br/>\n\t\t<a href=\"/domains/root/\">Root
62
+ Zone</a><br/>\n\t\t<a href=\"/domains/int/\">.INT</a><br/>\n\t\t<a href=\"/domains/arpa/\">.ARPA</a><br/>\n\t\t<a
63
+ href=\"/domains/idn-tables/\">IDN Repository</a></td>\n\n\t\t<td><b><a href=\"/protocols/\">Protocols</a></b><br/>\n\t\t<br/>\n\t\t<b><a
64
+ href=\"/numbers/\">Number Resources</a></b><br/>\n\t\t<a href=\"/abuse/\">Abuse
65
+ Information</a></td>\n\n\t\t<td id=\"iana-footer-icann\"><img src=\"/_img/icann-logo-micro.png\"><br/>IANA
66
+ is operated by the<br/><a href=\"http://www.icann.org/\">Internet Corporation
67
+ for Assigned Names and Numbers</a></td>\n\t</tr>\n\t</table>\n\n<div id=\"footer-beta-feedback\">\n
68
+ \ <p>Please direct general feedback regarding IANA to <a href=\"mailto:iana@iana.org?subject=General%20website%20feedback\">iana@iana.org</a>.</p>\n
69
+ \ </div>\n\n\t</div>\n\t</div>\n\t<!-- end common-bodytail -->\n\n\n</body>\n</html>\n"
70
+ http_version: '1.1'
71
+ recorded_at: Sun, 30 Sep 2012 02:45:14 GMT
72
+ recorded_with: VCR 2.2.5
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Tobacco::Burnout do
4
+ subject { Tobacco::Burnout }
5
+
6
+ let(:call_count) { 0 }
7
+ let(:max_timeouts) { 0 }
8
+
9
+ let(:untimely_block) {
10
+ ->(call_count, max_timeouts) {
11
+ if call_count < max_timeouts
12
+ call_count += 1
13
+
14
+ raise Timeout::Error
15
+ else
16
+ return "an important value"
17
+ end
18
+ }
19
+ }
20
+
21
+ context "when the attempted block times out exactly X times" do
22
+ before do
23
+ max_timeouts = 5
24
+ end
25
+
26
+ it "raises nothing" do
27
+ expect {
28
+ subject.try(5) { untimely_block.call(call_count, max_timeouts) }
29
+ }.to_not raise_error
30
+ end
31
+
32
+ it "returns whatever the block returned" do
33
+ subject.try(5) { untimely_block.call(call_count, max_timeouts) }.should == "an important value"
34
+ end
35
+ end
36
+
37
+ context "when the attempted block times out more than X times" do
38
+ before do
39
+ max_timeouts = 3
40
+ end
41
+
42
+ it "raises a MaximumAttemptsExceeded exception" do
43
+ expect {
44
+ subject.try(2) do
45
+ untimely_block.call(0, 4)
46
+ end
47
+ }.to raise_error(Tobacco::Burnout::MaximumAttemptsExceeded, /more than 2 time/)
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+ require 'tobacco'
3
+
4
+ shared_examples_for "attributes set" do
5
+ it 'msg is set' do
6
+ error.msg.should == msg
7
+ end
8
+
9
+ it 'filepath is set' do
10
+ error.filepath.should == filepath
11
+ end
12
+
13
+ it 'content is set' do
14
+ error.content.should == content
15
+ end
16
+
17
+ it 'object is an error object' do
18
+ error.object.message.should == error_object.message
19
+ end
20
+ end
21
+
22
+ describe Tobacco::Error do
23
+ subject { Tobacco::Error }
24
+
25
+ describe '#new' do
26
+ context 'sets attributes during initialize' do
27
+ let(:msg) { 'Error Writing' }
28
+ let(:filepath) { '/users/somepath/index.html' }
29
+ let(:content) { '<h1>Page Title</h1>'}
30
+ let(:error_object) { RuntimeError.new("Can't do that!") }
31
+
32
+ let(:error) do
33
+ subject.new(
34
+ msg: msg,
35
+ filepath: filepath,
36
+ content: content,
37
+ object: error_object
38
+ )
39
+ end
40
+
41
+ it_behaves_like "attributes set"
42
+ end
43
+
44
+ context 'allows setting attributes directly' do
45
+ let(:msg) { 'Error Writing' }
46
+ let(:filepath) { '/users/no_permissions_here/index.html' }
47
+ let(:content) { '<h1>Overdone</h1>'}
48
+ let(:error_object) { Exception.new("I know this is crazy. Not sure what just happened. Try it again and the error may not happen... maybe") }
49
+ let(:error) { subject.new }
50
+
51
+ before do
52
+ error.msg = msg
53
+ error.filepath = filepath
54
+ error.content = content
55
+ error.object = error_object
56
+ end
57
+
58
+ it_behaves_like "attributes set"
59
+ end
60
+ end
61
+
62
+ describe '#to_a' do
63
+ context 'when destructuring using *splat' do
64
+ let(:msg) { 'Error Writing' }
65
+ let(:filepath) { '/users/somepath/index.html' }
66
+ let(:content) { '<h1>Page Title</h1>'}
67
+ let(:error_object) { RuntimeError.new("Can't do that!") }
68
+
69
+ let(:expected_array) { [ msg, filepath, content, error_object ] }
70
+
71
+ let(:error) do
72
+ subject.new(
73
+ msg: msg,
74
+ filepath: filepath,
75
+ content: content,
76
+ object: error_object
77
+ )
78
+ end
79
+
80
+ it 'sets the array of variables' do
81
+ splatted = *error
82
+ expected_array.should == splatted
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+ require 'tobacco'
3
+
4
+ describe Tobacco::Exhaler do
5
+
6
+ subject { Tobacco::Exhaler }
7
+
8
+ let(:exhaler) { subject.new(content, filepath) }
9
+
10
+ describe '#new' do
11
+ context 'when passing arguments' do
12
+ let(:content) { 'File content' }
13
+ let(:filepath) { '/users/someuser/work/index.html' }
14
+
15
+ it 'sets the content' do
16
+ exhaler.filepath.should == filepath
17
+ end
18
+
19
+ it 'sets the filepath' do
20
+ exhaler.content.should == content
21
+ end
22
+ end
23
+
24
+ context 'when setting attributes after creation' do
25
+ let(:exhaler) { Tobacco::Exhaler.new }
26
+
27
+ it 'sets the content' do
28
+ exhaler.content = 'new content'
29
+
30
+ exhaler.content.should == 'new content'
31
+ end
32
+
33
+ it 'sets the filepath' do
34
+ exhaler.filepath = '/var/other'
35
+
36
+ exhaler.filepath.should == '/var/other'
37
+ end
38
+ end
39
+ end
40
+
41
+ describe 'file operations' do
42
+ let(:filepath) { '/tmp/base_dir/index.html' }
43
+ let(:dir) { '/tmp/base_dir/' }
44
+ let(:content) { '<h1>Page Title</h1>' }
45
+
46
+ after { FileUtils.rm_rf(dir) }
47
+
48
+ describe '#create_directory' do
49
+ it 'creates a directory based on a filepath' do
50
+ exhaler.create_directory
51
+
52
+ File.exists?(dir).should be_true
53
+ end
54
+ end
55
+
56
+ describe '#write!' do
57
+ it 'writes content to a file' do
58
+ exhaler.write!
59
+ File.read(filepath).should == content
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+ require 'tobacco'
3
+
4
+ describe Tobacco::Inhaler do
5
+
6
+ subject { Tobacco::Inhaler.new(url) }
7
+
8
+ describe '#new' do
9
+ let!(:url) { 'http://google.com/videos' }
10
+
11
+ it 'sets the url' do
12
+ subject.url.should == url
13
+ end
14
+ end
15
+
16
+ describe '#read' do
17
+
18
+ context 'when reading a url succeeds' do
19
+ let!(:url) { 'http://www.iana.org/domains/example/' }
20
+
21
+ before { VCR.insert_cassette('url_content') }
22
+ after { VCR.eject_cassette() }
23
+
24
+ it 'reads content from a url' do
25
+ subject.read
26
+ end
27
+ end
28
+
29
+ context 'when reading a url fails' do
30
+ let!(:url) { 'http://somehost' }
31
+
32
+ it 'errors on reading' do
33
+ expect { subject.read }.to raise_error(Tobacco::Burnout::MaximumAttemptsExceeded)
34
+ end
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,108 @@
1
+ require 'spec_helper'
2
+ require 'tobacco'
3
+
4
+ describe Tobacco::Roller do
5
+
6
+ context 'paths' do
7
+ before do
8
+ Tobacco.configure do |config|
9
+ config.published_host = 'http://localhost:3000'
10
+ config.base_path = 'published_content'
11
+ config.content_method = :content
12
+ config.content_url_method = :content_url
13
+ config.output_filepath_method = :output_filepath
14
+ end
15
+ end
16
+
17
+ subject { Tobacco::Roller.new(smoker) }
18
+
19
+ context 'when using default settings' do
20
+ let(:smoker) { double('smoker') }
21
+ let(:expected_output_filepath) { 'published_content/1/video/245' }
22
+
23
+ describe '#output_filepath' do
24
+ it 'builds a path when given array of options' do
25
+ smoker.stub(:output_filepath) { [ '1', 'video', '245' ] }
26
+
27
+ subject.output_filepath.should == expected_output_filepath
28
+ end
29
+
30
+ it 'builds a path when given a string' do
31
+ smoker.stub(:output_filepath) { '1/video/245' }
32
+
33
+ subject.output_filepath.should == expected_output_filepath
34
+ end
35
+ end
36
+
37
+ describe '#content_url' do
38
+ context 'when prepended with a forward slash' do
39
+ let(:expected_content_url) { 'http://localhost:3000/publisher/video/245' }
40
+
41
+ it 'builds a content url' do
42
+ smoker.stub(:content_url) { '/publisher/video/245' }
43
+
44
+ subject.content_url.should == expected_content_url
45
+ end
46
+ end
47
+
48
+ context 'when not prepended with a forward slash' do
49
+ let(:expected_content_url) { 'http://localhost:3000/publisher/video/245' }
50
+
51
+ it 'builds a content url' do
52
+ smoker.stub(:content_url) { 'publisher/video/245' }
53
+
54
+ subject.content_url.should == expected_content_url
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+
61
+ context 'when overriding default settings' do
62
+ let(:smoker) { double('smoker') }
63
+
64
+ before do
65
+ Tobacco.configure do |config|
66
+ config.published_host = 'http://localhost:5000'
67
+ config.base_path = 'pub_content'
68
+ config.content_method = :data
69
+ config.content_url_method = :url
70
+ config.output_filepath_method = :fpath
71
+ end
72
+ end
73
+
74
+ let(:smoker) { double('smoker') }
75
+ let(:expected_output_filepath) { 'pub_content/1/video/245' }
76
+
77
+ describe '#output_filepath' do
78
+ it 'builds an ouput filepath' do
79
+ smoker.stub(:fpath) { [ '1', 'video', '245' ] }
80
+
81
+ subject.output_filepath.should == expected_output_filepath
82
+ end
83
+ end
84
+
85
+ describe '#content_url' do
86
+ context 'when prepended with a forward slash' do
87
+ let(:expected_content_url) { 'http://localhost:5000/publisher/video/245' }
88
+
89
+ it 'builds a content url' do
90
+ smoker.stub(:url) { '/publisher/video/245' }
91
+
92
+ subject.content_url.should == expected_content_url
93
+ end
94
+ end
95
+
96
+ context 'when not prepended with a forward slash' do
97
+ let(:expected_content_url) { 'http://localhost:5000/publisher/video/245' }
98
+
99
+ it 'builds a content url' do
100
+ smoker.stub(:url) { 'publisher/video/245' }
101
+
102
+ subject.content_url.should == expected_content_url
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,204 @@
1
+ require 'spec_helper'
2
+ require 'tobacco'
3
+
4
+
5
+ describe Tobacco::Smoker do
6
+ before do
7
+ Tobacco.configure do |config|
8
+ config.published_host = 'http://localhost:3000'
9
+ config.base_path = '/tmp/published_content'
10
+ config.content_method = :content
11
+ config.content_url_method = :content_url
12
+ config.output_filepath_method = :output_filepath
13
+ end
14
+ end
15
+
16
+ let(:smoker) { mock('smoker') }
17
+
18
+ subject { Tobacco::Smoker.new(smoker) }
19
+
20
+ context '#write!' do
21
+
22
+ context 'when content is empty' do
23
+ let(:inhaler) { mock('inhaler', read: '') }
24
+ let(:filepath) { mock('roller', content_url: '/video', filepath: '/user') }
25
+
26
+ before do
27
+ Tobacco::Inhaler.should_receive(:new).and_return(inhaler)
28
+ subject.file_path_generator = filepath
29
+ end
30
+
31
+ it 'does not attempt a write' do
32
+ Tobacco::Exhaler.should_not_receive(:new)
33
+
34
+ subject.read
35
+ subject.write!
36
+ end
37
+ end
38
+
39
+ context 'content' do
40
+ let(:content) { 'Directly set content' }
41
+ let(:exhaler) { mock('exhaler', write!: true) }
42
+ let(:filepath) { mock('roller', content_url: '/video', filepath: '/user', output_filepath: '/desktop') }
43
+
44
+ before do
45
+ Tobacco::Exhaler.should_receive(:new).and_return(exhaler)
46
+ subject.file_path_generator = filepath
47
+ end
48
+
49
+ context 'when providing content directly' do
50
+
51
+ it 'allows setting content directly' do
52
+ exhaler.should_receive(:write!)
53
+ subject.content = content
54
+
55
+ subject.write!
56
+ end
57
+
58
+ it 'callback :on_success is called' do
59
+ smoker.should_receive(:before_write).with(content).and_return(content)
60
+ smoker.should_receive(:on_success).with(content)
61
+ subject.content = content
62
+
63
+ subject.write!
64
+ end
65
+ end
66
+
67
+ context 'when an error occurs during writing' do
68
+ let(:error) { raise RuntimeError.new('File Permission Error') }
69
+
70
+ before do
71
+ subject.file_path_generator = filepath
72
+ smoker.should_receive(:before_write).with(content).and_return(content)
73
+ exhaler.should_receive(:write!).and_return { error }
74
+ end
75
+
76
+ it 'calls the callback :on_write_error' do
77
+ smoker.should_receive(:on_write_error)
78
+ subject.content = content
79
+
80
+ subject.write!
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ describe '#modify_content_before_writing' do
87
+ let(:content) { '<h1>Summer Gear</h1>' }
88
+ let(:modified_content) { '<h1>Winter Gear</h1>' }
89
+
90
+ before do
91
+ smoker.stub(:before_write).and_return(modified_content)
92
+ end
93
+
94
+ it 'allows the smoker to modify content before writing' do
95
+ subject.content = content
96
+
97
+ subject.modify_content_before_writing.should == modified_content
98
+ end
99
+ end
100
+
101
+ describe '#generate_file_paths' do
102
+ it 'sets the file_path_generator' do
103
+ Tobacco::Roller.should_receive(:new).with(smoker)
104
+
105
+ subject.generate_file_paths
106
+ end
107
+ end
108
+
109
+ describe '#choose_reader' do
110
+ context 'when smoker provides the content' do
111
+ before { smoker.stub(:content) }
112
+
113
+ it 'uses the smoker for the content' do
114
+ subject.choose_reader
115
+ subject.reader.should == smoker
116
+ end
117
+ end
118
+
119
+ context 'when smoker does not provide content' do
120
+ let(:inhaler) { mock('inhaler', read: 'reader method') }
121
+ let(:filepath) { mock('roller', content_url: '/video') }
122
+
123
+ before do
124
+ Tobacco::Inhaler.should_receive(:new).and_return(inhaler)
125
+ subject.file_path_generator = filepath
126
+ end
127
+
128
+ it 'uses the smoker for the content' do
129
+ subject.choose_reader
130
+ subject.reader.should == inhaler
131
+ end
132
+ end
133
+ end
134
+
135
+ describe '#read_content' do
136
+ context 'when content is read from the smoker' do
137
+ let(:content) { 'Provided content' }
138
+
139
+ before { smoker.stub(:content).and_return(content) }
140
+
141
+ it 'should have the correct content' do
142
+ subject.choose_reader
143
+ subject.read_content
144
+
145
+ subject.content.should == content
146
+ end
147
+ end
148
+
149
+ context 'when content is read from url' do
150
+ let(:content) { 'Content from reading url' }
151
+ let(:inhaler) { mock('inhaler', read: content) }
152
+ let(:filepath) { mock('roller', content_url: '/video') }
153
+
154
+ before do
155
+ Tobacco::Inhaler.should_receive(:new).and_return(inhaler)
156
+ subject.file_path_generator = filepath
157
+ end
158
+
159
+ it 'should have the correct content' do
160
+ subject.choose_reader
161
+ subject.read_content
162
+
163
+ subject.content.should == content
164
+ end
165
+ end
166
+ end
167
+
168
+ describe '#read' do
169
+ context 'when content is empty' do
170
+ class Writer
171
+ attr_accessor :error
172
+ def on_read_error(error)
173
+ self.error = error
174
+ end
175
+ end
176
+
177
+ describe '#on_read_error' do
178
+ let(:inhaler) { mock('inhaler', read: '') }
179
+ let(:filepath) { mock('roller', content_url: '/video', filepath: '/file/path') }
180
+ let(:smoker) { Writer.new }
181
+
182
+ before do
183
+ Tobacco::Inhaler.should_receive(:new).and_return(inhaler)
184
+ subject.file_path_generator = filepath
185
+ end
186
+
187
+ it 'the callback is called on the smoker' do
188
+ subject.smoker.should_receive(:on_read_error)
189
+
190
+ subject.read
191
+ end
192
+
193
+ it 'has the correct message on the error object' do
194
+ subject.read
195
+
196
+ error = subject.smoker.error
197
+ message = error.object.message
198
+
199
+ message.should match(/No error encountered/)
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end