my_pdfkit 0.1.0.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,531 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ def app; Rack::Lint.new(@app); end
6
+
7
+ def mock_app(options = {}, conditions = {}, custom_headers = {})
8
+ main_app = lambda { |env|
9
+ @env = env
10
+ full_headers = headers.merge custom_headers
11
+ [200, full_headers, @body || ['Hello world!']]
12
+ }
13
+
14
+ builder = Rack::Builder.new
15
+ builder.use MyPDFKit::Middleware, options, conditions
16
+ builder.run main_app
17
+ @app = builder.to_app
18
+ end
19
+
20
+ describe MyPDFKit::Middleware do
21
+ let(:headers) do
22
+ {'content-type' => "text/html"}
23
+ end
24
+
25
+ describe "#call" do
26
+
27
+ describe 'threadsafety' do
28
+ before { mock_app }
29
+ it 'is threadsafe' do
30
+ n = 30
31
+ extensions = Array.new(n) { rand > 0.5 ? 'html' : 'pdf' }
32
+ actual_content_types = Hash.new
33
+
34
+ threads = (0...n).map { |i|
35
+ Thread.new do
36
+ resp = get("http://www.example.org/public/test.#{extensions[i]}")
37
+ actual_content_types[i] = resp.content_type
38
+ end
39
+ }
40
+
41
+ threads.each(&:join)
42
+
43
+ extensions.each_with_index do |extension, index|
44
+ result = actual_content_types[index]
45
+ case extension
46
+ when 'html', 'txt', 'csv'
47
+ expect(result).to eq("text/#{extension}")
48
+ when 'pdf'
49
+ expect(result).to eq('application/pdf')
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ describe "caching" do
56
+ let(:headers) do
57
+ {
58
+ 'content-type' => "text/html",
59
+ 'etag' => 'foo',
60
+ 'cache-control' => 'max-age=2592000, public'
61
+ }
62
+ end
63
+
64
+ context "by default" do
65
+ before { mock_app }
66
+
67
+ it "deletes etag" do
68
+ get 'http://www.example.org/public/test.pdf'
69
+ expect(last_response.headers["etag"]).to be_nil
70
+ end
71
+ it "deletes cache-control" do
72
+ get 'http://www.example.org/public/test.pdf'
73
+ expect(last_response.headers["cache-control"]).to be_nil
74
+ end
75
+ end
76
+
77
+ context "when on" do
78
+ before { mock_app({}, :caching => true) }
79
+
80
+ it "preserves etag" do
81
+ get 'http://www.example.org/public/test.pdf'
82
+ expect(last_response.headers["etag"]).not_to be_nil
83
+ end
84
+
85
+ it "preserves cache-control" do
86
+ get 'http://www.example.org/public/test.pdf'
87
+ expect(last_response.headers["cache-control"]).not_to be_nil
88
+ end
89
+ end
90
+ end
91
+
92
+ describe "conditions" do
93
+ describe ":only" do
94
+
95
+ describe "regex" do
96
+ describe "one" do
97
+ before { mock_app({}, :only => %r[^/public]) }
98
+
99
+ context "matching" do
100
+ specify do
101
+ get 'http://www.example.org/public/test.pdf'
102
+ expect(last_response.headers["content-type"]).to eq("application/pdf")
103
+ expect(last_response.body.bytesize).to eq(MyPDFKit.new("Hello world!").to_pdf.bytesize)
104
+ end
105
+ end
106
+
107
+ context "not matching" do
108
+ specify do
109
+ get 'http://www.example.org/secret/test.pdf'
110
+ expect(last_response.headers["content-type"]).to eq("text/html")
111
+ expect(last_response.body).to eq("Hello world!")
112
+ end
113
+ end
114
+ end # one regex
115
+
116
+ describe "multiple" do
117
+ before { mock_app({}, :only => [%r[^/invoice], %r[^/public]]) }
118
+
119
+ context "matching" do
120
+ specify do
121
+ get 'http://www.example.org/public/test.pdf'
122
+ expect(last_response.headers["content-type"]).to eq("application/pdf")
123
+ expect(last_response.body.bytesize).to eq(MyPDFKit.new("Hello world!").to_pdf.bytesize)
124
+ end
125
+ end
126
+
127
+ context "not matching" do
128
+ specify do
129
+ get 'http://www.example.org/secret/test.pdf'
130
+ expect(last_response.headers["content-type"]).to eq("text/html")
131
+ expect(last_response.body).to eq("Hello world!")
132
+ end
133
+ end
134
+ end # multiple regex
135
+ end # regex
136
+
137
+ describe "string" do
138
+ describe "one" do
139
+ before { mock_app({}, :only => '/public') }
140
+
141
+ context "matching" do
142
+ specify do
143
+ get 'http://www.example.org/public/test.pdf'
144
+ expect(last_response.headers["content-type"]).to eq("application/pdf")
145
+ expect(last_response.body.bytesize).to eq(MyPDFKit.new("Hello world!").to_pdf.bytesize)
146
+ end
147
+ end
148
+
149
+ context "not matching" do
150
+ specify do
151
+ get 'http://www.example.org/secret/test.pdf'
152
+ expect(last_response.headers["content-type"]).to eq("text/html")
153
+ expect(last_response.body).to eq("Hello world!")
154
+ end
155
+ end
156
+ end # one string
157
+
158
+ describe "multiple" do
159
+ before { mock_app({}, :only => ['/invoice', '/public']) }
160
+
161
+ context "matching" do
162
+ specify do
163
+ get 'http://www.example.org/public/test.pdf'
164
+ expect(last_response.headers["content-type"]).to eq("application/pdf")
165
+ expect(last_response.body.bytesize).to eq(MyPDFKit.new("Hello world!").to_pdf.bytesize)
166
+ end
167
+ end
168
+
169
+ context "not matching" do
170
+ specify do
171
+ get 'http://www.example.org/secret/test.pdf'
172
+ expect(last_response.headers["content-type"]).to eq("text/html")
173
+ expect(last_response.body).to eq("Hello world!")
174
+ end
175
+ end
176
+ end # multiple string
177
+ end # string
178
+
179
+ end
180
+
181
+ describe ":except" do
182
+
183
+ describe "regex" do
184
+ describe "one" do
185
+ before { mock_app({}, :except => %r[^/secret]) }
186
+
187
+ context "matching" do
188
+ specify do
189
+ get 'http://www.example.org/public/test.pdf'
190
+ expect(last_response.headers["content-type"]).to eq("application/pdf")
191
+ expect(last_response.body.bytesize).to eq(MyPDFKit.new("Hello world!").to_pdf.bytesize)
192
+ end
193
+ end
194
+
195
+ context "not matching" do
196
+ specify do
197
+ get 'http://www.example.org/secret/test.pdf'
198
+ expect(last_response.headers["content-type"]).to eq("text/html")
199
+ expect(last_response.body).to eq("Hello world!")
200
+ end
201
+ end
202
+ end # one regex
203
+
204
+ describe "multiple" do
205
+ before { mock_app({}, :except => [%r[^/prawn], %r[^/secret]]) }
206
+
207
+ context "matching" do
208
+ specify do
209
+ get 'http://www.example.org/public/test.pdf'
210
+ expect(last_response.headers["content-type"]).to eq("application/pdf")
211
+ expect(last_response.body.bytesize).to eq(MyPDFKit.new("Hello world!").to_pdf.bytesize)
212
+ end
213
+ end
214
+
215
+ context "not matching" do
216
+ specify do
217
+ get 'http://www.example.org/secret/test.pdf'
218
+ expect(last_response.headers["content-type"]).to eq("text/html")
219
+ expect(last_response.body).to eq("Hello world!")
220
+ end
221
+ end
222
+ end # multiple regex
223
+ end # regex
224
+
225
+ describe "string" do
226
+ describe "one" do
227
+ before { mock_app({}, :except => '/secret') }
228
+
229
+ context "matching" do
230
+ specify do
231
+ get 'http://www.example.org/public/test.pdf'
232
+ expect(last_response.headers["content-type"]).to eq("application/pdf")
233
+ expect(last_response.body.bytesize).to eq(MyPDFKit.new("Hello world!").to_pdf.bytesize)
234
+ end
235
+ end
236
+
237
+ context "not matching" do
238
+ specify do
239
+ get 'http://www.example.org/secret/test.pdf'
240
+ expect(last_response.headers["content-type"]).to eq("text/html")
241
+ expect(last_response.body).to eq("Hello world!")
242
+ end
243
+ end
244
+ end # one string
245
+
246
+ describe "multiple" do
247
+ before { mock_app({}, :except => ['/prawn', '/secret']) }
248
+
249
+ context "matching" do
250
+ specify do
251
+ get 'http://www.example.org/public/test.pdf'
252
+ expect(last_response.headers["content-type"]).to eq("application/pdf")
253
+ expect(last_response.body.bytesize).to eq(MyPDFKit.new("Hello world!").to_pdf.bytesize)
254
+ end
255
+ end
256
+
257
+ context "not matching" do
258
+ specify do
259
+ get 'http://www.example.org/secret/test.pdf'
260
+ expect(last_response.headers["content-type"]).to eq("text/html")
261
+ expect(last_response.body).to eq("Hello world!")
262
+ end
263
+ end
264
+ end # multiple string
265
+ end # string
266
+
267
+ end
268
+
269
+ describe "saving generated pdf to disk" do
270
+ before do
271
+ #make sure tests don't find an old test_save.pdf
272
+ File.delete('spec/test_save.pdf') if File.exist?('spec/test_save.pdf')
273
+ expect(File.exist?('spec/test_save.pdf')).to eq(false)
274
+ end
275
+
276
+ context "when header MyPDFKit-save-pdf is present" do
277
+ it "saves the .pdf to disk" do
278
+ headers = { 'MyPDFKit-save-pdf' => 'spec/test_save.pdf' }
279
+ mock_app({}, {only: '/public'}, headers)
280
+ get 'http://www.example.org/public/test_save.pdf'
281
+ expect(File.exist?('spec/test_save.pdf')).to eq(true)
282
+ end
283
+
284
+ it "does not raise when target directory does not exist" do
285
+ headers = { 'MyPDFKit-save-pdf' => '/this/dir/does/not/exist/spec/test_save.pdf' }
286
+ mock_app({}, {only: '/public'}, headers)
287
+ expect {
288
+ get 'http://www.example.com/public/test_save.pdf'
289
+ }.not_to raise_error
290
+ end
291
+ end
292
+
293
+ context "when header MyPDFKit-save-pdf is not present" do
294
+ it "does not saved the .pdf to disk" do
295
+ mock_app({}, {only: '/public'}, {} )
296
+ get 'http://www.example.org/public/test_save.pdf'
297
+ expect(File.exist?('spec/test_save.pdf')).to eq(false)
298
+ end
299
+ end
300
+ end
301
+
302
+ describe 'javascript delay' do
303
+ context 'when header MyPDFKit-javascript-delay is present' do
304
+ it 'passes header value through to MyPDFKit initialiser' do
305
+ expect(MyPDFKit).to receive(:new).with('Hello world!', {
306
+ root_url: 'http://www.example.com/', protocol: 'http', javascript_delay: 4321
307
+ }).and_call_original
308
+
309
+ headers = { 'MyPDFKit-javascript-delay' => '4321' }
310
+ mock_app({}, { only: '/public' }, headers)
311
+ get 'http://www.example.com/public/test_save.pdf'
312
+ end
313
+
314
+ it 'handles invalid content in header' do
315
+ expect(MyPDFKit).to receive(:new).with('Hello world!', {
316
+ root_url: 'http://www.example.com/', protocol: 'http', javascript_delay: 0
317
+ }).and_call_original
318
+
319
+ headers = { 'MyPDFKit-javascript-delay' => 'invalid' }
320
+ mock_app({}, { only: '/public' }, headers)
321
+ get 'http://www.example.com/public/test_save.pdf'
322
+ end
323
+
324
+ it 'overrides default option' do
325
+ expect(MyPDFKit).to receive(:new).with('Hello world!', {
326
+ root_url: 'http://www.example.com/', protocol: 'http', javascript_delay: 4321
327
+ }).and_call_original
328
+
329
+ headers = { 'MyPDFKit-javascript-delay' => '4321' }
330
+ mock_app({ javascript_delay: 1234 }, { only: '/public' }, headers)
331
+ get 'http://www.example.com/public/test_save.pdf'
332
+ end
333
+ end
334
+
335
+ context 'when header MyPDFKit-javascript-delay is not present' do
336
+ it 'passes through default option' do
337
+ expect(MyPDFKit).to receive(:new).with('Hello world!', {
338
+ root_url: 'http://www.example.com/', protocol: 'http', javascript_delay: 1234
339
+ }).and_call_original
340
+
341
+ mock_app({ javascript_delay: 1234 }, { only: '/public' }, { })
342
+ get 'http://www.example.com/public/test_save.pdf'
343
+ end
344
+ end
345
+ end
346
+
347
+ describe ":disposition" do
348
+ describe "doesn't overwrite existing value" do
349
+ let(:headers) do
350
+ super().merge({
351
+ 'content-disposition' => 'attachment; filename=report-20200101.pdf'
352
+ })
353
+ end
354
+
355
+ specify do
356
+ mock_app({}, { :disposition => 'inline' })
357
+ get 'http://www.example.org/public/test.pdf'
358
+ expect(last_response.headers["content-disposition"]).to eq('attachment; filename=report-20200101.pdf')
359
+ end
360
+ end
361
+
362
+ describe "inline or blank" do
363
+ context "default" do
364
+ specify do
365
+ mock_app
366
+ get 'http://www.example.org/public/test.pdf'
367
+ expect(last_response.headers["content-disposition"]).to eq("inline")
368
+ end
369
+ end
370
+
371
+ context "inline" do
372
+ specify do
373
+ mock_app({}, { :disposition => 'inline' })
374
+ get 'http://www.example.org/public/test.pdf'
375
+ expect(last_response.headers["content-disposition"]).to eq("inline")
376
+ end
377
+ end
378
+ end
379
+
380
+ describe "attachment" do
381
+ context "attachment" do
382
+ specify do
383
+ mock_app({}, { :disposition => 'attachment' })
384
+ get 'http://www.example.org/public/test.pdf'
385
+ expect(last_response.headers["content-disposition"]).to eq("attachment")
386
+ end
387
+ end
388
+
389
+ context "attachment with filename" do
390
+ specify do
391
+ mock_app({}, { :disposition => 'attachment; filename=report.pdf' })
392
+ get 'http://www.example.org/public/test.pdf'
393
+ expect(last_response.headers["content-disposition"]).to eq("attachment; filename=report.pdf")
394
+ end
395
+ end
396
+ end
397
+ end
398
+
399
+ describe "error handling" do
400
+ let(:error) { StandardError.new("Something went wrong") }
401
+
402
+ context "errors raised by PDF generation" do
403
+ specify do
404
+ mock_app
405
+ allow(MyPDFKit).to receive(:new).and_raise(error)
406
+ get 'http://www.example.org/public/test.pdf'
407
+ expect(last_response.status).to eq(500)
408
+ expect(last_response.body).to eq(error.message)
409
+ end
410
+ end
411
+
412
+ context "errors raised upstream" do
413
+ specify do
414
+ mock_app
415
+ allow(@app).to receive(:call).and_raise(error)
416
+
417
+ expect {
418
+ get 'http://www.example.org/public/test.pdf'
419
+ }.to raise_error(error)
420
+ end
421
+ end
422
+ end
423
+ end
424
+
425
+ describe "content type header" do
426
+ before { mock_app }
427
+
428
+ context "lower case" do
429
+ specify "header gets correctly updated" do
430
+ get 'http://www.example.org/public/test.pdf'
431
+ expect(last_response.headers["content-type"]).to eq("application/pdf")
432
+ end
433
+ end
434
+
435
+ context "mixed case" do
436
+ let(:headers) do
437
+ {'Content-Type' => "text/html"}
438
+ end
439
+
440
+ specify "header gets correctly updated" do
441
+ pending("this test only applies to rack 2.x and is rejected by rack 3.x") if Rack.release >= "3.0.0"
442
+ get 'http://www.example.org/public/test.pdf'
443
+ expect(last_response.headers["Content-Type"]).to eq("application/pdf")
444
+ end
445
+ end
446
+ end
447
+
448
+ describe "remove .pdf from PATH_INFO and REQUEST_URI" do
449
+ before { mock_app }
450
+
451
+ context "matching" do
452
+
453
+ specify do
454
+ get 'http://www.example.org/public/file.pdf'
455
+ expect(@env["PATH_INFO"]).to eq("/public/file")
456
+ expect(@env["REQUEST_URI"]).to eq("/public/file")
457
+ expect(@env["SCRIPT_NAME"]).to be_empty
458
+ end
459
+ specify do
460
+ get 'http://www.example.org/public/file.txt'
461
+ expect(@env["PATH_INFO"]).to eq("/public/file.txt")
462
+ expect(@env["REQUEST_URI"]).to be_nil
463
+ expect(@env["SCRIPT_NAME"]).to be_empty
464
+ end
465
+ end
466
+
467
+ context "subdomain matching" do
468
+ before do
469
+ main_app = lambda { |env|
470
+ @env = env
471
+ @env['SCRIPT_NAME'] = '/example.org'
472
+ headers = {'content-type' => "text/html"}
473
+ [200, headers, @body || ['Hello world!']]
474
+ }
475
+
476
+ builder = Rack::Builder.new
477
+ builder.use MyPDFKit::Middleware
478
+ builder.run main_app
479
+ @app = builder.to_app
480
+ end
481
+ specify do
482
+ get 'http://example.org/sub/public/file.pdf'
483
+ expect(@env["PATH_INFO"]).to eq("/sub/public/file")
484
+ expect(@env["REQUEST_URI"]).to eq("/sub/public/file")
485
+ expect(@env["SCRIPT_NAME"]).to eq("/example.org")
486
+ end
487
+ specify do
488
+ get 'http://example.org/sub/public/file.txt'
489
+ expect(@env["PATH_INFO"]).to eq("/sub/public/file.txt")
490
+ expect(@env["REQUEST_URI"]).to be_nil
491
+ expect(@env["SCRIPT_NAME"]).to eq("/example.org")
492
+ end
493
+ end
494
+
495
+ end
496
+ end
497
+
498
+ describe "#root_url and #protocol" do
499
+ before do
500
+ @pdf = MyPDFKit::Middleware.new({})
501
+ @env = { 'REQUEST_URI' => 'http://example.com/document.pdf', 'rack.url_scheme' => 'http', 'HTTP_HOST' => 'example.com' }
502
+ end
503
+
504
+ context 'when root_url is not configured' do
505
+ it "infers the root_url and protocol from the environment" do
506
+ root_url = @pdf.send(:root_url, @env)
507
+ protocol = @pdf.send(:protocol, @env)
508
+
509
+ expect(root_url).to eq('http://example.com/')
510
+ expect(protocol).to eq('http')
511
+ end
512
+ end
513
+
514
+ context 'when root_url is configured' do
515
+ before do
516
+ MyPDFKit.configuration.root_url = 'http://example.net/'
517
+ end
518
+ after do
519
+ MyPDFKit.configuration.root_url = nil
520
+ end
521
+
522
+ it "takes the root_url from the configuration, and infers the protocol from the environment" do
523
+ root_url = @pdf.send(:root_url, @env)
524
+ protocol = @pdf.send(:protocol, @env)
525
+
526
+ expect(root_url).to eq('http://example.net/')
527
+ expect(protocol).to eq('http')
528
+ end
529
+ end
530
+ end
531
+ end
data/spec/os_spec.rb ADDED
@@ -0,0 +1,67 @@
1
+ #encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'spec_helper'
5
+ require 'rbconfig'
6
+
7
+ describe 'OS' do
8
+ subject { MyPDFKit::OS }
9
+
10
+ describe 'host_is_windows?' do
11
+ it 'is callable' do
12
+ expect(subject).to respond_to(:host_is_windows?)
13
+ end
14
+
15
+ def test_is_windows(bool, host_os)
16
+ allow(RbConfig::CONFIG).to receive(:[]).with('host_os').and_return(host_os)
17
+
18
+ expect(subject.host_is_windows?).to be bool
19
+ end
20
+
21
+ it 'returns true if the host_os is set to "mswin"' do
22
+ test_is_windows(true, 'mswin')
23
+ end
24
+
25
+ it 'returns true if the host_os is set to "msys"' do
26
+ test_is_windows(true, 'msys')
27
+ end
28
+
29
+ it 'returns false if the host_os is set to "linux-gnu"' do
30
+ test_is_windows(false, 'linux-gnu')
31
+ end
32
+
33
+ it 'returns false if the host_os is set to "darwin14.1.0"' do
34
+ test_is_windows(false, 'darwin14.1.0')
35
+ end
36
+ end
37
+
38
+ describe 'shell_escape_for_os' do
39
+ it 'is callable' do
40
+ expect(subject).to respond_to(:shell_escape_for_os)
41
+ end
42
+
43
+ it 'calls shelljoin on linux' do
44
+ args = double(:shelljoin)
45
+ allow(RbConfig::CONFIG).to receive(:[]).with('host_os').and_return('linux-gnu')
46
+
47
+ expect(args).to receive(:shelljoin)
48
+ MyPDFKit::OS.shell_escape_for_os(args)
49
+ end
50
+
51
+ it 'calls shelljoin on darwin14.1.10' do
52
+ args = double(:shelljoin)
53
+ allow(RbConfig::CONFIG).to receive(:[]).with('host_os').and_return('darwin14.1.10-gnu')
54
+
55
+ expect(args).to receive(:shelljoin)
56
+ MyPDFKit::OS.shell_escape_for_os(args)
57
+ end
58
+
59
+ it 'escapes special characters on Windows' do
60
+ args = ['foo|bar', 'biz(baz)', 'foo<baz>bar', 'hello^world&goodbye']
61
+ allow(RbConfig::CONFIG).to receive(:[]).with('host_os').and_return('mswin')
62
+
63
+ escaped_args = MyPDFKit::OS.shell_escape_for_os(args)
64
+ expect(escaped_args).to eq('foo^|bar biz^(baz^) foo^<baz^>bar hello^^world^&goodbye')
65
+ end
66
+ end
67
+ end