ruby-link-checker 0.4.0 → 0.5.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.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/coverage.yml +24 -0
  3. data/.github/workflows/danger-comment.yml +17 -0
  4. data/.github/workflows/danger.yml +12 -0
  5. data/.github/workflows/lint.yml +20 -0
  6. data/.github/workflows/release.yml +14 -0
  7. data/.github/workflows/test.yml +25 -0
  8. data/.gitignore +6 -0
  9. data/.rspec +2 -0
  10. data/.rubocop.yml +26 -0
  11. data/.rubocop_todo.yml +332 -0
  12. data/AGENTS.md +23 -0
  13. data/CHANGELOG.md +15 -0
  14. data/CONTRIBUTING.md +4 -0
  15. data/Dangerfile +0 -0
  16. data/Gemfile +3 -0
  17. data/LICENSE.md +0 -0
  18. data/README.md +78 -4
  19. data/RELEASING.md +17 -40
  20. data/Rakefile +8 -0
  21. data/benchmarks/check.rb +0 -0
  22. data/benchmarks/data/opensearch.org/links.txt +0 -0
  23. data/benchmarks/data/opensearch.org/small.txt +0 -0
  24. data/lib/ruby-link-checker/async/http/checker.rb +32 -6
  25. data/lib/ruby-link-checker/async/http/config.rb +4 -0
  26. data/lib/ruby-link-checker/async/http/result.rb +4 -0
  27. data/lib/ruby-link-checker/async/http.rb +0 -0
  28. data/lib/ruby-link-checker/cache.rb +101 -0
  29. data/lib/ruby-link-checker/callbacks.rb +0 -0
  30. data/lib/ruby-link-checker/checker.rb +76 -4
  31. data/lib/ruby-link-checker/config.rb +62 -0
  32. data/lib/ruby-link-checker/errors/base_error.rb +0 -0
  33. data/lib/ruby-link-checker/errors/connection_error.rb +23 -0
  34. data/lib/ruby-link-checker/errors/redirect_limit_error.rb +15 -0
  35. data/lib/ruby-link-checker/errors/redirect_loop_error.rb +0 -0
  36. data/lib/ruby-link-checker/errors.rb +2 -0
  37. data/lib/ruby-link-checker/logger.rb +0 -0
  38. data/lib/ruby-link-checker/net/http/checker.rb +2 -1
  39. data/lib/ruby-link-checker/net/http/config.rb +0 -0
  40. data/lib/ruby-link-checker/net/http/result.rb +4 -0
  41. data/lib/ruby-link-checker/net/http.rb +0 -0
  42. data/lib/ruby-link-checker/result.rb +25 -0
  43. data/lib/ruby-link-checker/task.rb +2 -0
  44. data/lib/ruby-link-checker/tasks.rb +40 -6
  45. data/lib/ruby-link-checker/typhoeus/hydra/checker.rb +27 -5
  46. data/lib/ruby-link-checker/typhoeus/hydra/config.rb +0 -0
  47. data/lib/ruby-link-checker/typhoeus/hydra/result.rb +4 -0
  48. data/lib/ruby-link-checker/typhoeus/hydra.rb +0 -0
  49. data/lib/ruby-link-checker/version.rb +1 -1
  50. data/lib/ruby-link-checker.rb +1 -0
  51. data/lib/ruby_link_checker.rb +0 -0
  52. data/ruby-link-checker.gemspec +1 -1
  53. data/spec/fixtures/200.yml +0 -0
  54. data/spec/fixtures/301_200.yml +0 -0
  55. data/spec/fixtures/301_301.yml +0 -0
  56. data/spec/fixtures/301_400_301_200.yml +0 -0
  57. data/spec/fixtures/301_403.yml +0 -0
  58. data/spec/fixtures/308_429.yml +0 -0
  59. data/spec/fixtures/404.yml +0 -0
  60. data/spec/fixtures/404_200.yml +0 -0
  61. data/spec/fixtures/404_404.yml +0 -0
  62. data/spec/fixtures/429_200.yml +0 -0
  63. data/spec/fixtures/429_429_200.yml +0 -0
  64. data/spec/ruby-link-checker/async/http/checker_spec.rb +86 -1
  65. data/spec/ruby-link-checker/callbacks_spec.rb +0 -0
  66. data/spec/ruby-link-checker/checker_spec.rb +89 -2
  67. data/spec/ruby-link-checker/config_spec.rb +6 -0
  68. data/spec/ruby-link-checker/errors/redirect_loop_error_spec.rb +0 -0
  69. data/spec/ruby-link-checker/net/http/checker_spec.rb +15 -0
  70. data/spec/ruby-link-checker/result_error_spec.rb +17 -0
  71. data/spec/ruby-link-checker/result_spec.rb +0 -0
  72. data/spec/ruby-link-checker/task_spec.rb +0 -0
  73. data/spec/ruby-link-checker/tasks_spec.rb +44 -0
  74. data/spec/ruby-link-checker/typhoeus/hydra/checker_spec.rb +88 -1
  75. data/spec/ruby-link-checker/version_spec.rb +0 -0
  76. data/spec/spec_helper.rb +0 -0
  77. data/spec/support/config.rb +0 -0
  78. data/spec/support/link_checker.rb +243 -3
  79. data/spec/support/vcr.rb +0 -0
  80. data/spec/support/with_result.rb +0 -0
  81. data/spec/support/with_url.rb +0 -0
  82. metadata +21 -34
  83. data/Gemfile.lock +0 -334
  84. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc.png +0 -0
  85. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc_disabled.png +0 -0
  86. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_both.png +0 -0
  87. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc.png +0 -0
  88. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc_disabled.png +0 -0
  89. data/coverage/assets/0.13.2/application.css +0 -1
  90. data/coverage/assets/0.13.2/application.js +0 -7
  91. data/coverage/assets/0.13.2/colorbox/border.png +0 -0
  92. data/coverage/assets/0.13.2/colorbox/controls.png +0 -0
  93. data/coverage/assets/0.13.2/colorbox/loading.gif +0 -0
  94. data/coverage/assets/0.13.2/colorbox/loading_background.png +0 -0
  95. data/coverage/assets/0.13.2/favicon_green.png +0 -0
  96. data/coverage/assets/0.13.2/favicon_red.png +0 -0
  97. data/coverage/assets/0.13.2/favicon_yellow.png +0 -0
  98. data/coverage/assets/0.13.2/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  99. data/coverage/assets/0.13.2/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  100. data/coverage/assets/0.13.2/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  101. data/coverage/assets/0.13.2/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  102. data/coverage/assets/0.13.2/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  103. data/coverage/assets/0.13.2/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  104. data/coverage/assets/0.13.2/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  105. data/coverage/assets/0.13.2/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  106. data/coverage/assets/0.13.2/images/ui-icons_222222_256x240.png +0 -0
  107. data/coverage/assets/0.13.2/images/ui-icons_2e83ff_256x240.png +0 -0
  108. data/coverage/assets/0.13.2/images/ui-icons_454545_256x240.png +0 -0
  109. data/coverage/assets/0.13.2/images/ui-icons_888888_256x240.png +0 -0
  110. data/coverage/assets/0.13.2/images/ui-icons_cd0a0a_256x240.png +0 -0
  111. data/coverage/assets/0.13.2/loading.gif +0 -0
  112. data/coverage/assets/0.13.2/magnify.png +0 -0
  113. data/coverage/index.html +0 -10746
@@ -15,7 +15,7 @@ describe LinkChecker::Typhoeus::Hydra::Checker do
15
15
  end
16
16
  before :all do
17
17
  VCR.configure do |config|
18
- config.hook_into :typhoeus
18
+ config.hook_into :webmock
19
19
  end
20
20
  end
21
21
 
@@ -90,4 +90,91 @@ describe LinkChecker::Typhoeus::Hydra::Checker do
90
90
  checker.run
91
91
  end
92
92
  end
93
+
94
+ describe 'delayed retries' do
95
+ let(:checker) { described_class.new(methods: ['GET'], retries: 1, retry_after: 0.005) }
96
+
97
+ around do |example|
98
+ VCR.turned_off { example.run }
99
+ end
100
+
101
+ it 'processes unrelated requests before waiting for a retry' do
102
+ completed = []
103
+ stub_request(:get, 'https://www.example.org/retry').to_return({ status: 503 }, { status: 200 })
104
+ stub_request(:get, 'https://www.example.org/other').to_return(status: 200)
105
+ checker.on(:success) { |result| completed << result.uri.path }
106
+ checker.check('https://www.example.org/retry')
107
+ checker.check('https://www.example.org/other')
108
+ checker.run
109
+ expect(completed).to eq(['/other', '/retry'])
110
+ end
111
+ end
112
+
113
+ describe 'transport failures' do
114
+ let(:checker) { described_class.new(methods: ['GET']) }
115
+
116
+ around do |example|
117
+ VCR.turned_off do
118
+ WebMock.disable!
119
+ Typhoeus::Config.block_connection = true
120
+ example.run
121
+ ensure
122
+ Typhoeus::Config.block_connection = false
123
+ WebMock.enable!
124
+ end
125
+ end
126
+
127
+ after do
128
+ Typhoeus::Expectation.clear
129
+ end
130
+
131
+ it 'reports connection errors separately from HTTP failures' do
132
+ response = Typhoeus::Response.new(code: 0, return_code: :couldnt_connect)
133
+ Typhoeus.stub(nil).and_return(response)
134
+ checker.check('https://www.example.org/')
135
+ checker.run
136
+ expect(checker.results[:failure]).to be_empty
137
+ expect(checker.results[:error].first.error_type).to eq(:connection)
138
+ end
139
+ end
140
+
141
+ describe 'in-flight deduplication' do
142
+ let(:checker) { described_class.new(methods: ['GET'], deduplicate: true) }
143
+ let(:url) { 'https://www.example.org/' }
144
+
145
+ around do |example|
146
+ VCR.turned_off { example.run }
147
+ end
148
+
149
+ it 'fans out events to duplicates queued before completion' do
150
+ metadata = []
151
+ stub_request(:any, url).to_return(status: 200)
152
+ checker.on(:result) { |result| metadata << result.options }
153
+ checker.check(url, location: 'one.html')
154
+ checker.check(url, location: 'two.html')
155
+ checker.run
156
+ expect(a_request(:any, url)).to have_been_made.once
157
+ expect(metadata).to eq([{ location: 'one.html' }, { location: 'two.html' }])
158
+ end
159
+
160
+ it 'replays events when a subscriber joins during a result callback' do
161
+ stub_request(:any, url).to_return(status: 200)
162
+ checker.on(:result) { |result| checker.check(url, location: 'two.html') if result.options[:location] == 'one.html' }
163
+ checker.check(url, location: 'one.html')
164
+ checker.run
165
+ expect(checker.results[:success].map(&:options)).to eq([{ location: 'one.html' }, { location: 'two.html' }])
166
+ expect(a_request(:any, url)).to have_been_made.once
167
+ end
168
+
169
+ it 'does not memoize a response if settings changed while queued' do
170
+ stub_request(:any, url).to_return(status: 200)
171
+ checker.check(url)
172
+ checker.headers = { 'Accept' => 'text/html' }
173
+ checker.run
174
+ checker.headers = {}
175
+ checker.check(url)
176
+ checker.run
177
+ expect(a_request(:any, url)).to have_been_made.twice
178
+ end
179
+ end
93
180
  end
File without changes
data/spec/spec_helper.rb CHANGED
File without changes
File without changes
@@ -1,4 +1,244 @@
1
1
  shared_context 'a link checker' do
2
+ context 'with redirects', :aggregate_failures do
3
+ let(:checker) { described_class.new(methods: ['GET'], max_redirects: 2) }
4
+ let(:url) { 'https://www.example.org/start' }
5
+ let(:result) { checker.results.values.flatten.first }
6
+
7
+ around do |example|
8
+ VCR.turned_off { example.run }
9
+ end
10
+
11
+ it 'resolves each relative redirect against the current URL' do
12
+ stub_request(:any, url).to_return(status: 302, headers: { 'Location' => '/nested/next' })
13
+ stub_request(:any, 'https://www.example.org/nested/next').to_return(status: 302, headers: { 'Location' => 'final' })
14
+ stub_request(:any, 'https://www.example.org/nested/final').to_return(status: 200)
15
+ checker.check(url, location: 'page.html')
16
+ checker.run if checker.respond_to?(:run)
17
+ expect(result.uri.to_s).to eq(url)
18
+ expect(result.result_uri.to_s).to eq('https://www.example.org/nested/final')
19
+ expect(result.options).to eq(location: 'page.html')
20
+ end
21
+
22
+ it 'stops redirect chains at the configured limit' do
23
+ stub_request(:any, url).to_return(status: 302, headers: { 'Location' => '/one' })
24
+ stub_request(:any, 'https://www.example.org/one').to_return(status: 302, headers: { 'Location' => '/two' })
25
+ stub_request(:any, 'https://www.example.org/two').to_return(status: 302, headers: { 'Location' => '/three' })
26
+ checker.check(url)
27
+ checker.run if checker.respond_to?(:run)
28
+ expect(result.error).to be_a(LinkChecker::Errors::RedirectLimitError)
29
+ expect(result.uri.to_s).to eq(url)
30
+ expect(result.result_uri.to_s).to eq('https://www.example.org/two')
31
+ expect(a_request(:any, 'https://www.example.org/three')).not_to have_been_made
32
+ end
33
+
34
+ it 'detects a redirect back to the original URL without requesting it again' do
35
+ stub_request(:any, url).to_return(status: 302, headers: { 'Location' => '/one' })
36
+ stub_request(:any, 'https://www.example.org/one').to_return(status: 302, headers: { 'Location' => url })
37
+ checker.check(url)
38
+ checker.run if checker.respond_to?(:run)
39
+ expect(checker.results[:error].first.error).to be_a(LinkChecker::Errors::RedirectLoopError)
40
+ expect(a_request(:any, url)).to have_been_made.once
41
+ end
42
+
43
+ it 'allows redirects to be disabled' do
44
+ checker.max_redirects = 0
45
+ stub_request(:any, url).to_return(status: 302, headers: { 'Location' => '/one' })
46
+ checker.check(url)
47
+ checker.run if checker.respond_to?(:run)
48
+ expect(checker.results[:error].first.error).to be_a(LinkChecker::Errors::RedirectLimitError)
49
+ expect(a_request(:any, 'https://www.example.org/one')).not_to have_been_made
50
+ end
51
+ end
52
+
53
+ context 'with transient retries' do
54
+ let(:checker_options) { {} }
55
+ let(:checker) { described_class.new({ methods: ['GET'], retries: 2 }.merge(checker_options)) }
56
+ let(:url) { 'https://www.example.org/retry' }
57
+
58
+ around do |example|
59
+ VCR.turned_off { example.run }
60
+ end
61
+
62
+ it 'retries service errors and preserves metadata' do
63
+ stub_request(:get, url).to_return({ status: 503 }, { status: 200 })
64
+ checker.check(url, location: 'page.html')
65
+ checker.run if checker.respond_to?(:run)
66
+ expect(checker.results[:success].first.options).to eq(location: 'page.html')
67
+ expect(a_request(:get, url)).to have_been_made.twice
68
+ end
69
+
70
+ it 'does not retry permanent HTTP failures' do
71
+ stub_request(:get, url).to_return(status: 404)
72
+ checker.check(url)
73
+ checker.run if checker.respond_to?(:run)
74
+ expect(checker.results[:failure].first.code).to eq(404)
75
+ expect(a_request(:get, url)).to have_been_made.once
76
+ end
77
+
78
+ it 'retries timeouts and reports a timeout category' do
79
+ stub_request(:get, url).to_timeout
80
+ checker.check(url)
81
+ checker.run if checker.respond_to?(:run)
82
+ expect(checker.results[:error].first.error_type).to eq(:timeout)
83
+ expect(a_request(:get, url)).to have_been_made.times(3)
84
+ end
85
+
86
+ it 'does not retry malformed URLs' do
87
+ checker.check('\/invalid-url')
88
+ checker.run if checker.respond_to?(:run)
89
+ expect(checker.results[:error].first.error_type).to eq(:other)
90
+ expect(checker.results[:error].size).to eq(1)
91
+ end
92
+
93
+ context 'with a configured delay and backoff' do
94
+ let(:checker_options) { { retry_after: 0.005, retry_backoff: 2 } }
95
+
96
+ it 'waits before each retry without changing the retry budget' do
97
+ stub_request(:get, url).to_return({ status: 503 }, { status: 503 }, { status: 200 })
98
+ started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
99
+ checker.check(url)
100
+ checker.run if checker.respond_to?(:run)
101
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
102
+ expect(elapsed).to be >= 0.015
103
+ expect(a_request(:get, url)).to have_been_made.times(3)
104
+ expect(checker.results[:success].size).to eq(1)
105
+ end
106
+ end
107
+
108
+ context 'with Retry-After enabled' do
109
+ let(:checker_options) { { respect_retry_after: true, max_retry_delay: 0.005 } }
110
+
111
+ it 'honors a bounded server delay' do
112
+ stub_request(:get, url).to_return({ status: 429, headers: { 'Retry-After' => '5' } }, { status: 200 })
113
+ started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
114
+ checker.check(url)
115
+ checker.run if checker.respond_to?(:run)
116
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
117
+ expect(elapsed).to be >= 0.005
118
+ expect(elapsed).to be < 1
119
+ expect(checker.results[:success].size).to eq(1)
120
+ end
121
+
122
+ it 'accepts HTTP dates' do
123
+ stub_request(:get, url).to_return(
124
+ { status: 503, headers: { 'Retry-After' => (Time.now + 60).httpdate } }, { status: 200 }
125
+ )
126
+ checker.check(url)
127
+ checker.run if checker.respond_to?(:run)
128
+ expect(checker.results[:success].size).to eq(1)
129
+ end
130
+
131
+ it 'ignores invalid server delays' do
132
+ stub_request(:get, url).to_return({ status: 503, headers: { 'Retry-After' => 'invalid' } }, { status: 200 })
133
+ checker.check(url)
134
+ checker.run if checker.respond_to?(:run)
135
+ expect(checker.results[:success].size).to eq(1)
136
+ end
137
+ end
138
+ end
139
+
140
+ context 'with deduplication' do
141
+ let(:checker) { described_class.new(methods: ['GET'], deduplicate: true) }
142
+ let(:url) { 'https://www.example.org/' }
143
+
144
+ around do |example|
145
+ VCR.turned_off { example.run }
146
+ end
147
+
148
+ it 'checks once and preserves metadata for every submission' do
149
+ stub_request(:any, url).to_return(status: 200)
150
+ checker.check(url, location: 'one.html')
151
+ checker.check(url, location: 'two.html')
152
+ checker.run if checker.respond_to?(:run)
153
+ expect(a_request(:any, url)).to have_been_made.once
154
+ expect(checker.results[:success].map(&:options)).to eq([{ location: 'one.html' }, { location: 'two.html' }])
155
+ expect(checker.results[:success].map(&:uri)).to eq([URI(url), URI(url)])
156
+ end
157
+
158
+ it 'rechecks failures instead of retaining them' do
159
+ stub_request(:any, url).to_return({ status: 404 }, { status: 200 })
160
+ checker.check(url)
161
+ checker.run if checker.respond_to?(:run)
162
+ checker.check(url)
163
+ checker.run if checker.respond_to?(:run)
164
+ expect(a_request(:any, url)).to have_been_made.twice
165
+ expect(checker.results[:failure].size).to eq(1)
166
+ expect(checker.results[:success].size).to eq(1)
167
+ end
168
+
169
+ it 'keeps query values distinct' do
170
+ stub_request(:any, "#{url}?id=1").to_return(status: 200)
171
+ stub_request(:any, "#{url}?id=2").to_return(status: 404)
172
+ checker.check("#{url}?id=1")
173
+ checker.check("#{url}?id=2")
174
+ checker.run if checker.respond_to?(:run)
175
+ expect(checker.results[:success].first.uri.query).to eq('id=1')
176
+ expect(checker.results[:failure].first.uri.query).to eq('id=2')
177
+ end
178
+
179
+ it 'includes headers in request identity' do
180
+ stub_request(:any, url).to_return(status: 200)
181
+ checker.check(url)
182
+ checker.run if checker.respond_to?(:run)
183
+ checker.headers = { 'Accept' => 'text/html' }
184
+ checker.check(url)
185
+ checker.run if checker.respond_to?(:run)
186
+ expect(a_request(:any, url)).to have_been_made.twice
187
+ end
188
+
189
+ context 'without result collection' do
190
+ let(:checker) { described_class.new(methods: ['GET'], deduplicate: true, results: false) }
191
+
192
+ it 'still delivers results to every subscriber' do
193
+ completed = []
194
+ stub_request(:any, url).to_return(status: 200)
195
+ checker.on(:success) { |result| completed << result.options }
196
+ checker.check(url, location: 'one.html')
197
+ checker.check(url, location: 'two.html')
198
+ checker.run if checker.respond_to?(:run)
199
+ expect(completed).to eq([{ location: 'one.html' }, { location: 'two.html' }])
200
+ expect(checker.results).to be_nil
201
+ end
202
+ end
203
+ end
204
+
205
+ context 'with persistent caching' do
206
+ let(:directory) { Dir.mktmpdir('link-checker-spec-') }
207
+ let(:cache_options) { { file: File.join(directory, 'cache.json'), ttl: 60 } }
208
+ let(:checker) { described_class.new(methods: ['GET'], cache: cache_options) }
209
+ let(:next_checker) { described_class.new(methods: ['GET'], cache: cache_options) }
210
+ let(:url) { 'https://www.example.org/' }
211
+
212
+ around do |example|
213
+ VCR.turned_off { example.run }
214
+ end
215
+
216
+ after do
217
+ FileUtils.remove_entry(directory)
218
+ end
219
+
220
+ it 'reuses a success across checker instances with fresh metadata' do
221
+ stub_request(:any, url).to_return(status: 200)
222
+ checker.check(url, location: 'one.html')
223
+ checker.run if checker.respond_to?(:run)
224
+ next_checker.check(url, location: 'two.html')
225
+ next_checker.run if next_checker.respond_to?(:run)
226
+ expect(next_checker.results[:success].first.cached?).to be true
227
+ expect(next_checker.results[:success].first.options).to eq(location: 'two.html')
228
+ expect(a_request(:any, url)).to have_been_made.once
229
+ end
230
+
231
+ it 'does not persist failed checks' do
232
+ stub_request(:any, url).to_return({ status: 404 }, { status: 200 })
233
+ checker.check(url)
234
+ checker.run if checker.respond_to?(:run)
235
+ next_checker.check(url)
236
+ next_checker.run if next_checker.respond_to?(:run)
237
+ expect(next_checker.results[:success].first.cached?).to be false
238
+ expect(a_request(:any, url)).to have_been_made.twice
239
+ end
240
+ end
241
+
2
242
  context 'default' do
3
243
  subject do
4
244
  described_class.new
@@ -176,7 +416,7 @@ shared_context 'a link checker' do
176
416
 
177
417
  context 'with 1 retry' do
178
418
  subject do
179
- described_class.new(methods: ['GET'], retries: 1)
419
+ described_class.new(methods: ['GET'], retries: 1, retry_status_codes: nil)
180
420
  end
181
421
 
182
422
  it 'fails' do
@@ -192,7 +432,7 @@ shared_context 'a link checker' do
192
432
 
193
433
  context 'with 2 retries' do
194
434
  subject do
195
- described_class.new(methods: ['GET'], retries: 2)
435
+ described_class.new(methods: ['GET'], retries: 2, retry_status_codes: nil)
196
436
  end
197
437
 
198
438
  it 'fails' do
@@ -240,7 +480,7 @@ shared_context 'a link checker' do
240
480
  expect(result.error?).to be true
241
481
  expect(result.error).to be_a LinkChecker::Errors::RedirectLoopError
242
482
  expect(result.redirect?).to be false
243
- expect(subject).to have_received(:called!).with(:redirect, anything).twice
483
+ expect(subject).to have_received(:called!).with(:redirect, anything).once
244
484
  expect(subject).to have_received(:called!).with(:error, result)
245
485
  expect(subject).not_to have_received(:called!).with(:failure, result)
246
486
  expect(subject).not_to have_received(:called!).with(:success, result)
data/spec/support/vcr.rb CHANGED
File without changes
File without changes
File without changes
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-link-checker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Doubrovkine
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-11-08 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: async-http
@@ -37,16 +38,27 @@ dependencies:
37
38
  - - ">="
38
39
  - !ruby/object:Gem::Version
39
40
  version: '0'
41
+ description:
40
42
  email: dblock@dblock.org
41
43
  executables: []
42
44
  extensions: []
43
45
  extra_rdoc_files: []
44
46
  files:
47
+ - ".github/workflows/coverage.yml"
48
+ - ".github/workflows/danger-comment.yml"
49
+ - ".github/workflows/danger.yml"
50
+ - ".github/workflows/lint.yml"
51
+ - ".github/workflows/release.yml"
52
+ - ".github/workflows/test.yml"
53
+ - ".gitignore"
54
+ - ".rspec"
55
+ - ".rubocop.yml"
56
+ - ".rubocop_todo.yml"
57
+ - AGENTS.md
45
58
  - CHANGELOG.md
46
59
  - CONTRIBUTING.md
47
60
  - Dangerfile
48
61
  - Gemfile
49
- - Gemfile.lock
50
62
  - LICENSE.md
51
63
  - README.md
