]> BookStack Code Mirror - bookstack/blob - tests/Exports/PdfExportTest.php
Lexical: Added about button/view
[bookstack] / tests / Exports / PdfExportTest.php
1 <?php
2
3 namespace Tests\Exports;
4
5 use BookStack\Entities\Models\Page;
6 use BookStack\Exceptions\PdfExportException;
7 use BookStack\Exports\PdfGenerator;
8 use Tests\TestCase;
9
10 class PdfExportTest extends TestCase
11 {
12     public function test_page_pdf_export()
13     {
14         $page = $this->entities->page();
15         $this->asEditor();
16
17         $resp = $this->get($page->getUrl('/export/pdf'));
18         $resp->assertStatus(200);
19         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.pdf"');
20     }
21
22     public function test_book_pdf_export()
23     {
24         $page = $this->entities->page();
25         $book = $page->book;
26         $this->asEditor();
27
28         $resp = $this->get($book->getUrl('/export/pdf'));
29         $resp->assertStatus(200);
30         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.pdf"');
31     }
32
33     public function test_chapter_pdf_export()
34     {
35         $chapter = $this->entities->chapter();
36         $this->asEditor();
37
38         $resp = $this->get($chapter->getUrl('/export/pdf'));
39         $resp->assertStatus(200);
40         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.pdf"');
41     }
42
43
44     public function test_page_pdf_export_converts_iframes_to_links()
45     {
46         $page = Page::query()->first()->forceFill([
47             'html'     => '<iframe width="560" height="315" src="//www.youtube.com/embed/ShqUjt33uOs"></iframe>',
48         ]);
49         $page->save();
50
51         $pdfHtml = '';
52         $mockPdfGenerator = $this->mock(PdfGenerator::class);
53         $mockPdfGenerator->shouldReceive('fromHtml')
54             ->with(\Mockery::capture($pdfHtml))
55             ->andReturn('');
56         $mockPdfGenerator->shouldReceive('getActiveEngine')->andReturn(PdfGenerator::ENGINE_DOMPDF);
57
58         $this->asEditor()->get($page->getUrl('/export/pdf'));
59         $this->assertStringNotContainsString('iframe>', $pdfHtml);
60         $this->assertStringContainsString('<p><a href="https://www.youtube.com/embed/ShqUjt33uOs">https://www.youtube.com/embed/ShqUjt33uOs</a></p>', $pdfHtml);
61     }
62
63     public function test_page_pdf_export_opens_details_blocks()
64     {
65         $page = $this->entities->page()->forceFill([
66             'html'     => '<details><summary>Hello</summary><p>Content!</p></details>',
67         ]);
68         $page->save();
69
70         $pdfHtml = '';
71         $mockPdfGenerator = $this->mock(PdfGenerator::class);
72         $mockPdfGenerator->shouldReceive('fromHtml')
73             ->with(\Mockery::capture($pdfHtml))
74             ->andReturn('');
75         $mockPdfGenerator->shouldReceive('getActiveEngine')->andReturn(PdfGenerator::ENGINE_DOMPDF);
76
77         $this->asEditor()->get($page->getUrl('/export/pdf'));
78         $this->assertStringContainsString('<details open="open"', $pdfHtml);
79     }
80
81     public function test_wkhtmltopdf_only_used_when_allow_untrusted_is_true()
82     {
83         $page = $this->entities->page();
84
85         config()->set('exports.snappy.pdf_binary', '/abc123');
86         config()->set('app.allow_untrusted_server_fetching', false);
87
88         $resp = $this->asEditor()->get($page->getUrl('/export/pdf'));
89         $resp->assertStatus(200); // Sucessful response with invalid snappy binary indicates dompdf usage.
90
91         config()->set('app.allow_untrusted_server_fetching', true);
92         $resp = $this->get($page->getUrl('/export/pdf'));
93         $resp->assertStatus(500); // Bad response indicates wkhtml usage
94     }
95
96     public function test_pdf_command_option_used_if_set()
97     {
98         $page = $this->entities->page();
99         $command = 'cp {input_html_path} {output_pdf_path}';
100         config()->set('exports.pdf_command', $command);
101
102         $resp = $this->asEditor()->get($page->getUrl('/export/pdf'));
103         $download = $resp->getContent();
104
105         $this->assertStringContainsString(e($page->name), $download);
106         $this->assertStringContainsString('<html lang=', $download);
107     }
108
109     public function test_pdf_command_option_errors_if_output_path_not_written_to()
110     {
111         $page = $this->entities->page();
112         $command = 'echo "hi"';
113         config()->set('exports.pdf_command', $command);
114
115         $this->assertThrows(function () use ($page) {
116             $this->withoutExceptionHandling()->asEditor()->get($page->getUrl('/export/pdf'));
117         }, PdfExportException::class);
118     }
119
120     public function test_pdf_command_option_errors_if_command_returns_error_status()
121     {
122         $page = $this->entities->page();
123         $command = 'exit 1';
124         config()->set('exports.pdf_command', $command);
125
126         $this->assertThrows(function () use ($page) {
127             $this->withoutExceptionHandling()->asEditor()->get($page->getUrl('/export/pdf'));
128         }, PdfExportException::class);
129     }
130
131     public function test_pdf_command_timout_option_limits_export_time()
132     {
133         $page = $this->entities->page();
134         $command = 'php -r \'sleep(4);\'';
135         config()->set('exports.pdf_command', $command);
136         config()->set('exports.pdf_command_timeout', 1);
137
138         $this->assertThrows(function () use ($page) {
139             $start = time();
140             $this->withoutExceptionHandling()->asEditor()->get($page->getUrl('/export/pdf'));
141
142             $this->assertTrue(time() < ($start + 3));
143         }, PdfExportException::class,
144             "PDF Export via command failed due to timeout at 1 second(s)");
145     }
146 }