1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-11-06 00:50:36 +03:00

API: Built out tests for comment API endpoints

This commit is contained in:
Dan Brown
2025-10-23 16:52:29 +01:00
parent cbf27d70c8
commit fcacf7cacb
5 changed files with 228 additions and 21 deletions

View File

@@ -50,8 +50,8 @@ class CommentRepo
// Validate parent ID
if ($parentId !== null) {
$parentCommentExists = Comment::query()
->where('entity_id', '=', $entity->id)
->where('entity_type', '=', $entity->getMorphClass())
->where('commentable_id', '=', $entity->id)
->where('commentable_type', '=', $entity->getMorphClass())
->where('local_id', '=', $parentId)
->exists();
if (!$parentCommentExists) {

View File

@@ -23,9 +23,6 @@ class CommentApiController extends ApiController
{
// TODO - Add tree-style comment listing to page-show responses.
// TODO - Test visibility controls
// TODO - Test permissions of each action
protected array $rules = [
'create' => [
'page_id' => ['required', 'integer'],
@@ -34,7 +31,7 @@ class CommentApiController extends ApiController
'content_ref' => ['string'],
],
'update' => [
'html' => ['required', 'string'],
'html' => ['string'],
'archived' => ['boolean'],
]
];
@@ -85,6 +82,7 @@ class CommentApiController extends ApiController
public function read(string $id): JsonResponse
{
$comment = $this->commentRepo->getVisibleById(intval($id));
$comment->load('createdBy', 'updatedBy');
$replies = $this->commentRepo->getQueryForVisible()
->where('parent_id', '=', $comment->local_id)
@@ -117,17 +115,19 @@ class CommentApiController extends ApiController
$this->checkOwnablePermission(Permission::CommentUpdate, $comment);
$input = $this->validate($request, $this->rules()['update']);
$hasHtml = isset($input['html']);
if (isset($input['archived'])) {
$archived = $input['archived'];
if ($archived) {
$this->commentRepo->archive($comment, false);
if ($input['archived']) {
$this->commentRepo->archive($comment, !$hasHtml);
} else {
$this->commentRepo->unarchive($comment, false);
$this->commentRepo->unarchive($comment, !$hasHtml);
}
}
if ($hasHtml) {
$comment = $this->commentRepo->update($comment, $input['html']);
}
return response()->json($comment);
}

View File

@@ -41,7 +41,7 @@ class Comment extends Model implements Loggable, OwnableInterface
*/
public function entity(): MorphTo
{
return $this->morphTo('entity');
return $this->morphTo('commentable');
}
/**

View File

@@ -2,8 +2,8 @@
namespace Activity;
use BookStack\Activity\ActivityType;
use BookStack\Facades\Activity;
use BookStack\Activity\Models\Comment;
use BookStack\Permissions\Permission;
use Tests\Api\TestsApi;
use Tests\TestCase;
@@ -13,31 +13,238 @@ class CommentsApiTest extends TestCase
public function test_endpoint_permission_controls()
{
// TODO
$user = $this->users->editor();
$this->permissions->grantUserRolePermissions($user, [Permission::CommentDeleteAll, Permission::CommentUpdateAll]);
$page = $this->entities->page();
$comment = Comment::factory()->make();
$page->comments()->save($comment);
$this->actingAsForApi($user);
$actions = [
['GET', '/api/comments'],
['GET', "/api/comments/{$comment->id}"],
['POST', "/api/comments"],
['PUT', "/api/comments/{$comment->id}"],
['DELETE', "/api/comments/{$comment->id}"],
];
foreach ($actions as [$method, $endpoint]) {
$resp = $this->call($method, $endpoint);
$this->assertNotPermissionError($resp);
}
$comment = Comment::factory()->make();
$page->comments()->save($comment);
$this->getJson("/api/comments")->assertSee(['id' => $comment->id]);
$this->permissions->removeUserRolePermissions($user, [
Permission::CommentDeleteAll, Permission::CommentDeleteOwn,
Permission::CommentUpdateAll, Permission::CommentUpdateOwn,
Permission::CommentCreateAll
]);
$this->assertPermissionError($this->json('delete', "/api/comments/{$comment->id}"));
$this->assertPermissionError($this->json('put', "/api/comments/{$comment->id}"));
$this->assertPermissionError($this->json('post', "/api/comments"));
$this->assertNotPermissionError($this->json('get', "/api/comments/{$comment->id}"));
$this->permissions->disableEntityInheritedPermissions($page);
$this->json('get', "/api/comments/{$comment->id}")->assertStatus(404);
$this->getJson("/api/comments")->assertDontSee(['id' => $comment->id]);
}
public function test_index()
{
// TODO
$page = $this->entities->page();
Comment::query()->delete();
$comments = Comment::factory()->count(10)->make();
$page->comments()->saveMany($comments);
$firstComment = $comments->first();
$resp = $this->actingAsApiEditor()->getJson('/api/comments');
$resp->assertJson([
'data' => [
[
'id' => $firstComment->id,
'commentable_id' => $page->id,
'commentable_type' => 'page',
'parent_id' => null,
'local_id' => $firstComment->local_id,
],
],
]);
$resp->assertJsonCount(10, 'data');
$resp->assertJson(['total' => 10]);
$filtered = $this->getJson("/api/comments?filter[id]={$firstComment->id}");
$filtered->assertJsonCount(1, 'data');
$filtered->assertJson(['total' => 1]);
}
public function test_create()
{
// TODO
$page = $this->entities->page();
$resp = $this->actingAsApiEditor()->postJson('/api/comments', [
'page_id' => $page->id,
'html' => '<p>My wonderful comment</p>',
'content_ref' => 'test-content-ref',
]);
$resp->assertOk();
$id = $resp->json('id');
$this->assertDatabaseHas('comments', [
'id' => $id,
'commentable_id' => $page->id,
'commentable_type' => 'page',
'html' => '<p>My wonderful comment</p>',
]);
$comment = Comment::query()->findOrFail($id);
$this->assertIsInt($comment->local_id);
$reply = $this->actingAsApiEditor()->postJson('/api/comments', [
'page_id' => $page->id,
'html' => '<p>My wonderful reply</p>',
'content_ref' => 'test-content-ref',
'reply_to' => $comment->local_id,
]);
$reply->assertOk();
$this->assertDatabaseHas('comments', [
'id' => $reply->json('id'),
'commentable_id' => $page->id,
'commentable_type' => 'page',
'html' => '<p>My wonderful reply</p>',
'parent_id' => $comment->local_id,
]);
}
public function test_read()
{
// TODO
$page = $this->entities->page();
$user = $this->users->viewer();
$comment = Comment::factory()->make([
'html' => '<p>A lovely comment <script>hello</script></p>',
'created_by' => $user->id,
'updated_by' => $user->id,
]);
$page->comments()->save($comment);
$comment->refresh();
$reply = Comment::factory()->make([
'parent_id' => $comment->local_id,
'html' => '<p>A lovely<script>angry</script>reply</p>',
]);
$page->comments()->save($reply);
$resp = $this->actingAsApiEditor()->getJson("/api/comments/{$comment->id}");
$resp->assertJson([
'id' => $comment->id,
'commentable_id' => $page->id,
'commentable_type' => 'page',
'html' => '<p>A lovely comment </p>',
'archived' => false,
'created_by' => [
'id' => $user->id,
'name' => $user->name,
],
'updated_by' => [
'id' => $user->id,
'name' => $user->name,
],
'replies' => [
[
'id' => $reply->id,
'html' => '<p>A lovelyreply</p>'
]
]
]);
}
public function test_update()
{
// TODO
$page = $this->entities->page();
$user = $this->users->editor();
$this->permissions->grantUserRolePermissions($user, [Permission::CommentUpdateAll]);
$comment = Comment::factory()->make([
'html' => '<p>A lovely comment</p>',
'created_by' => $this->users->viewer()->id,
'updated_by' => $this->users->viewer()->id,
'parent_id' => null,
]);
$page->comments()->save($comment);
$this->actingAsForApi($user)->putJson("/api/comments/{$comment->id}", [
'html' => '<p>A lovely updated comment</p>',
])->assertOk();
$this->assertDatabaseHas('comments', [
'id' => $comment->id,
'html' => '<p>A lovely updated comment</p>',
'archived' => 0,
]);
$this->putJson("/api/comments/{$comment->id}", [
'archived' => true,
]);
$this->assertDatabaseHas('comments', [
'id' => $comment->id,
'html' => '<p>A lovely updated comment</p>',
'archived' => 1,
]);
$this->putJson("/api/comments/{$comment->id}", [
'archived' => false,
'html' => '<p>A lovely updated again comment</p>',
]);
$this->assertDatabaseHas('comments', [
'id' => $comment->id,
'html' => '<p>A lovely updated again comment</p>',
'archived' => 0,
]);
}
public function test_update_cannot_archive_replies()
{
$page = $this->entities->page();
$user = $this->users->editor();
$this->permissions->grantUserRolePermissions($user, [Permission::CommentUpdateAll]);
$comment = Comment::factory()->make([
'html' => '<p>A lovely comment</p>',
'created_by' => $this->users->viewer()->id,
'updated_by' => $this->users->viewer()->id,
'parent_id' => 90,
]);
$page->comments()->save($comment);
$resp = $this->actingAsForApi($user)->putJson("/api/comments/{$comment->id}", [
'archived' => true,
]);
$this->assertEquals($this->errorResponse('Only top-level comments can be archived.', 400), $resp->json());
$this->assertDatabaseHas('comments', [
'id' => $comment->id,
'archived' => 0,
]);
}
public function test_destroy()
{
// TODO
$page = $this->entities->page();
$user = $this->users->editor();
$this->permissions->grantUserRolePermissions($user, [Permission::CommentDeleteAll]);
$comment = Comment::factory()->make([
'html' => '<p>A lovely comment</p>',
]);
$page->comments()->save($comment);
$this->actingAsForApi($user)->deleteJson("/api/comments/{$comment->id}")->assertStatus(204);
$this->assertDatabaseMissing('comments', [
'id' => $comment->id,
]);
}
}

View File

@@ -199,7 +199,7 @@ abstract class TestCase extends BaseTestCase
{
if ($response->status() === 403 && $response instanceof JsonResponse) {
$errMessage = $response->getData(true)['error']['message'] ?? '';
return $errMessage === 'You do not have permission to perform the requested action.';
return str_contains($errMessage, 'do not have permission');
}
return $response->status() === 302