This repository has been archived by the owner on Jun 29, 2021. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Resolves #32 - A guest or an authenticated user can list answers of a…
… question (#53) * #32 gets answers from a question * #32 fix repeated route name * #32 code review improvements * #32 adds second round of code review * #32 adds minor code reviews * #32 adds final code review * #32 removes unneeded index * #32 edits changelog Co-authored-by: José Postiga <[email protected]>
- Loading branch information
1 parent
25521ea
commit 0ea6b54
Showing
6 changed files
with
239 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
domains/Discussions/Controllers/AnswersIndexController.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<?php | ||
|
||
namespace Domains\Discussions\Controllers; | ||
|
||
use Illuminate\Http\Request; | ||
use App\Http\Controllers\Controller; | ||
use Domains\Discussions\Models\Question; | ||
use Illuminate\Database\Eloquent\Builder; | ||
use Illuminate\Contracts\Auth\Factory as Auth; | ||
use Domains\Discussions\Resources\AnswerResource; | ||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection; | ||
|
||
class AnswersIndexController extends Controller | ||
{ | ||
private Question $question; | ||
|
||
public function __construct(Auth $auth, Question $question) | ||
{ | ||
if ($auth->guest()) { | ||
$this->middleware('throttle:30,1'); | ||
} | ||
|
||
$this->question = $question; | ||
} | ||
|
||
public function __invoke(Request $request, int $questionId): AnonymousResourceCollection | ||
{ | ||
$this->validate($request, [ | ||
'author' => ['sometimes', 'integer', 'exists:users,id'], | ||
'created' => ['sometimes', 'array', 'size:2'], | ||
'created.from' => ['required_with:created', 'date'], | ||
'created.to' => ['required_with:created', 'date', 'afterOrEqual:created.from'] | ||
]); | ||
|
||
$answers = $this->question | ||
->findOrFail($questionId) | ||
->answers() | ||
->when($authorId = $request->input('author'), | ||
static fn(Builder $answers) => $answers->whereAuthorId($authorId)) | ||
->when($created = $request->input('created'), | ||
static fn(Builder $answers) => $answers->whereBetween('created_at', [$created['from'], $created['to']])) | ||
->latest() | ||
->simplePaginate(15); | ||
|
||
return AnswerResource::collection($answers); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
|
||
namespace Domains\Discussions\Resources; | ||
|
||
use Domains\Accounts\Resources\UserResource; | ||
use Illuminate\Http\Resources\Json\JsonResource; | ||
|
||
class AnswerResource extends JsonResource | ||
{ | ||
public function toArray($request): array | ||
{ | ||
return [ | ||
'id' => $this->id, | ||
'content' => $this->content, | ||
'question_id' => $this->question_id, | ||
'author_id' => $this->author_id, | ||
'created_at' => $this->created_at, | ||
'updated_at' => $this->updated_at, | ||
'deleted_at' => $this->deleted_at, | ||
'question' => QuestionResource::collection( | ||
$this->whenLoaded('question') | ||
), | ||
'author' => UserResource::collection( | ||
$this->whenLoaded('author') | ||
), | ||
]; | ||
} | ||
} |
151 changes: 151 additions & 0 deletions
151
domains/Discussions/Tests/Feature/AnswersIndexControllerTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
<?php | ||
|
||
namespace Domains\Discussions\Tests\Feature; | ||
|
||
use Carbon\Carbon; | ||
use Domains\Accounts\Database\Factories\UserFactory; | ||
use Domains\Accounts\Models\User; | ||
use Domains\Discussions\Database\Factories\AnswerFactory; | ||
use Domains\Discussions\Database\Factories\QuestionFactory; | ||
use Domains\Discussions\Models\Answer; | ||
use Domains\Discussions\Models\Question; | ||
use Illuminate\Http\Response; | ||
use Laravel\Lumen\Testing\DatabaseMigrations; | ||
use Tests\TestCase; | ||
|
||
class AnswersIndexControllerTest extends TestCase | ||
{ | ||
use DatabaseMigrations; | ||
|
||
private User $user; | ||
private Question $question; | ||
private Answer $answer; | ||
private Answer $secondAnswer; | ||
|
||
protected function setUp(): void | ||
{ | ||
parent::setUp(); | ||
|
||
$this->user = UserFactory::new()->create(); | ||
|
||
$this->question = QuestionFactory::new([ | ||
'author_id' => $this->user->id | ||
])->create(); | ||
|
||
$this->answer = AnswerFactory::new([ | ||
'question_id' => $this->question->id, | ||
'author_id' => $this->user->id, | ||
'created_at' => Carbon::now()->subWeek()->toDateTimeString() | ||
])->create(); | ||
|
||
$this->secondAnswer = AnswerFactory::new([ | ||
'question_id' => $this->question->id, | ||
'created_at' => Carbon::now()->toDateTimeString() | ||
])->create(); | ||
} | ||
|
||
/** @test */ | ||
public function it_gets_paginated_answers_for_a_question(): void | ||
{ | ||
$this->get(route('discussions.questions.answers.list', ['questionId' => $this->question->id])) | ||
->seeJsonStructure([ | ||
'data' => [ | ||
[ | ||
'id', | ||
'content', | ||
'question_id', | ||
'author_id', | ||
'created_at', | ||
'updated_at', | ||
'deleted_at' | ||
] | ||
] | ||
]) | ||
->seeJsonContains(['id' => $this->answer->id]) | ||
->seeJsonContains(['content' => $this->answer->content]) | ||
->seeJsonContains(['id' => $this->secondAnswer->id]) | ||
->seeJsonContains(['content' => $this->secondAnswer->content]); | ||
} | ||
|
||
/** @test * */ | ||
public function it_gets_paginated_answers_for_a_question_from_a_particular_author(): void | ||
{ | ||
$this->get(route('discussions.questions.answers.list', ['questionId' => $this->question->id, 'author' => $this->user->id])) | ||
->seeJson(['id' => $this->answer->id]) | ||
->dontSeeJson(['id' => $this->secondAnswer->id]); | ||
} | ||
|
||
/** @test */ | ||
public function it_gets_paginated_answers_for_a_question_from_a_particular_time_frame(): void | ||
{ | ||
$aWeekAgo = Carbon::now()->subDays(8); | ||
$yesterday = Carbon::yesterday(); | ||
|
||
$this->get(route('discussions.questions.answers.list', ['questionId' => $this->question->id]) . '?created[from]=' . $aWeekAgo->format('Y-m-d') . '&created[to]=' . $yesterday->format('Y-m-d')) | ||
->seeJsonContains(['id' => $this->answer->id]) | ||
->seeJsonContains(['content' => $this->answer->content]) | ||
->seeJsonDoesntContains([ | ||
"id" => $this->secondAnswer->id | ||
]); | ||
} | ||
|
||
/** @test */ | ||
public function it_gets_paginated_answers_for_a_question_from_a_particular_time_frame_and_user(): void | ||
{ | ||
$thirdAnswer = AnswerFactory::new([ | ||
'question_id' => $this->question->id, | ||
'author_id' => $this->user->id, | ||
'created_at' => Carbon::now()->subWeek()->toDateTimeString() | ||
])->create(); | ||
|
||
$aWeekAgo = Carbon::now()->subDays(8); | ||
$yesterday = Carbon::yesterday(); | ||
|
||
$this->get(route('discussions.questions.answers.list', ['questionId' => $this->question->id]) . '?created[from]=' . $aWeekAgo->format('Y-m-d') . '&created[to]=' . $yesterday->format('Y-m-d') . '&author=1') | ||
->seeJsonContains(['id' => $this->answer->id]) | ||
->seeJsonContains(['content' => $this->answer->content]) | ||
->seeJsonContains(['id' => $thirdAnswer->id]) | ||
->seeJsonContains(['content' => $thirdAnswer->content]) | ||
->dontSeeJson([ | ||
"id" => $this->secondAnswer->id | ||
]); | ||
} | ||
|
||
/** @test */ | ||
public function it_blocks_guest_for_many_attempts(): void | ||
{ | ||
for ($attempt = 0; $attempt < 30; ++$attempt) { | ||
$this->get(route('discussions.questions.answers.list', ['questionId' => $this->question->id])) | ||
->assertResponseStatus(Response::HTTP_OK); | ||
} | ||
|
||
$this->get(route('discussions.questions.answers.list', ['questionId' => $this->question->id])) | ||
->assertResponseStatus(Response::HTTP_TOO_MANY_REQUESTS); | ||
} | ||
|
||
/** @test */ | ||
public function it_not_blocks_authenticated_user_for_many_attempts(): void | ||
{ | ||
$this->actingAs($this->user); | ||
|
||
for ($attempt = 0; $attempt < 30; ++$attempt) { | ||
$this->get(route('discussions.questions.answers.list', ['questionId' => $this->question->id])); | ||
} | ||
|
||
$this->get(route('discussions.questions.answers.list', ['questionId' => $this->question->id])) | ||
->assertResponseStatus(Response::HTTP_OK); | ||
} | ||
|
||
/** @test */ | ||
public function it_gets_question_and_author_when_loaded(): void | ||
{ | ||
$answer = AnswerFactory::new([ | ||
'question_id' => $this->question->id, | ||
'author_id' => $this->user->id, | ||
'created_at' => Carbon::now()->subWeek()->toDateTimeString() | ||
])->create()->load('question', 'author'); | ||
|
||
$this->assertArrayHasKey('author', $answer->relationsToArray()); | ||
$this->assertArrayHasKey('question', $answer->relationsToArray()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters