diff --git a/app/Http/Controllers/QuestionAnswerController.php b/app/Http/Controllers/QuestionAnswerController.php index 23a9ed2f..e1dfa4a3 100644 --- a/app/Http/Controllers/QuestionAnswerController.php +++ b/app/Http/Controllers/QuestionAnswerController.php @@ -104,4 +104,16 @@ public function destroy(Answer $answer): RedirectResponse ->back() ->with("success", "Answer deleted"); } + + /** + * @throws AuthorizationException + */ + public function clone(Answer $answer, Question $question): RedirectResponse + { + $answer->cloneTo($question); + + return redirect() + ->back() + ->with("success", "Answer cloned"); + } } diff --git a/app/Http/Controllers/QuizQuestionController.php b/app/Http/Controllers/QuizQuestionController.php index a243f805..f2389a19 100644 --- a/app/Http/Controllers/QuizQuestionController.php +++ b/app/Http/Controllers/QuizQuestionController.php @@ -81,4 +81,16 @@ public function destroy(Question $question): RedirectResponse ->back() ->with("success", "Question deleted"); } + + /** + * @throws AuthorizationException + */ + public function clone(Question $question, Quiz $quiz): RedirectResponse + { + $question->cloneTo($quiz); + + return redirect() + ->back() + ->with("success", "Question cloned"); + } } diff --git a/app/Models/Question.php b/app/Models/Question.php index 98baf5ca..d05bf809 100644 --- a/app/Models/Question.php +++ b/app/Models/Question.php @@ -5,6 +5,7 @@ namespace App\Models; use Carbon\Carbon; +use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -53,4 +54,29 @@ public function isLocked(): Attribute { return Attribute::get(fn(): bool => $this->quiz->isLocked); } + + /** + * @throws AuthorizationException + */ + public function cloneTo(Quiz $quiz): self + { + if ($quiz->isLocked) { + throw new AuthorizationException(); + } + + $questionCopy = $this->replicate(); + $questionCopy->quiz()->associate($quiz)->save(); + + foreach ($this->answers as $answer) { + $answerCopy = $answer->cloneTo($questionCopy); + + if ($answer->isCorrect) { + $questionCopy->correctAnswer()->associate($answerCopy); + } + } + + $questionCopy->save(); + + return $questionCopy; + } } diff --git a/routes/web.php b/routes/web.php index 1f635218..c8a188e0 100644 --- a/routes/web.php +++ b/routes/web.php @@ -11,6 +11,7 @@ Route::get("/", fn(): Response => inertia("Welcome")); Route::post("/quizzes/{quiz}/lock", [QuizController::class, "lock"]); +Route::post("/questions/{question}/clone/{quiz}", [QuizQuestionController::class, "clone"]); Route::post("/answers/{answer}/correct", [QuestionAnswerController::class, "markAsCorrect"]); Route::post("/answers/{answer}/invalid", [QuestionAnswerController::class, "markAsInvalid"]); Route::post("/answers/{answer}/clone/{question}", [QuestionAnswerController::class, "clone"]); diff --git a/tests/Feature/QuestionTest.php b/tests/Feature/QuestionTest.php index e037f10d..1d6fb8b8 100644 --- a/tests/Feature/QuestionTest.php +++ b/tests/Feature/QuestionTest.php @@ -267,4 +267,101 @@ public function testUserCannotDeleteQuestionThatNotExisted(): void ->delete("/questions/1") ->assertStatus(404); } + + public function testUserCanCopyQuestion(): void + { + $user = User::factory()->create(); + $quizA = Quiz::factory()->create(); + $quizB = Quiz::factory()->create(); + $question = Question::factory()->create(["quiz_id" => $quizA->id]); + Answer::factory()->count(10)->create(["question_id" => $question->id]); + + $this->assertDatabaseHas("questions", ["quiz_id" => $quizA->id]); + $this->assertDatabaseCount("answers", 10); + + $this->actingAs($user) + ->from("/quizzes") + ->post("/questions/{$question->id}/clone/{$quizB->id}") + ->assertRedirect("/quizzes"); + + $this->assertDatabaseHas("questions", ["quiz_id" => $quizB->id]); + $this->assertDatabaseCount("answers", 20); + } + + public function testUserCanCopyLockedQuestion(): void + { + $user = User::factory()->create(); + $quizA = Quiz::factory()->locked()->create(); + $quizB = Quiz::factory()->create(); + $question = Question::factory()->create(["quiz_id" => $quizA->id]); + + $this->assertDatabaseHas("questions", ["quiz_id" => $quizA->id]); + + $this->actingAs($user) + ->from("/quizzes") + ->post("/questions/{$question->id}/clone/{$quizB->id}") + ->assertRedirect("/quizzes"); + + $this->assertDatabaseHas("questions", ["quiz_id" => $quizB->id]); + } + + public function testUserCannotCopyAnswerToLockedQuestion(): void + { + $user = User::factory()->create(); + $quizA = Quiz::factory()->create(); + $quizB = Quiz::factory()->locked()->create(); + $question = Question::factory()->create(["quiz_id" => $quizA->id]); + + $this->assertDatabaseHas("questions", ["quiz_id" => $quizA->id]); + + $this->actingAs($user) + ->from("/quizzes") + ->post("/questions/{$question->id}/clone/{$quizB->id}") + ->assertStatus(403); + + $this->assertDatabaseHas("questions", ["quiz_id" => $quizA->id]); + } + + public function testUserCanCopyQuestionWithCorrectAnswer(): void + { + $user = User::factory()->create(); + $quizA = Quiz::factory()->create(); + $quizB = Quiz::factory()->create(); + $question = Question::factory()->create(["quiz_id" => $quizA->id]); + $answer = Answer::factory()->create(["text" => "correct", "question_id" => $question->id]); + + $question->correctAnswer()->associate($answer); + $question->save(); + + $this->actingAs($user) + ->from("/quizzes") + ->post("/questions/{$question->id}/clone/{$quizB->id}") + ->assertRedirect("/quizzes"); + + $this->assertNotNull($quizA->questions[0]->correctAnswer); + $this->assertNotNull($quizB->questions[0]->correctAnswer); + $this->assertNotEquals($quizA->questions[0]->correctAnswer->id, $quizB->questions[0]->correctAnswer->id); + } + + public function testUserCannotCopyQuestionThatNotExisted(): void + { + $user = User::factory()->create(); + $quiz = Question::factory()->create(); + + $this->actingAs($user) + ->from("/quizzes") + ->post("/questions/2/clone/{$quiz->id}") + ->assertStatus(404); + } + + public function testUserCannotCopyAnswerToQuestionThatNotExisted(): void + { + $user = User::factory()->create(); + $question = Question::factory()->create(); + + $this->actingAs($user) + ->from("/quizzes") + ->post("/questions/{$question->id}/clone/2") + ->assertStatus(404); + } }