]> BookStack Code Mirror - bookstack/blob - tests/Entity/CommentStoreTest.php
Maintenance: Updated larastan target level, fixed issues from tests
[bookstack] / tests / Entity / CommentStoreTest.php
1 <?php
2
3 namespace Tests\Entity;
4
5 use BookStack\Activity\ActivityType;
6 use BookStack\Activity\Models\Comment;
7 use BookStack\Entities\Models\Page;
8 use Tests\TestCase;
9
10 class CommentStoreTest extends TestCase
11 {
12     public function test_add_comment()
13     {
14         $this->asAdmin();
15         $page = $this->entities->page();
16
17         $comment = Comment::factory()->make(['parent_id' => 2]);
18         $resp = $this->postJson("/comment/$page->id", $comment->getAttributes());
19
20         $resp->assertStatus(200);
21         $resp->assertSee($comment->html, false);
22
23         $pageResp = $this->get($page->getUrl());
24         $pageResp->assertSee($comment->html, false);
25
26         $this->assertDatabaseHas('comments', [
27             'local_id'    => 1,
28             'entity_id'   => $page->id,
29             'entity_type' => Page::newModelInstance()->getMorphClass(),
30             'parent_id'   => 2,
31         ]);
32
33         $this->assertActivityExists(ActivityType::COMMENT_CREATE);
34     }
35     public function test_add_comment_stores_content_reference_only_if_format_valid()
36     {
37         $validityByRefs = [
38             'bkmrk-my-title:4589284922:4-3' => true,
39             'bkmrk-my-title:4589284922:' => true,
40             'bkmrk-my-title:4589284922:abc' => false,
41             'my-title:4589284922:' => false,
42             'bkmrk-my-title-4589284922:' => false,
43         ];
44
45         $page = $this->entities->page();
46
47         foreach ($validityByRefs as $ref => $valid) {
48             $this->asAdmin()->postJson("/comment/$page->id", [
49                 'html' => '<p>My comment</p>',
50                 'parent_id' => null,
51                 'content_ref' => $ref,
52             ]);
53
54             if ($valid) {
55                 $this->assertDatabaseHas('comments', ['entity_id' => $page->id, 'content_ref' => $ref]);
56             } else {
57                 $this->assertDatabaseMissing('comments', ['entity_id' => $page->id, 'content_ref' => $ref]);
58             }
59         }
60     }
61
62     public function test_comment_edit()
63     {
64         $this->asAdmin();
65         $page = $this->entities->page();
66
67         $comment = Comment::factory()->make();
68         $this->postJson("/comment/$page->id", $comment->getAttributes());
69
70         $comment = $page->comments()->first();
71         $newHtml = '<p>updated text content</p>';
72         $resp = $this->putJson("/comment/$comment->id", [
73             'html' => $newHtml,
74         ]);
75
76         $resp->assertStatus(200);
77         $resp->assertSee($newHtml, false);
78         $resp->assertDontSee($comment->html, false);
79
80         $this->assertDatabaseHas('comments', [
81             'html'      => $newHtml,
82             'entity_id' => $page->id,
83         ]);
84
85         $this->assertActivityExists(ActivityType::COMMENT_UPDATE);
86     }
87
88     public function test_comment_delete()
89     {
90         $this->asAdmin();
91         $page = $this->entities->page();
92
93         $comment = Comment::factory()->make();
94         $this->postJson("/comment/$page->id", $comment->getAttributes());
95
96         $comment = $page->comments()->first();
97
98         $resp = $this->delete("/comment/$comment->id");
99         $resp->assertStatus(200);
100
101         $this->assertDatabaseMissing('comments', [
102             'id' => $comment->id,
103         ]);
104
105         $this->assertActivityExists(ActivityType::COMMENT_DELETE);
106     }
107
108     public function test_comment_archive_and_unarchive()
109     {
110         $this->asAdmin();
111         $page = $this->entities->page();
112
113         $comment = Comment::factory()->make();
114         $page->comments()->save($comment);
115         $comment->refresh();
116
117         $this->put("/comment/$comment->id/archive");
118
119         $this->assertDatabaseHas('comments', [
120             'id' => $comment->id,
121             'archived' => true,
122         ]);
123
124         $this->assertActivityExists(ActivityType::COMMENT_UPDATE);
125
126         $this->put("/comment/$comment->id/unarchive");
127
128         $this->assertDatabaseHas('comments', [
129             'id' => $comment->id,
130             'archived' => false,
131         ]);
132
133         $this->assertActivityExists(ActivityType::COMMENT_UPDATE);
134     }
135
136     public function test_archive_endpoints_require_delete_or_edit_permissions()
137     {
138         $viewer = $this->users->viewer();
139         $page = $this->entities->page();
140
141         $comment = Comment::factory()->make();
142         $page->comments()->save($comment);
143         $comment->refresh();
144
145         $endpoints = ["/comment/$comment->id/archive", "/comment/$comment->id/unarchive"];
146
147         foreach ($endpoints as $endpoint) {
148             $resp = $this->actingAs($viewer)->put($endpoint);
149             $this->assertPermissionError($resp);
150         }
151
152         $this->permissions->grantUserRolePermissions($viewer, ['comment-delete-all']);
153
154         foreach ($endpoints as $endpoint) {
155             $resp = $this->actingAs($viewer)->put($endpoint);
156             $resp->assertOk();
157         }
158
159         $this->permissions->removeUserRolePermissions($viewer, ['comment-delete-all']);
160         $this->permissions->grantUserRolePermissions($viewer, ['comment-update-all']);
161
162         foreach ($endpoints as $endpoint) {
163             $resp = $this->actingAs($viewer)->put($endpoint);
164             $resp->assertOk();
165         }
166     }
167
168     public function test_non_top_level_comments_cant_be_archived_or_unarchived()
169     {
170         $this->asAdmin();
171         $page = $this->entities->page();
172
173         $comment = Comment::factory()->make();
174         $page->comments()->save($comment);
175         $subComment = Comment::factory()->make(['parent_id' => $comment->id]);
176         $page->comments()->save($subComment);
177         $subComment->refresh();
178
179         $resp = $this->putJson("/comment/$subComment->id/archive");
180         $resp->assertStatus(400);
181
182         $this->assertDatabaseHas('comments', [
183             'id' => $subComment->id,
184             'archived' => false,
185         ]);
186
187         $resp = $this->putJson("/comment/$subComment->id/unarchive");
188         $resp->assertStatus(400);
189     }
190
191     public function test_scripts_cannot_be_injected_via_comment_html()
192     {
193         $page = $this->entities->page();
194
195         $script = '<script>const a = "script";</script><script>const b = "sneakyscript";</script><p onclick="1">My lovely comment</p>';
196         $this->asAdmin()->postJson("/comment/$page->id", [
197             'html' => $script,
198         ]);
199
200         $pageView = $this->get($page->getUrl());
201         $pageView->assertDontSee($script, false);
202         $pageView->assertDontSee('sneakyscript', false);
203         $pageView->assertSee('<p>My lovely comment</p>', false);
204
205         $comment = $page->comments()->first();
206         $this->putJson("/comment/$comment->id", [
207             'html' => $script . '<p>updated</p>',
208         ]);
209
210         $pageView = $this->get($page->getUrl());
211         $pageView->assertDontSee($script, false);
212         $pageView->assertDontSee('sneakyscript', false);
213         $pageView->assertSee('<p>My lovely comment</p><p>updated</p>');
214     }
215
216     public function test_scripts_are_removed_even_if_already_in_db()
217     {
218         $page = $this->entities->page();
219         Comment::factory()->create([
220             'html' => '<script>superbadscript</script><script>superbadscript</script><p onclick="superbadonclick">scriptincommentest</p>',
221             'entity_type' => 'page', 'entity_id' => $page
222         ]);
223
224         $resp = $this->asAdmin()->get($page->getUrl());
225         $resp->assertSee('scriptincommentest', false);
226         $resp->assertDontSee('superbadscript', false);
227         $resp->assertDontSee('superbadonclick', false);
228     }
229
230     public function test_comment_html_is_limited()
231     {
232         $page = $this->entities->page();
233         $input = '<h1>Test</h1><p id="abc" href="beans">Content<a href="#cat" data-a="b">a</a><section>Hello</section><section>there</section></p>';
234         $expected = '<p>Content<a href="#cat">a</a></p>';
235
236         $resp = $this->asAdmin()->post("/comment/{$page->id}", ['html' => $input]);
237         $resp->assertOk();
238         $this->assertDatabaseHas('comments', [
239            'entity_type' => 'page',
240            'entity_id' => $page->id,
241            'html' => $expected,
242         ]);
243
244         $comment = $page->comments()->first();
245         $resp = $this->put("/comment/{$comment->id}", ['html' => $input]);
246         $resp->assertOk();
247         $this->assertDatabaseHas('comments', [
248             'id'   => $comment->id,
249             'html' => $expected,
250         ]);
251     }
252
253     public function test_comment_html_spans_are_cleaned()
254     {
255         $page = $this->entities->page();
256         $input = '<p><span class="beans">Hello</span> do you have <span style="white-space: discard;">biscuits</span>?</p>';
257         $expected = '<p><span>Hello</span> do you have <span>biscuits</span>?</p>';
258
259         $resp = $this->asAdmin()->post("/comment/{$page->id}", ['html' => $input]);
260         $resp->assertOk();
261         $this->assertDatabaseHas('comments', [
262             'entity_type' => 'page',
263             'entity_id' => $page->id,
264             'html' => $expected,
265         ]);
266
267         $comment = $page->comments()->first();
268         $resp = $this->put("/comment/{$comment->id}", ['html' => $input]);
269         $resp->assertOk();
270         $this->assertDatabaseHas('comments', [
271             'id'   => $comment->id,
272             'html' => $expected,
273         ]);
274     }
275 }