merge post-comment-controllers
This commit is contained in:
commit
ec4f729c63
8 changed files with 743 additions and 0 deletions
124
backend/app/Controllers/CommentController.php
Normal file
124
backend/app/Controllers/CommentController.php
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Comment\Comment;
|
||||
use App\Comment\UseCases\CreateComment\CreateComment;
|
||||
use App\Comment\UseCases\CreateComment\CreateCommentRequest;
|
||||
use App\Comment\UseCases\DeleteComment\DeleteComment;
|
||||
use App\Comment\UseCases\DeleteComment\DeleteCommentRequest;
|
||||
use App\Comment\UseCases\ListCommentsForPost\ListCommentsForPost;
|
||||
use App\Comment\UseCases\ListCommentsForPost\ListCommentsForPostRequest;
|
||||
use App\Exceptions\BadRequestException;
|
||||
use App\Exceptions\ForbiddenException;
|
||||
use App\User\User;
|
||||
use App\User\UserRepository;
|
||||
use DomainException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CommentController
|
||||
{
|
||||
public function __construct(
|
||||
private CreateComment $createComment,
|
||||
private DeleteComment $deleteComment,
|
||||
private ListCommentsForPost $listCommentsForPost,
|
||||
private UserRepository $userRepo,
|
||||
) {}
|
||||
|
||||
public function listForPost(Request $request, int $postId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$comments = $this->listCommentsForPost->execute(
|
||||
new ListCommentsForPostRequest(postId: $postId),
|
||||
);
|
||||
} catch (BadRequestException $exception) {
|
||||
return new JsonResponse(
|
||||
['error' => $exception->getMessage()], 400,
|
||||
);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'comments' => array_map(
|
||||
function (Comment $comment) {
|
||||
return $this->serialize($comment);
|
||||
},
|
||||
$comments,
|
||||
),
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function create(Request $request, int $postId): JsonResponse
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $request->attributes->get('user');
|
||||
try {
|
||||
$comment = $this->createComment->execute(new CreateCommentRequest(
|
||||
postId: $postId,
|
||||
userId: $user->getId(),
|
||||
body: $request->input('body'),
|
||||
));
|
||||
} catch (BadRequestException $exception) {
|
||||
return new JsonResponse(
|
||||
['error' => $exception->getMessage()], 400,
|
||||
);
|
||||
} catch (DomainException $exception) {
|
||||
return new JsonResponse(
|
||||
['error' => $exception->getMessage()], 404,
|
||||
);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'comment' => $this->serialize($comment),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function delete(Request $request, int $id): JsonResponse
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $request->attributes->get('user');
|
||||
try {
|
||||
$this->deleteComment->execute(new DeleteCommentRequest(
|
||||
commentId: $id,
|
||||
requesterId: $user->getId(),
|
||||
requesterIsAdmin: $user->isAdmin(),
|
||||
));
|
||||
} catch (BadRequestException $exception) {
|
||||
return new JsonResponse(
|
||||
['error' => $exception->getMessage()], 400,
|
||||
);
|
||||
} catch (ForbiddenException $exception) {
|
||||
return new JsonResponse(
|
||||
['error' => $exception->getMessage()], 403,
|
||||
);
|
||||
}
|
||||
|
||||
return new JsonResponse(null, 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* id: int,
|
||||
* postId: int,
|
||||
* userId: int,
|
||||
* authorDisplayName: string,
|
||||
* body: string,
|
||||
* createdAt: string
|
||||
* }
|
||||
*/
|
||||
private function serialize(Comment $comment): array
|
||||
{
|
||||
$author = $this->userRepo->find($comment->getUserId());
|
||||
|
||||
return [
|
||||
'id' => $comment->getId(),
|
||||
'postId' => $comment->getPostId(),
|
||||
'userId' => $comment->getUserId(),
|
||||
'authorDisplayName' => $author === null
|
||||
? ''
|
||||
: $author->getDisplayName(),
|
||||
'body' => $comment->getBody(),
|
||||
'createdAt' => $comment->getCreatedAt()->format(DATE_ATOM),
|
||||
];
|
||||
}
|
||||
}
|
||||
181
backend/app/Controllers/PostController.php
Normal file
181
backend/app/Controllers/PostController.php
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Exceptions\BadRequestException;
|
||||
use App\Exceptions\ForbiddenException;
|
||||
use App\Post\Post;
|
||||
use App\Post\UseCases\CreatePost\CreatePost;
|
||||
use App\Post\UseCases\CreatePost\CreatePostRequest;
|
||||
use App\Post\UseCases\DeletePost\DeletePost;
|
||||
use App\Post\UseCases\DeletePost\DeletePostRequest;
|
||||
use App\Post\UseCases\GetPost\GetPost;
|
||||
use App\Post\UseCases\ListRecentPosts\ListRecentPosts;
|
||||
use App\Post\UseCases\ListRecentPosts\ListRecentPostsRequest;
|
||||
use App\Post\UseCases\ListUserPosts\ListUserPosts;
|
||||
use App\Post\UseCases\ListUserPosts\ListUserPostsRequest;
|
||||
use App\User\User;
|
||||
use App\User\UserRepository;
|
||||
use DomainException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PostController
|
||||
{
|
||||
private const RECENT_LIMIT = 20;
|
||||
|
||||
public function __construct(
|
||||
private CreatePost $createPost,
|
||||
private DeletePost $deletePost,
|
||||
private GetPost $getPost,
|
||||
private ListRecentPosts $listRecentPosts,
|
||||
private ListUserPosts $listUserPosts,
|
||||
private UserRepository $userRepo,
|
||||
) {}
|
||||
|
||||
public function recent(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$posts = $this->listRecentPosts->execute(
|
||||
new ListRecentPostsRequest(limit: self::RECENT_LIMIT),
|
||||
);
|
||||
} catch (BadRequestException $exception) {
|
||||
return new JsonResponse(
|
||||
['error' => $exception->getMessage()], 400,
|
||||
);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'posts' => array_map(
|
||||
function (Post $post) {
|
||||
return $this->serialize($post);
|
||||
},
|
||||
$posts,
|
||||
),
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function show(Request $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$post = $this->getPost->execute($id);
|
||||
} catch (BadRequestException $exception) {
|
||||
return new JsonResponse(
|
||||
['error' => $exception->getMessage()], 400,
|
||||
);
|
||||
}
|
||||
if ($post === null) {
|
||||
return new JsonResponse(['error' => 'post not found'], 404);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'post' => $this->serialize($post),
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function listByUser(
|
||||
Request $request,
|
||||
string $displayName,
|
||||
): JsonResponse {
|
||||
$user = $this->userRepo->findByDisplayName($displayName);
|
||||
if ($user === null) {
|
||||
return new JsonResponse(['error' => 'user not found'], 404);
|
||||
}
|
||||
try {
|
||||
$posts = $this->listUserPosts->execute(
|
||||
new ListUserPostsRequest(userId: $user->getId()),
|
||||
);
|
||||
} catch (BadRequestException $exception) {
|
||||
return new JsonResponse(
|
||||
['error' => $exception->getMessage()], 400,
|
||||
);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'user' => [
|
||||
'id' => $user->getId(),
|
||||
'displayName' => $user->getDisplayName(),
|
||||
],
|
||||
'posts' => array_map(
|
||||
function (Post $post) {
|
||||
return $this->serialize($post);
|
||||
},
|
||||
$posts,
|
||||
),
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function create(Request $request): JsonResponse
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $request->attributes->get('user');
|
||||
try {
|
||||
$post = $this->createPost->execute(new CreatePostRequest(
|
||||
userId: $user->getId(),
|
||||
title: $request->input('title'),
|
||||
body: $request->input('body'),
|
||||
));
|
||||
} catch (BadRequestException $exception) {
|
||||
return new JsonResponse(
|
||||
['error' => $exception->getMessage()], 400,
|
||||
);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'post' => $this->serialize($post),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function delete(Request $request, int $id): JsonResponse
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $request->attributes->get('user');
|
||||
try {
|
||||
$this->deletePost->execute(new DeletePostRequest(
|
||||
postId: $id,
|
||||
requesterId: $user->getId(),
|
||||
requesterIsAdmin: $user->isAdmin(),
|
||||
));
|
||||
} catch (BadRequestException $exception) {
|
||||
return new JsonResponse(
|
||||
['error' => $exception->getMessage()], 400,
|
||||
);
|
||||
} catch (ForbiddenException $exception) {
|
||||
return new JsonResponse(
|
||||
['error' => $exception->getMessage()], 403,
|
||||
);
|
||||
} catch (DomainException $exception) {
|
||||
return new JsonResponse(
|
||||
['error' => $exception->getMessage()], 409,
|
||||
);
|
||||
}
|
||||
|
||||
return new JsonResponse(null, 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* id: int,
|
||||
* userId: int,
|
||||
* authorDisplayName: string,
|
||||
* title: string,
|
||||
* body: string,
|
||||
* createdAt: string
|
||||
* }
|
||||
*/
|
||||
private function serialize(Post $post): array
|
||||
{
|
||||
$author = $this->userRepo->find($post->getUserId());
|
||||
|
||||
return [
|
||||
'id' => $post->getId(),
|
||||
'userId' => $post->getUserId(),
|
||||
'authorDisplayName' => $author === null
|
||||
? ''
|
||||
: $author->getDisplayName(),
|
||||
'title' => $post->getTitle(),
|
||||
'body' => $post->getBody(),
|
||||
'createdAt' => $post->getCreatedAt()->format(DATE_ATOM),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ namespace App\Providers;
|
|||
|
||||
use App\Auth\EloquentSessionRepository;
|
||||
use App\Auth\SessionRepository;
|
||||
use App\Comment\CommentRepository;
|
||||
use App\Comment\EloquentCommentRepository;
|
||||
use App\Email\EmailConfirmationToken\EloquentEmailConfirmationTokenRepository;
|
||||
use App\Email\EmailConfirmationToken\EmailConfirmationTokenRepository;
|
||||
use App\Post\EloquentPostRepository;
|
||||
|
|
@ -32,5 +34,9 @@ class RepositoryServiceProvider extends ServiceProvider
|
|||
PostRepository::class,
|
||||
EloquentPostRepository::class,
|
||||
);
|
||||
$this->app->bind(
|
||||
CommentRepository::class,
|
||||
EloquentCommentRepository::class,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
enforceTimeLimit="true"
|
||||
defaultTimeLimit="30"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Unit">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
|
||||
use App\Controllers\AuthController;
|
||||
use App\Controllers\CommentController;
|
||||
use App\Controllers\PostController;
|
||||
use App\Http\Middleware\AuthMiddleware;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
|
|
@ -15,3 +17,32 @@ Route::post('/logout', [AuthController::class, 'logout'])
|
|||
->middleware(AuthMiddleware::class);
|
||||
Route::get('/me', [AuthController::class, 'me'])
|
||||
->middleware(AuthMiddleware::class);
|
||||
|
||||
Route::get('/posts', [PostController::class, 'recent']);
|
||||
Route::get('/posts/{id}', [PostController::class, 'show'])
|
||||
->whereNumber('id');
|
||||
Route::post('/posts', [PostController::class, 'create'])
|
||||
->middleware(AuthMiddleware::class);
|
||||
Route::delete('/posts/{id}', [PostController::class, 'delete'])
|
||||
->whereNumber('id')
|
||||
->middleware(AuthMiddleware::class);
|
||||
|
||||
Route::get(
|
||||
'/users/{displayName}/posts',
|
||||
[PostController::class, 'listByUser'],
|
||||
);
|
||||
|
||||
Route::get(
|
||||
'/posts/{postId}/comments',
|
||||
[CommentController::class, 'listForPost'],
|
||||
)->whereNumber('postId');
|
||||
Route::post(
|
||||
'/posts/{postId}/comments',
|
||||
[CommentController::class, 'create'],
|
||||
)->whereNumber('postId')
|
||||
->middleware(AuthMiddleware::class);
|
||||
Route::delete(
|
||||
'/comments/{id}',
|
||||
[CommentController::class, 'delete'],
|
||||
)->whereNumber('id')
|
||||
->middleware(AuthMiddleware::class);
|
||||
|
|
|
|||
72
backend/tests/Feature/AuthenticatesUsers.php
Normal file
72
backend/tests/Feature/AuthenticatesUsers.php
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Email\EmailConfirmationToken\EmailConfirmationTokenRepository;
|
||||
use App\Shared\ValueObject\EmailAddress;
|
||||
use App\User\User;
|
||||
use App\User\UserRepository;
|
||||
|
||||
trait AuthenticatesUsers
|
||||
{
|
||||
/**
|
||||
* @return array{user: User, cookie: string}
|
||||
*/
|
||||
private function signupAndLogin(
|
||||
string $email,
|
||||
string $displayName,
|
||||
string $password,
|
||||
): array {
|
||||
$this->postJson('/api/signup', [
|
||||
'email' => $email,
|
||||
'displayName' => $displayName,
|
||||
])->assertStatus(201);
|
||||
|
||||
$userRepo = $this->app->make(UserRepository::class);
|
||||
$user = $userRepo->findByEmail(new EmailAddress($email));
|
||||
$tokenRepo = $this->app->make(
|
||||
EmailConfirmationTokenRepository::class,
|
||||
);
|
||||
$token = $tokenRepo->findByUser($user);
|
||||
|
||||
$this->postJson('/api/confirm-email', [
|
||||
'token' => $token->getToken(),
|
||||
'password' => $password,
|
||||
])->assertStatus(200);
|
||||
|
||||
$loginResponse = $this->postJson('/api/login', [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
$loginResponse->assertStatus(200);
|
||||
$cookie = $loginResponse->getCookie('auth_token', false);
|
||||
|
||||
$reloaded = $userRepo->findByEmail(new EmailAddress($email));
|
||||
|
||||
return [
|
||||
'user' => $reloaded,
|
||||
'cookie' => $cookie->getValue(),
|
||||
];
|
||||
}
|
||||
|
||||
private function resetClientState(): void
|
||||
{
|
||||
$this->defaultCookies = [];
|
||||
$this->unencryptedCookies = [];
|
||||
$this->withCredentials = false;
|
||||
}
|
||||
|
||||
private function promoteToAdmin(int $userId): void
|
||||
{
|
||||
$userRepo = $this->app->make(UserRepository::class);
|
||||
$user = $userRepo->find($userId);
|
||||
$userRepo->update(new User(
|
||||
id: $user->getId(),
|
||||
email: $user->getEmail(),
|
||||
displayName: $user->getDisplayName(),
|
||||
passwordHash: $user->getPasswordHash(),
|
||||
isAdmin: true,
|
||||
emailConfirmedAt: $user->getEmailConfirmedAt(),
|
||||
));
|
||||
}
|
||||
}
|
||||
172
backend/tests/Feature/Comment/CommentFlowTest.php
Normal file
172
backend/tests/Feature/Comment/CommentFlowTest.php
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Comment;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\Feature\AuthenticatesUsers;
|
||||
use Tests\TestCase;
|
||||
|
||||
class CommentFlowTest extends TestCase
|
||||
{
|
||||
use AuthenticatesUsers;
|
||||
use RefreshDatabase;
|
||||
|
||||
private function createPost(string $cookie, string $title): int
|
||||
{
|
||||
$response = $this->withCredentials()->withUnencryptedCookie('auth_token', $cookie)
|
||||
->postJson('/api/posts', [
|
||||
'title' => $title,
|
||||
'body' => 'b',
|
||||
]);
|
||||
$response->assertStatus(201);
|
||||
|
||||
return $response->json('post.id');
|
||||
}
|
||||
|
||||
public function test_anonymous_can_list_comments(): void
|
||||
{
|
||||
$alice = $this->signupAndLogin(
|
||||
email: 'alice@example.com',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$postId = $this->createPost($alice['cookie'], 'P1');
|
||||
|
||||
$response = $this->getJson("/api/posts/{$postId}/comments");
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonPath('comments', []);
|
||||
}
|
||||
|
||||
public function test_authenticated_user_creates_comment(): void
|
||||
{
|
||||
$alice = $this->signupAndLogin(
|
||||
email: 'alice@example.com',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$postId = $this->createPost($alice['cookie'], 'P1');
|
||||
|
||||
$bob = $this->signupAndLogin(
|
||||
email: 'bob@example.com',
|
||||
displayName: 'bob',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$response = $this->withCredentials()->withUnencryptedCookie('auth_token', $bob['cookie'])
|
||||
->postJson("/api/posts/{$postId}/comments", [
|
||||
'body' => 'nice post',
|
||||
]);
|
||||
$response->assertStatus(201);
|
||||
$response->assertJsonPath('comment.body', 'nice post');
|
||||
$response->assertJsonPath('comment.authorDisplayName', 'bob');
|
||||
}
|
||||
|
||||
public function test_anonymous_cannot_create_comment(): void
|
||||
{
|
||||
$alice = $this->signupAndLogin(
|
||||
email: 'alice@example.com',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$postId = $this->createPost($alice['cookie'], 'P1');
|
||||
|
||||
$this->resetClientState();
|
||||
$this->postJson("/api/posts/{$postId}/comments", [
|
||||
'body' => 'hi',
|
||||
])->assertStatus(401);
|
||||
}
|
||||
|
||||
public function test_create_on_missing_post_returns_404(): void
|
||||
{
|
||||
$alice = $this->signupAndLogin(
|
||||
email: 'alice@example.com',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$this->withCredentials()->withUnencryptedCookie('auth_token', $alice['cookie'])
|
||||
->postJson('/api/posts/9999/comments', [
|
||||
'body' => 'hi',
|
||||
])->assertStatus(404);
|
||||
}
|
||||
|
||||
public function test_author_deletes_own_comment(): void
|
||||
{
|
||||
$alice = $this->signupAndLogin(
|
||||
email: 'alice@example.com',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$postId = $this->createPost($alice['cookie'], 'P1');
|
||||
|
||||
$bob = $this->signupAndLogin(
|
||||
email: 'bob@example.com',
|
||||
displayName: 'bob',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$createResponse = $this->withCredentials()->withUnencryptedCookie('auth_token', $bob['cookie'])
|
||||
->postJson("/api/posts/{$postId}/comments", [
|
||||
'body' => 'nice',
|
||||
]);
|
||||
$commentId = $createResponse->json('comment.id');
|
||||
|
||||
$this->withCredentials()->withUnencryptedCookie('auth_token', $bob['cookie'])
|
||||
->deleteJson("/api/comments/{$commentId}")
|
||||
->assertStatus(204);
|
||||
}
|
||||
|
||||
public function test_other_user_cannot_delete_comment(): void
|
||||
{
|
||||
$alice = $this->signupAndLogin(
|
||||
email: 'alice@example.com',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$postId = $this->createPost($alice['cookie'], 'P1');
|
||||
|
||||
$bob = $this->signupAndLogin(
|
||||
email: 'bob@example.com',
|
||||
displayName: 'bob',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$createResponse = $this->withCredentials()->withUnencryptedCookie('auth_token', $bob['cookie'])
|
||||
->postJson("/api/posts/{$postId}/comments", [
|
||||
'body' => 'nice',
|
||||
]);
|
||||
$commentId = $createResponse->json('comment.id');
|
||||
|
||||
$this->withCredentials()->withUnencryptedCookie('auth_token', $alice['cookie'])
|
||||
->deleteJson("/api/comments/{$commentId}")
|
||||
->assertStatus(403);
|
||||
}
|
||||
|
||||
public function test_admin_deletes_any_comment(): void
|
||||
{
|
||||
$alice = $this->signupAndLogin(
|
||||
email: 'alice@example.com',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$postId = $this->createPost($alice['cookie'], 'P1');
|
||||
|
||||
$bob = $this->signupAndLogin(
|
||||
email: 'bob@example.com',
|
||||
displayName: 'bob',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$createResponse = $this->withCredentials()->withUnencryptedCookie('auth_token', $bob['cookie'])
|
||||
->postJson("/api/posts/{$postId}/comments", [
|
||||
'body' => 'nice',
|
||||
]);
|
||||
$commentId = $createResponse->json('comment.id');
|
||||
|
||||
$this->promoteToAdmin($alice['user']->getId());
|
||||
$loginResponse = $this->postJson('/api/login', [
|
||||
'email' => 'alice@example.com',
|
||||
'password' => 'longenoughpassword',
|
||||
]);
|
||||
$aliceCookie = $loginResponse->getCookie('auth_token', false);
|
||||
|
||||
$this->withCredentials()->withUnencryptedCookie('auth_token', $aliceCookie->getValue())
|
||||
->deleteJson("/api/comments/{$commentId}")
|
||||
->assertStatus(204);
|
||||
}
|
||||
}
|
||||
156
backend/tests/Feature/Post/PostFlowTest.php
Normal file
156
backend/tests/Feature/Post/PostFlowTest.php
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Post;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\Feature\AuthenticatesUsers;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PostFlowTest extends TestCase
|
||||
{
|
||||
use AuthenticatesUsers;
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_authenticated_user_creates_post(): void
|
||||
{
|
||||
$session = $this->signupAndLogin(
|
||||
email: 'alice@example.com',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
|
||||
$response = $this->withCredentials()->withUnencryptedCookie('auth_token', $session['cookie'])
|
||||
->postJson('/api/posts', [
|
||||
'title' => 'My Post',
|
||||
'body' => 'Hello world',
|
||||
]);
|
||||
$response->assertStatus(201);
|
||||
$response->assertJsonPath('post.title', 'My Post');
|
||||
$response->assertJsonPath('post.authorDisplayName', 'alice');
|
||||
}
|
||||
|
||||
public function test_anonymous_create_post_returns_401(): void
|
||||
{
|
||||
$this->postJson('/api/posts', [
|
||||
'title' => 'My Post',
|
||||
'body' => 'Hello world',
|
||||
])->assertStatus(401);
|
||||
}
|
||||
|
||||
public function test_recent_posts_are_public(): void
|
||||
{
|
||||
$session = $this->signupAndLogin(
|
||||
email: 'alice@example.com',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$this->withCredentials()->withUnencryptedCookie('auth_token', $session['cookie'])
|
||||
->postJson('/api/posts', [
|
||||
'title' => 'P1',
|
||||
'body' => 'B1',
|
||||
])->assertStatus(201);
|
||||
|
||||
$response = $this->getJson('/api/posts');
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonPath('posts.0.title', 'P1');
|
||||
}
|
||||
|
||||
public function test_show_returns_404_when_missing(): void
|
||||
{
|
||||
$this->getJson('/api/posts/9999')->assertStatus(404);
|
||||
}
|
||||
|
||||
public function test_user_posts_listed_by_display_name(): void
|
||||
{
|
||||
$session = $this->signupAndLogin(
|
||||
email: 'alice@example.com',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$this->withCredentials()->withUnencryptedCookie('auth_token', $session['cookie'])
|
||||
->postJson('/api/posts', [
|
||||
'title' => 'A1',
|
||||
'body' => 'b',
|
||||
])->assertStatus(201);
|
||||
|
||||
$response = $this->getJson('/api/users/alice/posts');
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonPath('user.displayName', 'alice');
|
||||
$response->assertJsonPath('posts.0.title', 'A1');
|
||||
}
|
||||
|
||||
public function test_other_user_cannot_delete_post(): void
|
||||
{
|
||||
$alice = $this->signupAndLogin(
|
||||
email: 'alice@example.com',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$createResponse = $this->withCredentials()->withUnencryptedCookie('auth_token', $alice['cookie'])
|
||||
->postJson('/api/posts', [
|
||||
'title' => 'A1',
|
||||
'body' => 'b',
|
||||
]);
|
||||
$postId = $createResponse->json('post.id');
|
||||
|
||||
$bob = $this->signupAndLogin(
|
||||
email: 'bob@example.com',
|
||||
displayName: 'bob',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$this->withCredentials()->withUnencryptedCookie('auth_token', $bob['cookie'])
|
||||
->deleteJson("/api/posts/{$postId}")
|
||||
->assertStatus(403);
|
||||
}
|
||||
|
||||
public function test_author_deletes_own_post(): void
|
||||
{
|
||||
$alice = $this->signupAndLogin(
|
||||
email: 'alice@example.com',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$createResponse = $this->withCredentials()->withUnencryptedCookie('auth_token', $alice['cookie'])
|
||||
->postJson('/api/posts', [
|
||||
'title' => 'A1',
|
||||
'body' => 'b',
|
||||
]);
|
||||
$postId = $createResponse->json('post.id');
|
||||
|
||||
$this->withCredentials()->withUnencryptedCookie('auth_token', $alice['cookie'])
|
||||
->deleteJson("/api/posts/{$postId}")
|
||||
->assertStatus(204);
|
||||
}
|
||||
|
||||
public function test_admin_deletes_anyones_post(): void
|
||||
{
|
||||
$alice = $this->signupAndLogin(
|
||||
email: 'alice@example.com',
|
||||
displayName: 'alice',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$createResponse = $this->withCredentials()->withUnencryptedCookie('auth_token', $alice['cookie'])
|
||||
->postJson('/api/posts', [
|
||||
'title' => 'A1',
|
||||
'body' => 'b',
|
||||
]);
|
||||
$postId = $createResponse->json('post.id');
|
||||
|
||||
$bob = $this->signupAndLogin(
|
||||
email: 'bob@example.com',
|
||||
displayName: 'bob',
|
||||
password: 'longenoughpassword',
|
||||
);
|
||||
$this->promoteToAdmin($bob['user']->getId());
|
||||
// Re-login bob to get a fresh cookie/payload
|
||||
$loginResponse = $this->postJson('/api/login', [
|
||||
'email' => 'bob@example.com',
|
||||
'password' => 'longenoughpassword',
|
||||
]);
|
||||
$bobCookie = $loginResponse->getCookie('auth_token', false);
|
||||
|
||||
$this->withCredentials()->withUnencryptedCookie('auth_token', $bobCookie->getValue())
|
||||
->deleteJson("/api/posts/{$postId}")
|
||||
->assertStatus(204);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue