metainspector 5.18.0 → 5.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,195 @@
1
+ require 'spec_helper'
2
+
3
+ describe MetaInspector, 'head_first option' do
4
+ let(:page_url) { 'http://example.com/head-first' }
5
+ let(:html) { '<html><head><title>Downloaded page</title></head></html>' }
6
+ let!(:get_request) do
7
+ stub_request(:get, page_url).to_return(body: html, headers: { 'Content-Type' => 'text/html' })
8
+ end
9
+
10
+ it 'does not send HEAD by default' do
11
+ expect(MetaInspector.new(page_url).title).to eq('Downloaded page')
12
+
13
+ expect(a_request(:head, page_url)).not_to have_been_made
14
+ expect(get_request).to have_been_requested.once
15
+ end
16
+
17
+ it 'does not send HEAD when explicitly disabled' do
18
+ expect(MetaInspector.new(page_url, head_first: false).title).to eq('Downloaded page')
19
+
20
+ expect(a_request(:head, page_url)).not_to have_been_made
21
+ end
22
+
23
+ it 'sends HEAD before GET for HTML and exposes the GET response' do
24
+ requests = []
25
+ head_request = stub_request(:head, page_url).to_return do
26
+ requests << :head
27
+ { headers: { 'Content-Type' => 'text/html; charset=UTF-8', 'X-Response' => 'head' } }
28
+ end
29
+ stub_request(:get, page_url).to_return do
30
+ requests << :get
31
+ { body: html, headers: { 'Content-Type' => 'text/html', 'X-Response' => 'get' } }
32
+ end
33
+
34
+ page = MetaInspector.new(page_url, head_first: true)
35
+
36
+ expect(page.title).to eq('Downloaded page')
37
+ expect(page.content_type).to eq('text/html')
38
+ expect(page.response.headers['x-response']).to eq('get')
39
+ expect(page.to_s).to eq(html)
40
+ expect(head_request).to have_been_requested.once
41
+ expect(requests).to eq([:head, :get])
42
+ end
43
+
44
+ it 'rejects non-HTML content without sending GET' do
45
+ head_request = stub_request(:head, page_url)
46
+ .to_return(headers: { 'Content-Type' => 'application/pdf; version=1.7' })
47
+
48
+ expect do
49
+ MetaInspector.new(page_url, head_first: true)
50
+ end.to raise_error(MetaInspector::NonHtmlError,
51
+ 'The url provided contains application/pdf content instead of text/html content')
52
+
53
+ expect(head_request).to have_been_requested.once
54
+ expect(get_request).not_to have_been_requested
55
+ end
56
+
57
+ it 'downloads non-HTML content when explicitly allowed' do
58
+ head_request = stub_request(:head, page_url).to_return(headers: { 'Content-Type' => 'text/plain' })
59
+ stub_request(:get, page_url).to_return(body: 'Plain text', headers: { 'Content-Type' => 'text/plain' })
60
+
61
+ page = MetaInspector.new(page_url, head_first: true, allow_non_html_content: true)
62
+
63
+ expect(page.to_s).to eq('Plain text')
64
+ expect(page.content_type).to eq('text/plain')
65
+ expect(head_request).to have_been_requested.once
66
+ expect(get_request).to have_been_requested.once
67
+ end
68
+
69
+ it 'continues to GET when HEAD has no content type' do
70
+ head_request = stub_request(:head, page_url).to_return(status: 200)
71
+
72
+ expect(MetaInspector.new(page_url, head_first: true).title).to eq('Downloaded page')
73
+
74
+ expect(head_request).to have_been_requested.once
75
+ expect(get_request).to have_been_requested.once
76
+ end
77
+
78
+ [403, 404, 405, 501].each do |status|
79
+ it "continues to GET when HEAD returns #{status}" do
80
+ head_request = stub_request(:head, page_url)
81
+ .to_return(status: status, headers: { 'Content-Type' => 'text/plain' })
82
+
83
+ expect(MetaInspector.new(page_url, head_first: true).title).to eq('Downloaded page')
84
+
85
+ expect(head_request).to have_been_requested.once
86
+ expect(get_request).to have_been_requested.once
87
+ end
88
+ end
89
+
90
+ it 'still rejects a non-HTML GET response when HEAD reports HTML' do
91
+ head_request = stub_request(:head, page_url).to_return(headers: { 'Content-Type' => 'text/html' })
92
+ stub_request(:get, page_url).to_return(headers: { 'Content-Type' => 'application/pdf' })
93
+
94
+ expect do
95
+ MetaInspector.new(page_url, head_first: true)
96
+ end.to raise_error(MetaInspector::NonHtmlError)
97
+
98
+ expect(head_request).to have_been_requested.once
99
+ expect(get_request).to have_been_requested.once
100
+ end
101
+
102
+ it 'does not send requests for a supplied document' do
103
+ expect(MetaInspector.new(page_url, head_first: true, document: html).title).to eq('Downloaded page')
104
+
105
+ expect(a_request(:head, page_url)).not_to have_been_made
106
+ expect(get_request).not_to have_been_requested
107
+ end
108
+
109
+ it 'sends custom headers on both requests' do
110
+ headers = { 'User-Agent' => 'Custom agent', 'Authorization' => 'Bearer test-token' }
111
+ head_request = stub_request(:head, page_url).with(headers: headers)
112
+ .to_return(headers: { 'Content-Type' => 'text/html' })
113
+ get_with_headers = stub_request(:get, page_url).with(headers: headers).to_return(body: html)
114
+
115
+ expect(MetaInspector.new(page_url, head_first: true, headers: headers).title).to eq('Downloaded page')
116
+
117
+ expect(head_request).to have_been_requested.once
118
+ expect(get_with_headers).to have_been_requested.once
119
+ end
120
+
121
+ it 'follows HEAD redirects to reject non-HTML content before GET' do
122
+ target = 'http://example.com/large.pdf'
123
+ stub_request(:head, page_url).to_return(status: 302, headers: { 'Location' => target })
124
+ target_request = stub_request(:head, target).to_return(headers: { 'Content-Type' => 'application/pdf' })
125
+
126
+ expect do
127
+ MetaInspector.new(page_url, head_first: true)
128
+ end.to raise_error(MetaInspector::NonHtmlError)
129
+
130
+ expect(target_request).to have_been_requested.once
131
+ expect(a_request(:get, /example.com/)).not_to have_been_made
132
+ end
133
+
134
+ it 'follows GET redirects independently of HEAD and exposes the final GET URL' do
135
+ head_target = 'http://example.com/head-target'
136
+ get_target = 'http://example.com/get-target'
137
+ head_request = stub_request(:head, page_url).to_return(status: 302, headers: { 'Location' => head_target })
138
+ stub_request(:head, head_target).to_return(headers: { 'Content-Type' => 'text/html' })
139
+ stub_request(:get, page_url).to_return(status: 302, headers: { 'Location' => get_target })
140
+ final_request = stub_request(:get, get_target).to_return(body: html, headers: { 'Content-Type' => 'text/html' })
141
+
142
+ page = MetaInspector.new(page_url, head_first: true)
143
+
144
+ expect(page.title).to eq('Downloaded page')
145
+ expect(page.url).to eq(get_target)
146
+ expect(head_request).to have_been_requested.once
147
+ expect(final_request).to have_been_requested.once
148
+ expect(a_request(:get, head_target)).not_to have_been_made
149
+ end
150
+
151
+ it 'does not follow HEAD redirects when redirections are disabled' do
152
+ head_request = stub_request(:head, page_url)
153
+ .to_return(status: 302, headers: { 'Location' => 'http://example.com/target', 'Content-Type' => 'text/plain' })
154
+
155
+ page = MetaInspector.new(page_url, head_first: true, allow_redirections: false)
156
+
157
+ expect(page.title).to eq('Downloaded page')
158
+ expect(page.url).to eq(page_url)
159
+ expect(head_request).to have_been_requested.once
160
+ expect(a_request(:head, 'http://example.com/target')).not_to have_been_made
161
+ end
162
+
163
+ it 'keeps cookies from HEAD for the GET request' do
164
+ head_request = stub_request(:head, page_url)
165
+ .to_return(headers: { 'Content-Type' => 'text/html', 'Set-Cookie' => 'session=checked; path=/' })
166
+ get_with_cookie = stub_request(:get, page_url).with(headers: { 'Cookie' => 'session=checked' }).to_return(body: html)
167
+
168
+ expect(MetaInspector.new(page_url, head_first: true).title).to eq('Downloaded page')
169
+
170
+ expect(head_request).to have_been_requested.once
171
+ expect(get_with_cookie).to have_been_requested.once
172
+ end
173
+
174
+ it 'wraps HEAD timeouts without sending GET' do
175
+ head_request = stub_request(:head, page_url).to_raise(Net::ReadTimeout)
176
+
177
+ expect do
178
+ MetaInspector.new(page_url, head_first: true, retries: 0)
179
+ end.to raise_error(MetaInspector::TimeoutError)
180
+
181
+ expect(head_request).to have_been_requested.once
182
+ expect(get_request).not_to have_been_requested
183
+ end
184
+
185
+ it 'wraps HEAD connection failures without sending GET' do
186
+ head_request = stub_request(:head, page_url).to_raise(Faraday::ConnectionFailed.new('connection failed'))
187
+
188
+ expect do
189
+ MetaInspector.new(page_url, head_first: true, retries: 0)
190
+ end.to raise_error(MetaInspector::RequestError)
191
+
192
+ expect(head_request).to have_been_requested.once
193
+ expect(get_request).not_to have_been_requested
194
+ end
195
+ end
@@ -70,6 +70,31 @@ describe MetaInspector do
70
70
  end
71
71
 
72
72
  describe "images.best" do
73
+ it 'should prefer the Parsely image over the largest image' do
74
+ html = '<meta name="parsely-image-url" content="/parsely.jpg"><img src="/largest.jpg" width="100" height="100">'
75
+ page = MetaInspector.new('http://example.com/', document: html)
76
+
77
+ expect(page.images.best).to eq('http://example.com/parsely.jpg')
78
+ end
79
+
80
+ ['', ' &#10; '].each do |value|
81
+ it "should fall back to the largest image when the Parsely image is empty (#{value.inspect})" do
82
+ html = %(<meta name="parsely-image-url" content="#{value}"><img src="/largest.jpg" width="100" height="100">)
83
+ page = MetaInspector.new('http://example.com/', document: html)
84
+
85
+ expect(page.images.best).to eq('http://example.com/largest.jpg')
86
+ end
87
+ end
88
+
89
+ it "should find a valid image when another image URL cannot be parsed" do
90
+ html = '<html><body><img src="http://exa mple.com/x.png"><img src="/100x100"></body></html>'
91
+ page = MetaInspector.new('http://example.com/', document: html)
92
+
93
+ allow(FastImage).to receive(:size).and_call_original
94
+ expect(FastImage).not_to receive(:size).with(nil)
95
+ expect(page.images.best).to eq("http://example.com/100x100")
96
+ end
97
+
73
98
  it "should find the og image" do
74
99
  page = MetaInspector.new('http://www.theonion.com/articles/apple-claims-new-iphone-only-visible-to-most-loyal,2772/')
75
100
 
@@ -103,6 +128,44 @@ describe MetaInspector do
103
128
  end
104
129
 
105
130
  describe "images.owner_suggested" do
131
+ it 'should find the Parsely image when no existing metadata is present' do
132
+ html = '<meta name="parsely-image-url" content="https://images.example.com/parsely.jpg?b=2&amp;a=1">'
133
+ page = MetaInspector.new('http://example.com/', document: html)
134
+
135
+ expect(page.images.owner_suggested).to eq('https://images.example.com/parsely.jpg?b=2&a=1')
136
+ end
137
+
138
+ it 'should resolve a relative Parsely image against the document base URL' do
139
+ html = '<base href="/assets/"><meta name="parsely-image-url" content="parsely.jpg">'
140
+ page = MetaInspector.new('http://example.com/articles/page', document: html)
141
+
142
+ expect(page.images.owner_suggested).to eq('http://example.com/assets/parsely.jpg')
143
+ end
144
+
145
+ it 'should use the Parsely image when existing metadata is empty' do
146
+ html = '<meta property="og:image" content=""><meta name="twitter:image" content=""><meta name="parsely-image-url" content="/parsely.jpg">'
147
+ page = MetaInspector.new('http://example.com/', document: html)
148
+
149
+ expect(page.images.owner_suggested).to eq('http://example.com/parsely.jpg')
150
+ end
151
+
152
+ ['og:image', 'twitter:image'].each do |candidate|
153
+ it "should prefer #{candidate} over the Parsely image" do
154
+ html = %(<meta name="parsely-image-url" content="/parsely.jpg"><meta property="#{candidate}" content="/existing.jpg">)
155
+ page = MetaInspector.new('http://example.com/', document: html)
156
+
157
+ expect(page.images.owner_suggested).to eq('http://example.com/existing.jpg')
158
+ end
159
+ end
160
+
161
+ ['', ' &#10; '].each do |value|
162
+ it "should ignore an empty Parsely image (#{value.inspect})" do
163
+ page = MetaInspector.new('http://example.com/', document: %(<meta name="parsely-image-url" content="#{value}">))
164
+
165
+ expect(page.images.owner_suggested).to be_nil
166
+ end
167
+ end
168
+
106
169
  it "should find the og image" do
107
170
  page = MetaInspector.new('http://www.theonion.com/articles/apple-claims-new-iphone-only-visible-to-most-loyal,2772/')
108
171
 
@@ -135,6 +198,176 @@ describe MetaInspector do
135
198
  end
136
199
 
137
200
  describe "images.with_size" do
201
+ describe "max_image_downloads" do
202
+ let(:html) do
203
+ '<img src="/10x10"><img src="/100x100"><img src="/remaining.jpg">'
204
+ end
205
+
206
+ before do
207
+ stub_request(:get, 'http://example.com/remaining.jpg').to_return(status: 404)
208
+ end
209
+
210
+ it "should download only the first images needing dimensions in document order" do
211
+ page = MetaInspector.new('http://example.com/', document: html, max_image_downloads: 2)
212
+
213
+ expect(page.images.with_size).to eq([
214
+ ['http://example.com/100x100', 100, 100],
215
+ ['http://example.com/10x10', 10, 10],
216
+ ['http://example.com/remaining.jpg', 0, 0]
217
+ ])
218
+ expect(WebMock).to have_requested(:get, 'http://example.com/10x10').once
219
+ expect(WebMock).to have_requested(:get, 'http://example.com/100x100').once
220
+ expect(WebMock).not_to have_requested(:get, 'http://example.com/remaining.jpg')
221
+ end
222
+
223
+ it "should preserve HTML dimensions without consuming the download budget" do
224
+ html = '<img src="/known.jpg" width="200" height="200"><img src="/10x10"><img src="/100x100"><img src="/later.jpg" width="300" height="300">'
225
+ page = MetaInspector.new('http://example.com/', document: html, max_image_downloads: 1)
226
+
227
+ expect(page.images.with_size).to eq([
228
+ ['http://example.com/later.jpg', 300, 300],
229
+ ['http://example.com/known.jpg', 200, 200],
230
+ ['http://example.com/10x10', 10, 10],
231
+ ['http://example.com/100x100', 0, 0]
232
+ ])
233
+ expect(WebMock).to have_requested(:get, 'http://example.com/10x10').once
234
+ expect(WebMock).not_to have_requested(:get, 'http://example.com/100x100')
235
+ end
236
+
237
+ it "should exclude invalid and duplicate URLs before consuming the budget" do
238
+ html = '<img src="http://exa mple.com/broken.jpg"><img src="/10x10"><img src="http://example.com/10x10"><img src="/100x100"><img src="/remaining.jpg">'
239
+ page = MetaInspector.new('http://example.com/', document: html, max_image_downloads: 2)
240
+
241
+ expect(page.images.with_size).to eq([
242
+ ['http://example.com/100x100', 100, 100],
243
+ ['http://example.com/10x10', 10, 10],
244
+ ['http://example.com/remaining.jpg', 0, 0]
245
+ ])
246
+ expect(WebMock).to have_requested(:get, 'http://example.com/10x10').once
247
+ expect(WebMock).to have_requested(:get, 'http://example.com/100x100').once
248
+ expect(WebMock).not_to have_requested(:get, 'http://example.com/remaining.jpg')
249
+ end
250
+
251
+ it "should treat an image missing one HTML dimension as needing a download" do
252
+ html = '<img src="/10x10" width="500"><img src="/100x100" height="500">'
253
+ page = MetaInspector.new('http://example.com/', document: html, max_image_downloads: 1)
254
+
255
+ expect(page.images.with_size).to eq([
256
+ ['http://example.com/10x10', 10, 10],
257
+ ['http://example.com/100x100', 0, 0]
258
+ ])
259
+ expect(WebMock).to have_requested(:get, 'http://example.com/10x10').once
260
+ expect(WebMock).not_to have_requested(:get, 'http://example.com/100x100')
261
+ end
262
+
263
+ it "should disable downloads with a zero limit while preserving HTML dimensions" do
264
+ html = '<img src="/10x10"><img src="/known.jpg" width="200" height="100">'
265
+ page = MetaInspector.new('http://example.com/', document: html, max_image_downloads: 0)
266
+
267
+ expect(page.images.with_size).to eq([
268
+ ['http://example.com/known.jpg', 200, 100],
269
+ ['http://example.com/10x10', 0, 0]
270
+ ])
271
+ expect(WebMock).not_to have_requested(:get, 'http://example.com/10x10')
272
+ end
273
+
274
+ it "should respect download_images: false even with a positive limit" do
275
+ page = MetaInspector.new('http://example.com/', document: html, download_images: false, max_image_downloads: 2)
276
+
277
+ expect(page.images.with_size).to eq([
278
+ ['http://example.com/10x10', 0, 0],
279
+ ['http://example.com/100x100', 0, 0],
280
+ ['http://example.com/remaining.jpg', 0, 0]
281
+ ])
282
+ expect(WebMock).not_to have_requested(:get, %r{http://example.com/})
283
+ end
284
+
285
+ [nil, 10].each do |limit|
286
+ it "should download every candidate when the limit is #{limit.inspect}" do
287
+ page = MetaInspector.new('http://example.com/', document: html, max_image_downloads: limit)
288
+
289
+ expect(page.images.with_size).to eq([
290
+ ['http://example.com/100x100', 100, 100],
291
+ ['http://example.com/10x10', 10, 10],
292
+ ['http://example.com/remaining.jpg', 0, 0]
293
+ ])
294
+ expect(WebMock).to have_requested(:get, 'http://example.com/10x10').once
295
+ expect(WebMock).to have_requested(:get, 'http://example.com/100x100').once
296
+ expect(WebMock).to have_requested(:get, 'http://example.com/remaining.jpg').once
297
+ end
298
+ end
299
+
300
+ [:not_found, :invalid_image, :timeout].each do |failure|
301
+ it "should count a #{failure} image attempt toward the limit" do
302
+ request = stub_request(:get, 'http://example.com/broken.jpg')
303
+ case failure
304
+ when :not_found then request.to_return(status: 404)
305
+ when :invalid_image then request.to_return(body: 'not an image')
306
+ when :timeout then request.to_timeout
307
+ end
308
+ html = '<img src="/broken.jpg"><img src="/100x100">'
309
+ page = MetaInspector.new('http://example.com/', document: html, max_image_downloads: 1)
310
+
311
+ expect(page.images.with_size).to eq([
312
+ ['http://example.com/broken.jpg', 0, 0],
313
+ ['http://example.com/100x100', 0, 0]
314
+ ])
315
+ expect(request).to have_been_requested.once
316
+ expect(WebMock).not_to have_requested(:get, 'http://example.com/100x100')
317
+ end
318
+ end
319
+
320
+ it "should reuse the limited results for repeated size and best-image queries" do
321
+ page = MetaInspector.new('http://example.com/', document: html, max_image_downloads: 1)
322
+
323
+ 2.times do
324
+ expect(page.images.with_size.first).to eq(['http://example.com/10x10', 10, 10])
325
+ expect(page.images.largest).to eq('http://example.com/10x10')
326
+ expect(page.images.best).to eq('http://example.com/10x10')
327
+ end
328
+ expect(WebMock).to have_requested(:get, 'http://example.com/10x10').once
329
+ expect(WebMock).not_to have_requested(:get, 'http://example.com/100x100')
330
+ end
331
+
332
+ it "should keep the budget local to each document" do
333
+ 2.times do
334
+ page = MetaInspector.new('http://example.com/', document: html, max_image_downloads: 1)
335
+
336
+ expect(page.images.with_size.first).to eq(['http://example.com/10x10', 10, 10])
337
+ end
338
+ expect(WebMock).to have_requested(:get, 'http://example.com/10x10').twice
339
+ expect(WebMock).not_to have_requested(:get, 'http://example.com/100x100')
340
+ end
341
+
342
+ it "should keep all image URLs without downloading them when enumerating images" do
343
+ page = MetaInspector.new('http://example.com/', document: html, max_image_downloads: 1)
344
+
345
+ expect(page.images.to_a).to eq([
346
+ 'http://example.com/10x10',
347
+ 'http://example.com/100x100',
348
+ 'http://example.com/remaining.jpg'
349
+ ])
350
+ expect(WebMock).not_to have_requested(:get, %r{http://example.com/})
351
+ end
352
+ end
353
+
354
+ [true, false].each do |download_images|
355
+ it "should ignore unparseable image URLs with download_images: #{download_images}" do
356
+ html = '<html><body><img src="http://exa mple.com/x.png"><img src="/valid.png" width="100" height="100"></body></html>'
357
+ page = MetaInspector.new('https://example.com/', document: html, download_images: download_images)
358
+
359
+ expect(FastImage).not_to receive(:size).with(nil)
360
+ expect(page.images.with_size).to eq([["https://example.com/valid.png", 100, 100]])
361
+ end
362
+ end
363
+
364
+ it "should ignore unparseable image URLs even when HTML sizes are present" do
365
+ html = '<html><body><img src="http://exa mple.com/x.png" width="100" height="100"></body></html>'
366
+ page = MetaInspector.new('https://example.com/', document: html)
367
+
368
+ expect(page.images.with_size).to eq([])
369
+ end
370
+
138
371
  it "should return sorted by area array of [img_url, width, height] using html sizes" do
139
372
  page = MetaInspector.new('http://example.com/largest_image_in_html')
140
373
 
@@ -174,6 +174,28 @@ describe MetaInspector do
174
174
  end
175
175
 
176
176
  describe 'Relative links with relative base' do
177
+ [
178
+ ['assets/', 'https://example.com/posts/article', 'https://example.com/posts/assets/next.html'],
179
+ ['../assets/', 'https://example.com/posts/2026/article', 'https://example.com/posts/assets/next.html'],
180
+ ['assets/base.html', 'https://example.com/posts/article', 'https://example.com/posts/assets/next.html'],
181
+ ['assets/', 'https://example.com/posts/article/', 'https://example.com/posts/article/assets/next.html'],
182
+ ['/assets/', 'https://example.com:8443/posts/article', 'https://example.com:8443/assets/next.html']
183
+ ].each do |base_href, url, expected_link|
184
+ it "resolves base #{base_href.inspect} against #{url}" do
185
+ html = %(<html><head><base href="#{base_href}"></head><body><a href="next.html">Next</a></body></html>)
186
+ m = MetaInspector.new(url, document: html)
187
+
188
+ expect(m.links.all).to eq([expected_link])
189
+ end
190
+ end
191
+
192
+ it 'resolves image URLs against a document-relative base in a nested directory' do
193
+ html = '<html><head><base href="assets/"></head><body><img src="image.jpg"></body></html>'
194
+ m = MetaInspector.new('https://example.com/posts/article', document: html)
195
+
196
+ expect(m.images.to_a).to eq(['https://example.com/posts/assets/image.jpg'])
197
+ end
198
+
177
199
  it 'should get the relative links with relative directory base' do
178
200
  m = MetaInspector.new('http://relativewithrelativebase.com/relativedir')
179
201
  expect(m.links.all).to eq(['http://relativewithrelativebase.com/other/about',
@@ -52,22 +52,36 @@ describe MetaInspector do
52
52
  end
53
53
 
54
54
  context "when there is a callback to be ran between redirects that blocks redirections to private IP addresses" do
55
- it "raises an exception" do
56
- stub_request(:get, "https://www.facebook.com/")
57
- .to_return(:status => 302,
58
- :headers => { "Location" => "http://10.0.0.0/" })
59
-
60
- redirect_options = {
55
+ let(:redirect_options) do
56
+ {
61
57
  callback: proc do |_previous_response, next_request|
62
58
  ip_address = Resolv.getaddress(next_request.url.host)
63
59
  raise PrivateIPAddressError if IPAddr.new(ip_address).private?
64
60
  end
65
61
  }
62
+ end
63
+
64
+ before do
65
+ stub_request(:get, "https://www.facebook.com/")
66
+ .to_return(:status => 302,
67
+ :headers => { "Location" => "http://10.0.0.0/" })
68
+ end
66
69
 
70
+ it "raises an exception" do
67
71
  expect {
68
72
  MetaInspector.new("https://www.facebook.com/", faraday_options: { redirect: redirect_options })
69
73
  }.to raise_error PrivateIPAddressError
70
74
  end
75
+
76
+ it "keeps running the callback when the same faraday_options are reused" do
77
+ faraday_options = { redirect: redirect_options }
78
+
79
+ 2.times do
80
+ expect {
81
+ MetaInspector.new("https://www.facebook.com/", faraday_options: faraday_options)
82
+ }.to raise_error PrivateIPAddressError
83
+ end
84
+ end
71
85
  end
72
86
  end
73
87
 
@@ -1,6 +1,50 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe MetaInspector do
4
+ describe '#language' do
5
+ ['en', 'en-US', 'zh-Hant-TW'].each do |language|
6
+ it "should return the declared language #{language.inspect}" do
7
+ page = MetaInspector.new('http://example.com/', document: %(<html lang="#{language}"><body></body></html>))
8
+
9
+ expect(page.language).to eq(language)
10
+ end
11
+ end
12
+
13
+ it 'should strip surrounding whitespace from the declared language' do
14
+ page = MetaInspector.new('http://example.com/', document: "<html lang=\" \t en-US \n \"><body></body></html>")
15
+
16
+ expect(page.language).to eq('en-US')
17
+ end
18
+
19
+ [nil, '', " \t\n "].each do |value|
20
+ it "should return nil for a missing or blank language (#{value.inspect})" do
21
+ attribute = value.nil? ? '' : %( lang="#{value}")
22
+ page = MetaInspector.new('http://example.com/', document: "<html#{attribute}><body></body></html>")
23
+
24
+ expect(page.language).to be_nil
25
+ end
26
+ end
27
+
28
+ it 'should read the root language rather than a descendant language' do
29
+ page = MetaInspector.new('http://example.com/', document: '<html lang="en"><body lang="es"></body></html>')
30
+
31
+ expect(page.language).to eq('en')
32
+ end
33
+
34
+ it 'should not infer language from xml:lang, metadata, or descendants' do
35
+ html = '<html xml:lang="fr"><head><meta http-equiv="content-language" content="de"></head><body lang="es">Hello</body></html>'
36
+ page = MetaInspector.new('http://example.com/', document: html)
37
+
38
+ expect(page.language).to be_nil
39
+ end
40
+
41
+ it 'should return the declared language from a fetched document' do
42
+ page = MetaInspector.new('http://pagerankalert.com/')
43
+
44
+ expect(page.language).to eq('en')
45
+ end
46
+ end
47
+
4
48
  it "should get the title from the head section" do
5
49
  page = MetaInspector.new('http://example.com')
6
50
  expect(page.title).to eq('An example page')
@@ -49,6 +93,42 @@ describe MetaInspector do
49
93
  end
50
94
 
51
95
  describe '#best_title' do
96
+ it 'should use the Parsely title when no existing candidate is present' do
97
+ page = MetaInspector.new('http://example.com/', document: '<meta name="parsely-title" content=" A Parsely&#10; title ">')
98
+
99
+ expect(page.best_title).to eq('A Parsely title')
100
+ end
101
+
102
+ it 'should use the Parsely title when existing candidates are empty' do
103
+ html = '<html><head><meta name="title" content=" "><meta property="og:title" content=""><title> </title><meta name="parsely-title" content="Parsely title"></head><body><h1> </h1></body></html>'
104
+ page = MetaInspector.new('http://example.com/', document: html)
105
+
106
+ expect(page.best_title).to eq('Parsely title')
107
+ end
108
+
109
+ [
110
+ ['<meta name="title" content="Existing title">', ''],
111
+ ['<meta property="og:title" content="Existing title">', ''],
112
+ ['<title>Existing title</title>', ''],
113
+ ['', '<title>Existing title</title>'],
114
+ ['', '<h1>Existing title</h1>']
115
+ ].each do |head, body|
116
+ it "should prefer #{head + body} over the Parsely title" do
117
+ html = %(<html><head><meta name="parsely-title" content="Parsely title">#{head}</head><body>#{body}</body></html>)
118
+ page = MetaInspector.new('http://example.com/', document: html)
119
+
120
+ expect(page.best_title).to eq('Existing title')
121
+ end
122
+ end
123
+
124
+ ['', ' &#10; '].each do |value|
125
+ it "should ignore an empty Parsely title (#{value.inspect})" do
126
+ page = MetaInspector.new('http://example.com/', document: %(<meta name="parsely-title" content="#{value}">))
127
+
128
+ expect(page.best_title).to be_nil
129
+ end
130
+ end
131
+
52
132
  it "should find 'head title' when that's the only thing" do
53
133
  page = MetaInspector.new('http://example.com/title_in_head')
54
134
  expect(page.best_title).to eq('This title came from the head')
@@ -105,6 +185,41 @@ describe MetaInspector do
105
185
  end
106
186
 
107
187
  describe "#best_author" do
188
+ it 'should use the Parsely author when no existing candidate is present' do
189
+ page = MetaInspector.new('http://example.com/', document: '<meta name="parsely-author" content=" A Parsely&#10; author ">')
190
+
191
+ expect(page.best_author).to eq('A Parsely author')
192
+ end
193
+
194
+ it 'should use the Parsely author when existing candidates are empty' do
195
+ html = '<meta name="author" content=" "><meta name="twitter:creator" content=""><meta name="parsely-author" content="Parsely author"><a rel="author"> </a><address> </address>'
196
+ page = MetaInspector.new('http://example.com/', document: html)
197
+
198
+ expect(page.best_author).to eq('Parsely author')
199
+ end
200
+
201
+ [
202
+ '<meta name="author" content="Existing author">',
203
+ '<a rel="author">Existing author</a>',
204
+ '<address>Existing author</address>',
205
+ '<meta name="twitter:creator" content="Existing author">'
206
+ ].each do |candidate|
207
+ it "should prefer #{candidate} over the Parsely author" do
208
+ html = '<meta name="parsely-author" content="Parsely author">' + candidate
209
+ page = MetaInspector.new('http://example.com/', document: html)
210
+
211
+ expect(page.best_author).to eq('Existing author')
212
+ end
213
+ end
214
+
215
+ ['', ' &#10; '].each do |value|
216
+ it "should ignore an empty Parsely author (#{value.inspect})" do
217
+ page = MetaInspector.new('http://example.com/', document: %(<meta name="parsely-author" content="#{value}">))
218
+
219
+ expect(page.best_author).to be_nil
220
+ end
221
+ end
222
+
108
223
  it "should return the author meta tag content if present" do
109
224
  page = MetaInspector.new('http://example.com/author_in_meta')
110
225