52
64
  - RELEASING.md
@@ -54,46 +66,19 @@ files:
54
66
  - benchmarks/check.rb
55
67
  - benchmarks/data/opensearch.org/links.txt
56
68
  - benchmarks/data/opensearch.org/small.txt
57
- - coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc.png
58
- - coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc_disabled.png
59
- - coverage/assets/0.13.2/DataTables-1.10.20/images/sort_both.png
60
- - coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc.png
61
- - coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc_disabled.png
62
- - coverage/assets/0.13.2/application.css
63
- - coverage/assets/0.13.2/application.js
64
- - coverage/assets/0.13.2/colorbox/border.png
65
- - coverage/assets/0.13.2/colorbox/controls.png
66
- - coverage/assets/0.13.2/colorbox/loading.gif
67
- - coverage/assets/0.13.2/colorbox/loading_background.png
68
- - coverage/assets/0.13.2/favicon_green.png
69
- - coverage/assets/0.13.2/favicon_red.png
70
- - coverage/assets/0.13.2/favicon_yellow.png
71
- - coverage/assets/0.13.2/images/ui-bg_flat_0_aaaaaa_40x100.png
72
- - coverage/assets/0.13.2/images/ui-bg_flat_75_ffffff_40x100.png
73
- - coverage/assets/0.13.2/images/ui-bg_glass_55_fbf9ee_1x400.png
74
- - coverage/assets/0.13.2/images/ui-bg_glass_65_ffffff_1x400.png
75
- - coverage/assets/0.13.2/images/ui-bg_glass_75_dadada_1x400.png
76
- - coverage/assets/0.13.2/images/ui-bg_glass_75_e6e6e6_1x400.png
77
- - coverage/assets/0.13.2/images/ui-bg_glass_95_fef1ec_1x400.png
78
- - coverage/assets/0.13.2/images/ui-bg_highlight-soft_75_cccccc_1x100.png
79
- - coverage/assets/0.13.2/images/ui-icons_222222_256x240.png
80
- - coverage/assets/0.13.2/images/ui-icons_2e83ff_256x240.png
81
- - coverage/assets/0.13.2/images/ui-icons_454545_256x240.png
82
- - coverage/assets/0.13.2/images/ui-icons_888888_256x240.png
83
- - coverage/assets/0.13.2/images/ui-icons_cd0a0a_256x240.png
84
- - coverage/assets/0.13.2/loading.gif
85
- - coverage/assets/0.13.2/magnify.png
86
- - coverage/index.html
87
69
  - lib/ruby-link-checker.rb
