Skip to content

Commit fcacf7c

Browse files
committed
API: Built out tests for comment API endpoints
1 parent cbf27d7 commit fcacf7c

File tree

5 files changed

+228
-21
lines changed

5 files changed

+228
-21
lines changed

app/Activity/CommentRepo.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ public function create(Entity $entity, string $html, ?int $parentId, string $con
5050
// Validate parent ID
5151
if ($parentId !== null) {
5252
$parentCommentExists = Comment::query()
53-
->where('entity_id', '=', $entity->id)
54-
->where('entity_type', '=', $entity->getMorphClass())
53+
->where('commentable_id', '=', $entity->id)
54+
->where('commentable_type', '=', $entity->getMorphClass())
5555
->where('local_id', '=', $parentId)
5656
->exists();
5757
if (!$parentCommentExists) {

app/Activity/Controllers/CommentApiController.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ class CommentApiController extends ApiController
2323
{
2424
// TODO - Add tree-style comment listing to page-show responses.
2525

26-
// TODO - Test visibility controls
27-
// TODO - Test permissions of each action
28-
2926
protected array $rules = [
3027
'create' => [
3128
'page_id' => ['required', 'integer'],
@@ -34,7 +31,7 @@ class CommentApiController extends ApiController
3431
'content_ref' => ['string'],
3532
],
3633
'update' => [
37-
'html' => ['required', 'string'],
34+
'html' => ['string'],
3835
'archived' => ['boolean'],
3936
]
4037
];
@@ -85,6 +82,7 @@ public function create(Request $request): JsonResponse
8582
public function read(string $id): JsonResponse
8683
{
8784
$comment = $this->commentRepo->getVisibleById(intval($id));
85+
$comment->load('createdBy', 'updatedBy');
8886

8987
$replies = $this->commentRepo->getQueryForVisible()
9088
->where('parent_id', '=', $comment->local_id)
@@ -117,17 +115,19 @@ public function update(Request $request, string $id): JsonResponse
117115
$this->checkOwnablePermission(Permission::CommentUpdate, $comment);
118116

119117
$input = $this->validate($request, $this->rules()['update']);
118+
$hasHtml = isset($input['html']);
120119

121120
if (isset($input['archived'])) {
122-
$archived = $input['archived'];
123-
if ($archived) {
124-
$this->commentRepo->archive($comment, false);
121+
if ($input['archived']) {
122+
$this->commentRepo->archive($comment, !$hasHtml);
125123
} else {
126-
$this->commentRepo->unarchive($comment, false);
124+
$this->commentRepo->unarchive($comment, !$hasHtml);
127125
}
128126
}
129127

130-
$comment = $this->commentRepo->update($comment, $input['html']);
128+
if ($hasHtml) {
129+
$comment = $this->commentRepo->update($comment, $input['html']);
130+
}
131131

132132
return response()->json($comment);
133133
}

app/Activity/Models/Comment.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class Comment extends Model implements Loggable, OwnableInterface
4141
*/
4242
public function entity(): MorphTo
4343
{
44-
return $this->morphTo('entity');
44+
return $this->morphTo('commentable');
4545
}
4646

4747
/**

tests/Activity/CommentsApiTest.php

Lines changed: 215 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace Activity;
44

5-
use BookStack\Activity\ActivityType;
6-
use BookStack\Facades\Activity;
5+
use BookStack\Activity\Models\Comment;
6+
use BookStack\Permissions\Permission;
77
use Tests\Api\TestsApi;
88
use Tests\TestCase;
99

@@ -13,31 +13,238 @@ class CommentsApiTest extends TestCase
1313

1414
public function test_endpoint_permission_controls()
1515
{
16-
// TODO
16+
$user = $this->users->editor();
17+
$this->permissions->grantUserRolePermissions($user, [Permission::CommentDeleteAll, Permission::CommentUpdateAll]);
18+
19+
$page = $this->entities->page();
20+
$comment = Comment::factory()->make();
21+
$page->comments()->save($comment);
22+
$this->actingAsForApi($user);
23+
24+
$actions = [
25+
['GET', '/api/comments'],
26+
['GET', "/api/comments/{$comment->id}"],
27+
['POST', "/api/comments"],
28+
['PUT', "/api/comments/{$comment->id}"],
29+
['DELETE', "/api/comments/{$comment->id}"],
30+
];
31+
32+
foreach ($actions as [$method, $endpoint]) {
33+
$resp = $this->call($method, $endpoint);
34+
$this->assertNotPermissionError($resp);
35+
}
36+
37+
$comment = Comment::factory()->make();
38+
$page->comments()->save($comment);
39+
$this->getJson("/api/comments")->assertSee(['id' => $comment->id]);
40+
41+
$this->permissions->removeUserRolePermissions($user, [
42+
Permission::CommentDeleteAll, Permission::CommentDeleteOwn,
43+
Permission::CommentUpdateAll, Permission::CommentUpdateOwn,
44+
Permission::CommentCreateAll
45+
]);
46+
47+
$this->assertPermissionError($this->json('delete', "/api/comments/{$comment->id}"));
48+
$this->assertPermissionError($this->json('put', "/api/comments/{$comment->id}"));
49+
$this->assertPermissionError($this->json('post', "/api/comments"));
50+
$this->assertNotPermissionError($this->json('get', "/api/comments/{$comment->id}"));
51+
52+
$this->permissions->disableEntityInheritedPermissions($page);
53+
$this->json('get', "/api/comments/{$comment->id}")->assertStatus(404);
54+
$this->getJson("/api/comments")->assertDontSee(['id' => $comment->id]);
1755
}
1856

1957
public function test_index()
2058
{
21-
// TODO
59+
$page = $this->entities->page();
60+
Comment::query()->delete();
61+
62+
$comments = Comment::factory()->count(10)->make();
63+
$page->comments()->saveMany($comments);
64+
65+
$firstComment = $comments->first();
66+
$resp = $this->actingAsApiEditor()->getJson('/api/comments');
67+
$resp->assertJson([
68+
'data' => [
69+
[
70+
'id' => $firstComment->id,
71+
'commentable_id' => $page->id,
72+
'commentable_type' => 'page',
73+
'parent_id' => null,
74+
'local_id' => $firstComment->local_id,
75+
],
76+
],
77+
]);
78+
$resp->assertJsonCount(10, 'data');
79+
$resp->assertJson(['total' => 10]);
80+
81+
$filtered = $this->getJson("/api/comments?filter[id]={$firstComment->id}");
82+
$filtered->assertJsonCount(1, 'data');
83+
$filtered->assertJson(['total' => 1]);
2284
}
2385

2486
public function test_create()
2587
{
26-
// TODO
88+
$page = $this->entities->page();
89+
90+
$resp = $this->actingAsApiEditor()->postJson('/api/comments', [
91+
'page_id' => $page->id,
92+
'html' => '<p>My wonderful comment</p>',
93+
'content_ref' => 'test-content-ref',
94+
]);
95+
$resp->assertOk();
96+
$id = $resp->json('id');
97+
98+
$this->assertDatabaseHas('comments', [
99+
'id' => $id,
100+
'commentable_id' => $page->id,
101+
'commentable_type' => 'page',
102+
'html' => '<p>My wonderful comment</p>',
103+
]);
104+
105+
$comment = Comment::query()->findOrFail($id);
106+
$this->assertIsInt($comment->local_id);
107+
108+
$reply = $this->actingAsApiEditor()->postJson('/api/comments', [
109+
'page_id' => $page->id,
110+
'html' => '<p>My wonderful reply</p>',
111+
'content_ref' => 'test-content-ref',
112+
'reply_to' => $comment->local_id,
113+
]);
114+
$reply->assertOk();
115+
116+
$this->assertDatabaseHas('comments', [
117+
'id' => $reply->json('id'),
118+
'commentable_id' => $page->id,
119+
'commentable_type' => 'page',
120+
'html' => '<p>My wonderful reply</p>',
121+
'parent_id' => $comment->local_id,
122+
]);
27123
}
28124

29125
public function test_read()
30126
{
31-
// TODO
127+
$page = $this->entities->page();
128+
$user = $this->users->viewer();
129+
$comment = Comment::factory()->make([
130+
'html' => '<p>A lovely comment <script>hello</script></p>',
131+
'created_by' => $user->id,
132+
'updated_by' => $user->id,
133+
]);
134+
$page->comments()->save($comment);
135+
$comment->refresh();
136+
$reply = Comment::factory()->make([
137+
'parent_id' => $comment->local_id,
138+
'html' => '<p>A lovely<script>angry</script>reply</p>',
139+
]);
140+
$page->comments()->save($reply);
141+
142+
$resp = $this->actingAsApiEditor()->getJson("/api/comments/{$comment->id}");
143+
$resp->assertJson([
144+
'id' => $comment->id,
145+
'commentable_id' => $page->id,
146+
'commentable_type' => 'page',
147+
'html' => '<p>A lovely comment </p>',
148+
'archived' => false,
149+
'created_by' => [
150+
'id' => $user->id,
151+
'name' => $user->name,
152+
],
153+
'updated_by' => [
154+
'id' => $user->id,
155+
'name' => $user->name,
156+
],
157+
'replies' => [
158+
[
159+
'id' => $reply->id,
160+
'html' => '<p>A lovelyreply</p>'
161+
]
162+
]
163+
]);
32164
}
33165

34166
public function test_update()
35167
{
36-
// TODO
168+
$page = $this->entities->page();
169+
$user = $this->users->editor();
170+
$this->permissions->grantUserRolePermissions($user, [Permission::CommentUpdateAll]);
171+
$comment = Comment::factory()->make([
172+
'html' => '<p>A lovely comment</p>',
173+
'created_by' => $this->users->viewer()->id,
174+
'updated_by' => $this->users->viewer()->id,
175+
'parent_id' => null,
176+
]);
177+
$page->comments()->save($comment);
178+
179+
$this->actingAsForApi($user)->putJson("/api/comments/{$comment->id}", [
180+
'html' => '<p>A lovely updated comment</p>',
181+
])->assertOk();
182+
183+
$this->assertDatabaseHas('comments', [
184+
'id' => $comment->id,
185+
'html' => '<p>A lovely updated comment</p>',
186+
'archived' => 0,
187+
]);
188+
189+
$this->putJson("/api/comments/{$comment->id}", [
190+
'archived' => true,
191+
]);
192+
193+
$this->assertDatabaseHas('comments', [
194+
'id' => $comment->id,
195+
'html' => '<p>A lovely updated comment</p>',
196+
'archived' => 1,
197+
]);
198+
199+
$this->putJson("/api/comments/{$comment->id}", [
200+
'archived' => false,
201+
'html' => '<p>A lovely updated again comment</p>',
202+
]);
203+
204+
$this->assertDatabaseHas('comments', [
205+
'id' => $comment->id,
206+
'html' => '<p>A lovely updated again comment</p>',
207+
'archived' => 0,
208+
]);
209+
}
210+
211+
public function test_update_cannot_archive_replies()
212+
{
213+
$page = $this->entities->page();
214+
$user = $this->users->editor();
215+
$this->permissions->grantUserRolePermissions($user, [Permission::CommentUpdateAll]);
216+
$comment = Comment::factory()->make([
217+
'html' => '<p>A lovely comment</p>',
218+
'created_by' => $this->users->viewer()->id,
219+
'updated_by' => $this->users->viewer()->id,
220+
'parent_id' => 90,
221+
]);
222+
$page->comments()->save($comment);
223+
224+
$resp = $this->actingAsForApi($user)->putJson("/api/comments/{$comment->id}", [
225+
'archived' => true,
226+
]);
227+
228+
$this->assertEquals($this->errorResponse('Only top-level comments can be archived.', 400), $resp->json());
229+
$this->assertDatabaseHas('comments', [
230+
'id' => $comment->id,
231+
'archived' => 0,
232+
]);
37233
}
38234

39235
public function test_destroy()
40236
{
41-
// TODO
237+
$page = $this->entities->page();
238+
$user = $this->users->editor();
239+
$this->permissions->grantUserRolePermissions($user, [Permission::CommentDeleteAll]);
240+
$comment = Comment::factory()->make([
241+
'html' => '<p>A lovely comment</p>',
242+
]);
243+
$page->comments()->save($comment);
244+
245+
$this->actingAsForApi($user)->deleteJson("/api/comments/{$comment->id}")->assertStatus(204);
246+
$this->assertDatabaseMissing('comments', [
247+
'id' => $comment->id,
248+
]);
42249
}
43250
}

tests/TestCase.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ private function isPermissionError($response): bool
199199
{
200200
if ($response->status() === 403 && $response instanceof JsonResponse) {
201201
$errMessage = $response->getData(true)['error']['message'] ?? '';
202-
return $errMessage === 'You do not have permission to perform the requested action.';
202+
return str_contains($errMessage, 'do not have permission');
203203
}
204204

205205
return $response->status() === 302

0 commit comments

Comments
 (0)