]> BookStack Code Mirror - bookstack/blob - tests/Entity/CommentStoreTest.php
Merge pull request #5584 from BookStackApp/content_comments
[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             'text'        => null,
31             'parent_id'   => 2,
32         ]);
33
34         $this->assertActivityExists(ActivityType::COMMENT_CREATE);
35     }
36     public function test_add_comment_stores_content_reference_only_if_format_valid()
37     {
38         $validityByRefs = [
39             'bkmrk-my-title:4589284922:4-3' => true,
40             'bkmrk-my-title:4589284922:' => true,
41             'bkmrk-my-title:4589284922:abc' => false,
42             'my-title:4589284922:' => false,
43             'bkmrk-my-title-4589284922:' => false,
44         ];
45
46         $page = $this->entities->page();
47
48         foreach ($validityByRefs as $ref => $valid) {
49             $this->asAdmin()->postJson("/comment/$page->id", [
50                 'html' => '<p>My comment</p>',
51                 'parent_id' => null,
52                 'content_ref' => $ref,
53             ]);
54
55             if ($valid) {
56                 $this->assertDatabaseHas('comments', ['entity_id' => $page->id, 'content_ref' => $ref]);
57             } else {
58                 $this->assertDatabaseMissing('comments', ['entity_id' => $page->id, 'content_ref' => $ref]);
59             }
60         }
61     }
62
63     public function test_comment_edit()
64     {
65         $this->asAdmin();
66         $page = $this->entities->page();
67
68         $comment = Comment::factory()->make();
69         $this->postJson("/comment/$page->id", $comment->getAttributes());
70
71         $comment = $page->comments()->first();
72         $newHtml = '<p>updated text content</p>';
73         $resp = $this->putJson("/comment/$comment->id", [
74             'html' => $newHtml,
75         ]);
76
77         $resp->assertStatus(200);
78         $resp->assertSee($newHtml, false);
79         $resp->assertDontSee($comment->html, false);
80
81         $this->assertDatabaseHas('comments', [
82             'html'      => $newHtml,
83             'entity_id' => $page->id,
84         ]);
85
86         $this->assertActivityExists(ActivityType::COMMENT_UPDATE);
87     }
88
89     public function test_comment_delete()
90     {
91         $this->asAdmin();
92         $page = $this->entities->page();
93
94         $comment = Comment::factory()->make();
95         $this->postJson("/comment/$page->id", $comment->getAttributes());
96
97         $comment = $page->comments()->first();
98
99         $resp = $this->delete("/comment/$comment->id");
100         $resp->assertStatus(200);
101
102         $this->assertDatabaseMissing('comments', [
103             'id' => $comment->id,
104         ]);
105
106         $this->assertActivityExists(ActivityType::COMMENT_DELETE);
107     }
108
109     public function test_comment_archive_and_unarchive()
110     {
111         $this->asAdmin();
112         $page = $this->entities->page();
113
114         $comment = Comment::factory()->make();
115         $page->comments()->save($comment);
116         $comment->refresh();
117
118         $this->put("/comment/$comment->id/archive");
119
120         $this->assertDatabaseHas('comments', [
121             'id' => $comment->id,
122             'archived' => true,
123         ]);
124
125         $this->assertActivityExists(ActivityType::COMMENT_UPDATE);
126
127         $this->put("/comment/$comment->id/unarchive");
128
129         $this->assertDatabaseHas('comments', [
130             'id' => $comment->id,
131             'archived' => false,
132         ]);
133
134         $this->assertActivityExists(ActivityType::COMMENT_UPDATE);
135     }
136
137     public function test_archive_endpoints_require_delete_or_edit_permissions()
138     {
139         $viewer = $this->users->viewer();
140         $page = $this->entities->page();
141
142         $comment = Comment::factory()->make();
143         $page->comments()->save($comment);
144         $comment->refresh();
145
146         $endpoints = ["/comment/$comment->id/archive", "/comment/$comment->id/unarchive"];
147
148         foreach ($endpoints as $endpoint) {
149             $resp = $this->actingAs($viewer)->put($endpoint);
150             $this->assertPermissionError($resp);
151         }
152
153         $this->permissions->grantUserRolePermissions($viewer, ['comment-delete-all']);
154
155         foreach ($endpoints as $endpoint) {
156             $resp = $this->actingAs($viewer)->put($endpoint);
157             $resp->assertOk();
158         }
159
160         $this->permissions->removeUserRolePermissions($viewer, ['comment-delete-all']);
161         $this->permissions->grantUserRolePermissions($viewer, ['comment-update-all']);
162
163         foreach ($endpoints as $endpoint) {
164             $resp = $this->actingAs($viewer)->put($endpoint);
165             $resp->assertOk();
166         }
167     }
168
169     public function test_non_top_level_comments_cant_be_archived_or_unarchived()
170     {
171         $this->asAdmin();
172         $page = $this->entities->page();
173
174         $comment = Comment::factory()->make();
175         $page->comments()->save($comment);
176         $subComment = Comment::factory()->make(['parent_id' => $comment->id]);
177         $page->comments()->save($subComment);
178         $subComment->refresh();
179
180         $resp = $this->putJson("/comment/$subComment->id/archive");
181         $resp->assertStatus(400);
182
183         $this->assertDatabaseHas('comments', [
184             'id' => $subComment->id,
185             'archived' => false,
186         ]);
187
188         $resp = $this->putJson("/comment/$subComment->id/unarchive");
189         $resp->assertStatus(400);
190     }
191
192     public function test_scripts_cannot_be_injected_via_comment_html()
193     {
194         $page = $this->entities->page();
195
196         $script = '<script>const a = "script";</script><p onclick="1">My lovely comment</p>';
197         $this->asAdmin()->postJson("/comment/$page->id", [
198             'html' => $script,
199         ]);
200
201         $pageView = $this->get($page->getUrl());
202         $pageView->assertDontSee($script, 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->assertSee('<p>My lovely comment</p><p>updated</p>');
213     }
214
215     public function test_scripts_are_removed_even_if_already_in_db()
216     {
217         $page = $this->entities->page();
218         Comment::factory()->create([
219             'html' => '<script>superbadscript</script><p onclick="superbadonclick">scriptincommentest</p>',
220             'entity_type' => 'page', 'entity_id' => $page
221         ]);
222
223         $resp = $this->asAdmin()->get($page->getUrl());
224         $resp->assertSee('scriptincommentest', false);
225         $resp->assertDontSee('superbadscript', false);
226         $resp->assertDontSee('superbadonclick', false);
227     }
228
229     public function test_comment_html_is_limited()
230     {
231         $page = $this->entities->page();
232         $input = '<h1>Test</h1><p id="abc" href="beans">Content<a href="#cat" data-a="b">a</a><section>Hello</section></p>';
233         $expected = '<p>Content<a href="#cat">a</a></p>';
234
235         $resp = $this->asAdmin()->post("/comment/{$page->id}", ['html' => $input]);
236         $resp->assertOk();
237         $this->assertDatabaseHas('comments', [
238            'entity_type' => 'page',
239            'entity_id' => $page->id,
240            'html' => $expected,
241         ]);
242
243         $comment = $page->comments()->first();
244         $resp = $this->put("/comment/{$comment->id}", ['html' => $input]);
245         $resp->assertOk();
246         $this->assertDatabaseHas('comments', [
247             'id'   => $comment->id,
248             'html' => $expected,
249         ]);
250     }
251 }