88
70
  - lib/ruby-link-checker/async/http.rb
89
71
  - lib/ruby-link-checker/async/http/checker.rb
90
72
  - lib/ruby-link-checker/async/http/config.rb
91
73
  - lib/ruby-link-checker/async/http/result.rb
74
+ - lib/ruby-link-checker/cache.rb
92
75
  - lib/ruby-link-checker/callbacks.rb
93
76
  - lib/ruby-link-checker/checker.rb
94
77
  - lib/ruby-link-checker/config.rb
95
78
  - lib/ruby-link-checker/errors.rb
96
79
  - lib/ruby-link-checker/errors/base_error.rb
80
+ - lib/ruby-link-checker/errors/connection_error.rb
81
+ - lib/ruby-link-checker/errors/redirect_limit_error.rb
97
82
  - lib/ruby-link-checker/errors/redirect_loop_error.rb
98
83
  - lib/ruby-link-checker/logger.rb
99
84
  - lib/ruby-link-checker/net/http.rb
@@ -144,6 +129,7 @@ licenses:
144
129
  - MIT
145
130
  metadata:
146
131
  rubygems_mfa_required: 'true'
132
+ post_install_message:
147
133
  rdoc_options: []
148
134
  require_paths:
149
135
  - lib
@@ -158,7 +144,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
144
  - !ruby/object:Gem::Version
159
145
  version: 1.3.6
160
146
  requirements: []
161
- rubygems_version: 3.6.9
147
+ rubygems_version: 3.5.22
148
+ signing_key:
162
149
  specification_version: 4
163
150
  summary: Fast ruby link checker.
164
151
  test_files: []