ndd-url_checker 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,100 @@
1
+ require 'cod'
2
+ require 'logging'
3
+ require 'ndd/url_checker/abstract_url_checker'
4
+ require 'ndd/url_checker/blocking_url_checker'
5
+ require 'ndd/url_checker/status'
6
+
7
+ module NDD
8
+ module UrlChecker
9
+
10
+ # An URL checker using forks to parallelize processing. To be used with MRI.
11
+ # @author David DIDIER
12
+ class ForkedUrlChecker < AbstractUrlChecker
13
+
14
+ attr_reader :delegate
15
+ attr_reader :parallelism
16
+
17
+ # Create a new instance.
18
+ # @param [AbstractUrlChecker] delegate_checker defaults to {NDD::UrlChecker::BlockingUrlChecker}.
19
+ # @param [Fixnum] parallelism the number of processes.
20
+ def initialize(delegate_checker=nil, parallelism=10)
21
+ @logger = Logging.logger[self]
22
+ @delegate = delegate_checker || BlockingUrlChecker.new
23
+ @parallelism = parallelism
24
+ end
25
+
26
+ def check(*urls)
27
+ return delegate.check(*urls) if urls.size < 2
28
+ process(urls, :check)
29
+ end
30
+
31
+ def validate(*urls)
32
+ return delegate.validate(*urls) if urls.size < 2
33
+ process(urls, :validate)
34
+ end
35
+
36
+
37
+ private
38
+
39
+ def process(urls, method)
40
+ # for receiving results
41
+ result_pipe = Cod.pipe
42
+ # partition the URLs, but not too much :)
43
+ url_slices = partition(urls, [parallelism, urls.size].min)
44
+ # and distribute them among the workers
45
+ pids = url_slices.each_with_index.map do |url_slice, index|
46
+ fork { Worker.new(index, result_pipe, delegate).send(method, url_slice) }
47
+ end
48
+
49
+ # read back the results
50
+ results = urls.reduce({}) do |hash, _|
51
+ result = result_pipe.get
52
+ hash.merge!(result)
53
+ @logger.debug("Processed URLs #{hash.size}/#{urls.size}")
54
+ hash
55
+ end
56
+
57
+ # kill all the workers
58
+ pids.each { |pid| Process.kill(:TERM, pid) }
59
+
60
+ results
61
+ end
62
+
63
+ # Evenly distributes data into buckets. For example:
64
+ #  partition([1, 2, 3], 2) => [[1, 3], [2]]
65
+ #  partition([1, 2, 3, 4, 5, 6], 3) => [[1, 4], [2, 5], [3, 6]]
66
+ def partition(data, buckets)
67
+ Array.new.tap do |slices|
68
+ buckets.times.each { |_| slices << Array.new }
69
+ data.each_with_index { |element, index| slices[index % buckets] << element }
70
+ end
71
+ end
72
+
73
+
74
+ # A simple worker class processing URL one by one.
75
+ class Worker
76
+ def initialize(id, result_pipe, url_checker)
77
+ @logger = Logging.logger[self]
78
+ @id = id
79
+ @result_pipe = result_pipe
80
+ @url_checker = url_checker
81
+ end
82
+
83
+ def check(urls)
84
+ @logger.debug("[worker #{@id}] Checking #{urls.size} URLs")
85
+ urls.each do |url|
86
+ @result_pipe.put({url => @url_checker.check(url)})
87
+ end
88
+ end
89
+
90
+ def validate(urls)
91
+ @logger.debug("[worker #{@id}] Validating #{urls.size} URLs")
92
+ urls.each do |url|
93
+ @result_pipe.put({url => @url_checker.validate(url)})
94
+ end
95
+ end
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,45 @@
1
+ require 'logging'
2
+ require 'ndd/url_checker/abstract_url_checker'
3
+ require 'ndd/url_checker/forked_url_checker'
4
+ require 'ndd/url_checker/status'
5
+ require 'ndd/url_checker/threaded_url_checker'
6
+
7
+ module NDD
8
+ module UrlChecker
9
+
10
+ # Wraps an instance of ThreadedUrlChecker or ForkedUrlChecker
11
+ # depending of the underlying Ruby implementation.
12
+ # @author David DIDIER
13
+ class ParallelUrlChecker < AbstractUrlChecker
14
+
15
+ attr_reader :delegate
16
+
17
+ # Create a new instance.
18
+ # @param [AbstractUrlChecker] delegate_checker defaults to {NDD::UrlChecker::BlockingUrlChecker}.
19
+ # @param [Fixnum] parallelism the number of threads or processes.
20
+ def initialize(delegate_checker=nil, parallelism=10)
21
+ @logger = Logging.logger[self]
22
+
23
+ @logger.debug "Ruby engine is #{RUBY_ENGINE}"
24
+ if RUBY_ENGINE == 'jruby'
25
+ @logger.info 'Creating a threaded URL checker'
26
+ parallel_checker = ThreadedUrlChecker.new(delegate_checker, parallelism)
27
+ else
28
+ @logger.info 'Creating a forked URL checker'
29
+ parallel_checker = ForkedUrlChecker.new(delegate_checker, parallelism)
30
+ end
31
+
32
+ @delegate = parallel_checker
33
+ end
34
+
35
+ def check(*urls)
36
+ @delegate.check(*urls)
37
+ end
38
+
39
+ def validate(*urls)
40
+ @delegate.validate(*urls)
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,104 @@
1
+ module NDD
2
+ module UrlChecker
3
+
4
+ # The result of a URI test.
5
+ # @author David DIDIER
6
+ # @attr_reader code [String] the state
7
+ # @attr_reader error [String|StandardError] the last error if any
8
+ # @attr_reader uris [Array<String>] the list of requested URI
9
+ class Status
10
+
11
+ attr_reader :code
12
+ attr_reader :error
13
+ attr_reader :uris
14
+
15
+ # Create a new NDD::UrlChecker::Status instance in the unknown state.
16
+ # @param uri [String|URI::HTTP] the requested URI.
17
+ def initialize(uri)
18
+ @uris = [uri.to_s]
19
+ @code = :unknown
20
+ end
21
+
22
+ # Returns the first requested URI.
23
+ # @return [String] the first requested URI.
24
+ def uri
25
+ uris.first
26
+ end
27
+
28
+ # Note that the code :unknown is neither valid nor invalid.
29
+ # @return [Boolean] true if valid, false otherwise.
30
+ def valid?
31
+ VALID_CODES.include? @code
32
+ end
33
+
34
+ # Note that the code :unknown is neither valid nor invalid.
35
+ # @return [Boolean] true if invalid, false otherwise.
36
+ def invalid?
37
+ INVALID_CODES.include? @code
38
+ end
39
+
40
+ # @return [Boolean] true if redirected, false otherwise.
41
+ def redirected?
42
+ @code == :redirected
43
+ end
44
+
45
+
46
+ # When the URI is valid without any redirect.
47
+ # @return [NDD::UrlChecker::Status] self.
48
+ def direct
49
+ update_code(:direct, %i(unknown))
50
+ end
51
+
52
+ # When a generic error is raised.
53
+ # @param error [StandardError|String] the generic error.
54
+ # @return [NDD::UrlChecker::Status] self.
55
+ def failed(error)
56
+ @error = error
57
+ update_code(:failed, %i(unknown redirected))
58
+ end
59
+
60
+ # Adds a new URI to the redirected URI list.
61
+ # @param uri [String|URI::HTTP] the redirection URI.
62
+ # @return [NDD::UrlChecker::Status] self.
63
+ def redirected(uri)
64
+ @uris << uri.to_s
65
+ update_code(:redirected, %i(unknown redirected))
66
+ end
67
+
68
+ # When there are too many redirects.
69
+ # @return [NDD::UrlChecker::Status] self.
70
+ def too_many_redirects
71
+ update_code(:too_many_redirects, %i(unknown redirected))
72
+ end
73
+
74
+ # When the host cannot be resolved.
75
+ # @return [NDD::UrlChecker::Status] self.
76
+ def unknown_host
77
+ update_code(:unknown_host, %i(unknown redirected))
78
+ end
79
+
80
+ def to_s
81
+ self.inspect
82
+ end
83
+
84
+
85
+ private
86
+
87
+ VALID_CODES = %i(direct redirected).freeze
88
+ INVALID_CODES = %i(failed too_many_redirects unknown_host).freeze
89
+
90
+ # Updates the code if the transition is valid.
91
+ # @param code [Symbol] the new code.
92
+ # @param valid_source_codes [Array<Symbol>] the codes from which the transition can happen.
93
+ # @return [NDD::UrlChecker::Status] self.
94
+ def update_code(code, valid_source_codes)
95
+ unless valid_source_codes.include?(@code)
96
+ raise "Changing the status code from :#{@code} to :#{code} is forbidden"
97
+ end
98
+ @code = code
99
+ self
100
+ end
101
+
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,36 @@
1
+ require 'logging'
2
+ require 'ndd/url_checker/abstract_url_checker'
3
+ require 'ndd/url_checker/blocking_url_checker'
4
+ require 'ndd/url_checker/status'
5
+
6
+ module NDD
7
+ module UrlChecker
8
+
9
+ # An URL checker using threads to parallelize processing. Does not work on MRI.
10
+ # @author David DIDIER
11
+ class ThreadedUrlChecker < AbstractUrlChecker
12
+
13
+ attr_reader :delegate
14
+
15
+ # Create a new instance.
16
+ # @param [AbstractUrlChecker] delegate_checker defaults to {NDD::UrlChecker::BlockingUrlChecker}.
17
+ # @param [Fixnum] parallelism the number of threads.
18
+ def initialize(delegate_checker=nil, parallelism=10)
19
+ @logger = Logging.logger[self]
20
+ @delegate = delegate_checker || BlockingUrlChecker.new
21
+ @parallelism = parallelism
22
+ end
23
+
24
+ def check(*urls)
25
+ # delegate.check(*urls)
26
+ raise 'TODO'
27
+ end
28
+
29
+ def validate(*urls)
30
+ # delegate.validate(*urls)
31
+ raise 'TODO'
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe NDD::UrlChecker::AbstractUrlChecker do
4
+
5
+ context '#validate' do
6
+ it 'is not implemented' do
7
+ expect { subject.validate('http://www.valid.mock/') }.to raise_error /must be implemented/
8
+ end
9
+ end
10
+
11
+ context '#check' do
12
+ it 'is not implemented' do
13
+ expect { subject.check('http://www.valid.mock/') }.to raise_error /must be implemented/
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe NDD::UrlChecker::BlockingUrlChecker do
4
+
5
+ before(:all) do
6
+ # Logging.logger.root.level = :debug
7
+ end
8
+
9
+ it_behaves_like 'a single URL checker'
10
+ it_behaves_like 'a multiple URL checker'
11
+
12
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe NDD::UrlChecker::ForkedUrlChecker do
4
+
5
+ before(:all) do
6
+ # Logging.logger.root.level = :debug
7
+ end
8
+
9
+ it_behaves_like 'a single URL checker'
10
+ it_behaves_like 'a multiple URL checker', skip_verify = true
11
+
12
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe NDD::UrlChecker::ParallelUrlChecker do
4
+
5
+ before(:all) do
6
+ # Logging.logger.root.level = :debug
7
+ end
8
+
9
+ # ---------------------------------------------------------------------------------------------------------- MRI -----
10
+ context 'when running MRI' do
11
+
12
+ before(:all) do
13
+ @old_engine = RUBY_ENGINE
14
+ silence_warnings { RUBY_ENGINE = 'ruby' }
15
+ end
16
+
17
+ describe '#initialize' do
18
+ it 'delegates to a ForkedUrlChecker instance' do
19
+ expect(NDD::UrlChecker::ParallelUrlChecker.new.delegate).to be_a NDD::UrlChecker::ForkedUrlChecker
20
+ end
21
+ end
22
+
23
+ it_behaves_like 'a single URL checker'
24
+
25
+ after(:all) do
26
+ silence_warnings { RUBY_ENGINE = @old_engine }
27
+ end
28
+ end
29
+
30
+ # -------------------------------------------------------------------------------------------------------- JRuby -----
31
+ context 'when running JRuby' do
32
+ before(:all) do
33
+ @old_engine = RUBY_ENGINE
34
+ silence_warnings { RUBY_ENGINE = 'jruby' }
35
+ end
36
+
37
+ describe '#initialize' do
38
+ it 'delegates to a ThreadedUrlChecker instance' do
39
+ expect(NDD::UrlChecker::ParallelUrlChecker.new.delegate).to be_a NDD::UrlChecker::ThreadedUrlChecker
40
+ end
41
+ end
42
+
43
+ # TODO
44
+ # it_behaves_like 'a single URL checker'
45
+
46
+ after(:all) do
47
+ silence_warnings { RUBY_ENGINE = @old_engine }
48
+ end
49
+ end
50
+
51
+
52
+ # ------------------------------------------------------------------------------------------------------ private -----
53
+ private
54
+
55
+ def silence_warnings(&block)
56
+ warn_level = $VERBOSE
57
+ $VERBOSE = nil
58
+ result = block.call
59
+ $VERBOSE = warn_level
60
+ result
61
+ end
62
+ end
@@ -0,0 +1,510 @@
1
+ require 'spec_helper'
2
+
3
+ describe NDD::UrlChecker::Status do
4
+
5
+ # ------------------------------------------------------------------------------------------------------ unknown -----
6
+ context 'when initialized' do
7
+ let(:uri) { 'http://www.example.com' }
8
+ let(:status) { NDD::UrlChecker::Status.new(uri) }
9
+
10
+ context '#uri' do
11
+ it 'returns the original URI' do
12
+ expect(status.uri).to eq uri
13
+ end
14
+ end
15
+
16
+ context '#uris' do
17
+ it 'returns the original URI' do
18
+ expect(status.uris).to eq [uri]
19
+ end
20
+ end
21
+
22
+ context '#code' do
23
+ it 'returns :unknown' do
24
+ expect(status.code).to eq :unknown
25
+ end
26
+ end
27
+
28
+ context '#valid?' do
29
+ it 'returns false' do
30
+ expect(status.valid?).to be_falsey
31
+ end
32
+ end
33
+
34
+ context '#invalid?' do
35
+ it 'returns false' do
36
+ expect(status.invalid?).to be_falsey
37
+ end
38
+ end
39
+
40
+ context '#error' do
41
+ it 'returns nil' do
42
+ expect(status.error).to be_nil
43
+ end
44
+ end
45
+
46
+ context '#direct' do
47
+ let!(:new_status) { status.direct }
48
+ it 'changes the code to :direct' do
49
+ expect(status.code).to eq :direct
50
+ end
51
+ it 'returns the status' do
52
+ expect(new_status).to eq status
53
+ end
54
+ end
55
+
56
+ context '#failed' do
57
+ let!(:new_status) { status.failed('some error') }
58
+ it 'changes the code to :failed' do
59
+ expect(status.code).to eq :failed
60
+ end
61
+ it 'returns the status' do
62
+ expect(new_status).to eq status
63
+ end
64
+ end
65
+
66
+ context '#redirected' do
67
+ let!(:new_status) { status.redirected('http://www.redirected.com') }
68
+ it 'changes the code to :redirected' do
69
+ expect(status.code).to eq :redirected
70
+ end
71
+ it 'returns the status' do
72
+ expect(new_status).to eq status
73
+ end
74
+ end
75
+
76
+ context '#too_many_redirects' do
77
+ let!(:new_status) { status.too_many_redirects }
78
+ it 'changes the code to :too_many_redirects' do
79
+ expect(status.code).to eq :too_many_redirects
80
+ end
81
+ it 'returns the status' do
82
+ expect(new_status).to eq status
83
+ end
84
+ end
85
+
86
+ context '#unknown_host' do
87
+ it 'changes the code to :unknown_host' do
88
+ status.unknown_host
89
+ expect(status.code).to eq :unknown_host
90
+ end
91
+ it 'returns the status' do
92
+ expect(status.unknown_host).to eq status
93
+ end
94
+ end
95
+
96
+ context '#to_s' do
97
+ it 'returns the status representation' do
98
+ expect(status.to_s).to match %r{^#<NDD::UrlChecker::Status:0[xX][0-9a-fA-F]+ @uris=\["http://www.example.com"\], @code=:unknown>$}
99
+ end
100
+ end
101
+ end
102
+
103
+ # ------------------------------------------------------------------------------------------------------- direct -----
104
+ context 'when code is :direct' do
105
+ let(:uri) { 'http://www.example.com' }
106
+ let(:status) { NDD::UrlChecker::Status.new(uri).direct }
107
+
108
+ context '#uri' do
109
+ it 'returns the original URI' do
110
+ expect(status.uri).to eq uri
111
+ end
112
+ end
113
+
114
+ context '#uris' do
115
+ it 'returns the original URI' do
116
+ expect(status.uris).to eq [uri]
117
+ end
118
+ end
119
+
120
+ context '#code' do
121
+ it 'returns :direct' do
122
+ expect(status.code).to eq :direct
123
+ end
124
+ end
125
+
126
+ context '#valid?' do
127
+ it 'returns true' do
128
+ expect(status.valid?).to be_truthy
129
+ end
130
+ end
131
+
132
+ context '#invalid?' do
133
+ it 'returns false' do
134
+ expect(status.invalid?).to be_falsey
135
+ end
136
+ end
137
+
138
+ context '#error' do
139
+ it 'returns nil' do
140
+ expect(status.error).to be_nil
141
+ end
142
+ end
143
+
144
+ context '#direct' do
145
+ it 'raises an error' do
146
+ expect { status.direct }.to raise_error(/from :direct to :direct is forbidden/)
147
+ end
148
+ end
149
+
150
+ context '#failed' do
151
+ it 'raises an error' do
152
+ expect { status.failed('some error') }.to raise_error(/from :direct to :failed is forbidden/)
153
+ end
154
+ end
155
+
156
+ context '#redirected' do
157
+ it 'raises an error' do
158
+ expect { status.redirected('http://www.redirected.com') }.to raise_error(/from :direct to :redirected is forbidden/)
159
+ end
160
+ end
161
+
162
+ context '#too_many_redirects' do
163
+ it 'raises an error' do
164
+ expect { status.too_many_redirects }.to raise_error(/from :direct to :too_many_redirects is forbidden/)
165
+ end
166
+ end
167
+
168
+ context '#unknown_host' do
169
+ it 'raises an error' do
170
+ expect { status.unknown_host }.to raise_error(/from :direct to :unknown_host is forbidden/)
171
+ end
172
+ end
173
+
174
+ context '#to_s' do
175
+ it 'returns the status representation' do
176
+ expect(status.to_s).to match %r{^#<NDD::UrlChecker::Status:0[xX][0-9a-fA-F]+ @uris=\["http://www.example.com"\], @code=:direct>$}
177
+ end
178
+ end
179
+ end
180
+
181
+ # ------------------------------------------------------------------------------------------------------- failed -----
182
+ context 'when code is :failed' do
183
+ let(:uri) { 'http://www.example.com' }
184
+ let(:status) { NDD::UrlChecker::Status.new(uri).failed('some error') }
185
+
186
+ context '#uri' do
187
+ it 'returns the original URI' do
188
+ expect(status.uri).to eq uri
189
+ end
190
+ end
191
+
192
+ context '#uris' do
193
+ it 'returns the original URI' do
194
+ expect(status.uris).to eq [uri]
195
+ end
196
+ end
197
+
198
+ context '#code' do
199
+ it 'returns :failed' do
200
+ expect(status.code).to eq :failed
201
+ end
202
+ end
203
+
204
+ context '#valid?' do
205
+ it 'returns false' do
206
+ expect(status.valid?).to be_falsey
207
+ end
208
+ end
209
+
210
+ context '#invalid?' do
211
+ it 'returns true' do
212
+ expect(status.invalid?).to be_truthy
213
+ end
214
+ end
215
+
216
+ context '#error' do
217
+ it 'returns the error' do
218
+ expect(status.error).to eq 'some error'
219
+ end
220
+ end
221
+
222
+ context '#direct' do
223
+ it 'raises an error' do
224
+ expect { status.direct }.to raise_error(/from :failed to :direct is forbidden/)
225
+ end
226
+ end
227
+
228
+ context '#failed' do
229
+ it 'raises an error' do
230
+ expect { status.failed('some error') }.to raise_error(/from :failed to :failed is forbidden/)
231
+ end
232
+ end
233
+
234
+ context '#redirected' do
235
+ it 'raises an error' do
236
+ expect { status.redirected('http://www.redirected.com') }.to raise_error(/from :failed to :redirected is forbidden/)
237
+ end
238
+ end
239
+
240
+ context '#too_many_redirects' do
241
+ it 'raises an error' do
242
+ expect { status.too_many_redirects }.to raise_error(/from :failed to :too_many_redirects is forbidden/)
243
+ end
244
+ end
245
+
246
+ context '#unknown_host' do
247
+ it 'raises an error' do
248
+ expect { status.unknown_host }.to raise_error(/from :failed to :unknown_host is forbidden/)
249
+ end
250
+ end
251
+
252
+ context '#to_s' do
253
+ it 'returns the status representation' do
254
+ expect(status.to_s).to match %r{^#<NDD::UrlChecker::Status:0[xX][0-9a-fA-F]+ @uris=\["http://www.example.com"\], @code=:failed, @error="some error">$}
255
+ end
256
+ end
257
+ end
258
+
259
+ # --------------------------------------------------------------------------------------------------- redirected -----
260
+ context 'when code is :redirected' do
261
+ let(:uri) { 'http://www.example.com' }
262
+ let(:redirect_uri) { 'http://www.redirected.com' }
263
+ let(:status) { NDD::UrlChecker::Status.new(uri).redirected(redirect_uri) }
264
+
265
+ context '#uri' do
266
+ it 'returns the original URI' do
267
+ expect(status.uri).to eq uri
268
+ end
269
+ end
270
+
271
+ context '#uris' do
272
+ it 'returns all the URI' do
273
+ expect(status.uris).to eq [uri, redirect_uri]
274
+ end
275
+ end
276
+
277
+ context '#code' do
278
+ it 'returns :redirected' do
279
+ expect(status.code).to eq :redirected
280
+ end
281
+ end
282
+
283
+ context '#valid?' do
284
+ it 'returns true' do
285
+ expect(status.valid?).to be_truthy
286
+ end
287
+ end
288
+
289
+ context '#invalid?' do
290
+ it 'returns false' do
291
+ expect(status.invalid?).to be_falsey
292
+ end
293
+ end
294
+
295
+ context '#error' do
296
+ it 'returns nil' do
297
+ expect(status.error).to be_nil
298
+ end
299
+ end
300
+
301
+ context '#direct' do
302
+ it 'raises an error' do
303
+ expect { status.direct }.to raise_error(/from :redirected to :direct is forbidden/)
304
+ end
305
+ end
306
+
307
+ context '#failed' do
308
+ let!(:new_status) { status.failed('some error') }
309
+ it 'changes the code to :failed' do
310
+ expect(status.code).to eq :failed
311
+ end
312
+ it 'returns the status' do
313
+ expect(new_status).to eq status
314
+ end
315
+ end
316
+
317
+ context '#redirected' do
318
+ let!(:new_status) { status.redirected('http://www.redirected.com') }
319
+ it 'changes the code to :redirected' do
320
+ expect(status.code).to eq :redirected
321
+ end
322
+ it 'returns the status' do
323
+ expect(new_status).to eq status
324
+ end
325
+ end
326
+
327
+ context '#too_many_redirects' do
328
+ let!(:new_status) { status.too_many_redirects }
329
+ it 'changes the code to :too_many_redirects' do
330
+ expect(status.code).to eq :too_many_redirects
331
+ end
332
+ it 'returns the status' do
333
+ expect(new_status).to eq status
334
+ end
335
+ end
336
+
337
+ context '#unknown_host' do
338
+ it 'changes the code to :unknown_host' do
339
+ status.unknown_host
340
+ expect(status.code).to eq :unknown_host
341
+ end
342
+ it 'returns the status' do
343
+ expect(status.unknown_host).to eq status
344
+ end
345
+ end
346
+
347
+ context '#to_s' do
348
+ it 'returns the status representation' do
349
+ expect(status.to_s).to match %r{^#<NDD::UrlChecker::Status:0[xX][0-9a-fA-F]+ @uris=\["http://www.example.com", "http://www.redirected.com"\], @code=:redirected>$}
350
+ end
351
+ end
352
+ end
353
+
354
+ # ------------------------------------------------------------------------------------------- too_many_redirects -----
355
+ context 'when code is :too_many_redirects' do
356
+ let(:uri) { 'http://www.example.com' }
357
+ let(:status) { NDD::UrlChecker::Status.new(uri).too_many_redirects }
358
+
359
+ context '#uri' do
360
+ it 'returns the original URI' do
361
+ expect(status.uri).to eq uri
362
+ end
363
+ end
364
+
365
+ context '#uris' do
366
+ it 'returns the original URI' do
367
+ expect(status.uris).to eq [uri]
368
+ end
369
+ end
370
+
371
+ context '#code' do
372
+ it 'returns :too_many_redirects' do
373
+ expect(status.code).to eq :too_many_redirects
374
+ end
375
+ end
376
+
377
+ context '#valid?' do
378
+ it 'returns false' do
379
+ expect(status.valid?).to be_falsey
380
+ end
381
+ end
382
+
383
+ context '#invalid?' do
384
+ it 'returns true' do
385
+ expect(status.invalid?).to be_truthy
386
+ end
387
+ end
388
+
389
+ context '#error' do
390
+ it 'returns nil' do
391
+ expect(status.error).to be_nil
392
+ end
393
+ end
394
+
395
+ context '#direct' do
396
+ it 'raises an error' do
397
+ expect { status.direct }.to raise_error(/from :too_many_redirects to :direct is forbidden/)
398
+ end
399
+ end
400
+
401
+ context '#failed' do
402
+ it 'raises an error' do
403
+ expect { status.failed('some error') }.to raise_error(/from :too_many_redirects to :failed is forbidden/)
404
+ end
405
+ end
406
+
407
+ context '#redirected' do
408
+ it 'raises an error' do
409
+ expect { status.redirected('http://www.redirected.com') }.to raise_error(/from :too_many_redirects to :redirected is forbidden/)
410
+ end
411
+ end
412
+
413
+ context '#too_many_redirects' do
414
+ it 'raises an error' do
415
+ expect { status.too_many_redirects }.to raise_error(/from :too_many_redirects to :too_many_redirects is forbidden/)
416
+ end
417
+ end
418
+
419
+ context '#unknown_host' do
420
+ it 'raises an error' do
421
+ expect { status.unknown_host }.to raise_error(/from :too_many_redirects to :unknown_host is forbidden/)
422
+ end
423
+ end
424
+
425
+ context '#to_s' do
426
+ it 'returns the status representation' do
427
+ expect(status.to_s).to match %r{^#<NDD::UrlChecker::Status:0[xX][0-9a-fA-F]+ @uris=\["http://www.example.com"\], @code=:too_many_redirects>$}
428
+ end
429
+ end
430
+ end
431
+
432
+ # ------------------------------------------------------------------------------------------------- unknown_host -----
433
+ context 'when code is :unknown_host' do
434
+ let(:uri) { 'http://www.example.com' }
435
+ let(:status) { NDD::UrlChecker::Status.new(uri).unknown_host }
436
+
437
+ context '#uri' do
438
+ it 'returns the original URI' do
439
+ expect(status.uri).to eq uri
440
+ end
441
+ end
442
+
443
+ context '#uris' do
444
+ it 'returns the original URI' do
445
+ expect(status.uris).to eq [uri]
446
+ end
447
+ end
448
+
449
+ context '#code' do
450
+ it 'returns :unknown_host' do
451
+ expect(status.code).to eq :unknown_host
452
+ end
453
+ end
454
+
455
+ context '#valid?' do
456
+ it 'returns false' do
457
+ expect(status.valid?).to be_falsey
458
+ end
459
+ end
460
+
461
+ context '#invalid?' do
462
+ it 'returns true' do
463
+ expect(status.invalid?).to be_truthy
464
+ end
465
+ end
466
+
467
+ context '#error' do
468
+ it 'returns nil' do
469
+ expect(status.error).to be_nil
470
+ end
471
+ end
472
+
473
+ context '#direct' do
474
+ it 'raises an error' do
475
+ expect { status.direct }.to raise_error(/from :unknown_host to :direct is forbidden/)
476
+ end
477
+ end
478
+
479
+ context '#failed' do
480
+ it 'raises an error' do
481
+ expect { status.failed('some error') }.to raise_error(/from :unknown_host to :failed is forbidden/)
482
+ end
483
+ end
484
+
485
+ context '#redirected' do
486
+ it 'raises an error' do
487
+ expect { status.redirected('http://www.redirected.com') }.to raise_error(/from :unknown_host to :redirected is forbidden/)
488
+ end
489
+ end
490
+
491
+ context '#too_many_redirects' do
492
+ it 'raises an error' do
493
+ expect { status.too_many_redirects }.to raise_error(/from :unknown_host to :too_many_redirects is forbidden/)
494
+ end
495
+ end
496
+
497
+ context '#unknown_host' do
498
+ it 'raises an error' do
499
+ expect { status.unknown_host }.to raise_error(/from :unknown_host to :unknown_host is forbidden/)
500
+ end
501
+ end
502
+
503
+ context '#to_s' do
504
+ it 'returns the status representation' do
505
+ expect(status.to_s).to match %r{^#<NDD::UrlChecker::Status:0[xX][0-9a-fA-F]+ @uris=\["http://www.example.com"\], @code=:unknown_host>$}
506
+ end
507
+ end
508
+ end
509
+
510
+ end