From 08e323338ae5bc136cf8cd1793d950f0e6f99192 Mon Sep 17 00:00:00 2001 From: Dominik Prabucki Date: Fri, 13 Dec 2024 17:14:57 +0100 Subject: [PATCH 01/10] Add local and online version of quiz --- app/Actions/SetQuizLocalAction.php | 16 +++++++++ app/Actions/SetQuizOnlineAction.php | 16 +++++++++ app/Http/Controllers/QuizController.php | 20 +++++++++++ app/Http/Resources/QuizResource.php | 4 ++- app/Models/Quiz.php | 3 ++ app/Policies/QuizPolicy.php | 2 +- database/factories/QuizFactory.php | 8 +++++ ...2024_08_08_123620_create_quizzes_table.php | 1 + ...3_141755_add_is_local_to_quizzes_table.php | 23 ++++++++++++ routes/web.php | 2 ++ tests/Feature/QuizTest.php | 31 ++++++++++++++-- tests/Unit/SetQuizLocalActionTest.php | 35 +++++++++++++++++++ tests/Unit/SetQuizOnlineActionTest.php | 35 +++++++++++++++++++ 13 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 app/Actions/SetQuizLocalAction.php create mode 100644 app/Actions/SetQuizOnlineAction.php create mode 100644 database/migrations/2024_12_13_141755_add_is_local_to_quizzes_table.php create mode 100644 tests/Unit/SetQuizLocalActionTest.php create mode 100644 tests/Unit/SetQuizOnlineActionTest.php diff --git a/app/Actions/SetQuizLocalAction.php b/app/Actions/SetQuizLocalAction.php new file mode 100644 index 00000000..b6ccf648 --- /dev/null +++ b/app/Actions/SetQuizLocalAction.php @@ -0,0 +1,16 @@ +is_local = true; + $quiz->save(); + } +} diff --git a/app/Actions/SetQuizOnlineAction.php b/app/Actions/SetQuizOnlineAction.php new file mode 100644 index 00000000..2e01e08d --- /dev/null +++ b/app/Actions/SetQuizOnlineAction.php @@ -0,0 +1,16 @@ +is_local = false; + $quiz->save(); + } +} diff --git a/app/Http/Controllers/QuizController.php b/app/Http/Controllers/QuizController.php index d8575907..e7df1cb0 100644 --- a/app/Http/Controllers/QuizController.php +++ b/app/Http/Controllers/QuizController.php @@ -7,6 +7,8 @@ use App\Actions\AssignToQuizAction; use App\Actions\CreateUserQuizAction; use App\Actions\LockQuizAction; +use App\Actions\SetQuizLocalAction; +use App\Actions\SetQuizOnlineAction; use App\Actions\UnlockQuizAction; use App\Helpers\SortHelper; use App\Http\Requests\QuizRequest; @@ -109,6 +111,24 @@ public function assign(AssignToQuizAction $action, Request $request, Quiz $quiz) ->with("status", "Przypisano do testu"); } + public function makeLocal(SetQuizLocalAction $action, Quiz $quiz): RedirectResponse + { + $action->execute($quiz); + + return redirect() + ->back() + ->with("status", "Zmiana quizu na stacjonarny"); + } + + public function makeOnline(SetQuizOnlineAction $action, Quiz $quiz): RedirectResponse + { + $action->execute($quiz); + + return redirect() + ->back() + ->with("status", "Zmiana quizu na online"); + } + private function filterArchivedQuizzes(Builder $query, Request $request): Builder { $showArchived = $request->query("archived", "false") === "true"; diff --git a/app/Http/Resources/QuizResource.php b/app/Http/Resources/QuizResource.php index 765549fe..dc57fa6b 100644 --- a/app/Http/Resources/QuizResource.php +++ b/app/Http/Resources/QuizResource.php @@ -13,6 +13,7 @@ public function toArray($request): array return [ "id" => $this->id, "title" => $this->title, + "description" => $this->description, "createdAt" => $this->created_at, "updatedAt" => $this->updated_at, "scheduledAt" => $this->scheduled_at, @@ -20,9 +21,10 @@ public function toArray($request): array "state" => $this->state, "canBeLocked" => $this->canBeLocked, "canBeUnlocked" => $this->canBeUnlocked, - "questions" => QuestionResource::collection($this->questions), + "questions" => $this->is_local ? [] : QuestionResource::collection($this->questions), "isUserAssigned" => $this->isUserAssigned($request->user()), "isRankingPublished" => $this->isRankingPublished, + "isLocal" => $this->is_local, ]; } } diff --git a/app/Models/Quiz.php b/app/Models/Quiz.php index b32fcdfd..0ebf482a 100644 --- a/app/Models/Quiz.php +++ b/app/Models/Quiz.php @@ -21,12 +21,14 @@ * @property ?Carbon $scheduled_at * @property ?Carbon $ranking_published_at * @property ?Carbon $locked_at + * @property bool $is_local * @property ?int $duration * @property bool $isLocked * @property bool $isPublished * @property bool $canBeLocked * @property bool $canBeUnlocked * @property string $state + * @property ?string $description * @property bool $isRankingPublished * @property ?Carbon $closeAt * @property Collection $questions @@ -43,6 +45,7 @@ class Quiz extends Model "scheduled_at", "duration", "ranking_published_at", + "description", ]; protected $guarded = []; diff --git a/app/Policies/QuizPolicy.php b/app/Policies/QuizPolicy.php index 9f1479ee..1b5cc123 100644 --- a/app/Policies/QuizPolicy.php +++ b/app/Policies/QuizPolicy.php @@ -27,7 +27,7 @@ public function delete(User $user, Quiz $quiz): bool public function submit(User $user, Quiz $quiz): bool { - return $quiz->isLocked; + return $quiz->isLocked && !$quiz->is_local; } public function lock(User $user, Quiz $quiz): bool diff --git a/database/factories/QuizFactory.php b/database/factories/QuizFactory.php index 1313560b..13081b72 100644 --- a/database/factories/QuizFactory.php +++ b/database/factories/QuizFactory.php @@ -46,4 +46,12 @@ public function withRanking(): static "ranking_published_at" => Carbon::now(), ]); } + + public function local(): static + { + return $this->state(fn(array $attributes): array => [ + "is_local" => true, + "description" => fake()->text(100), + ]); + } } diff --git a/database/migrations/2024_08_08_123620_create_quizzes_table.php b/database/migrations/2024_08_08_123620_create_quizzes_table.php index 8b433ef3..28022d06 100644 --- a/database/migrations/2024_08_08_123620_create_quizzes_table.php +++ b/database/migrations/2024_08_08_123620_create_quizzes_table.php @@ -14,6 +14,7 @@ public function up(): void $table->timestamps(); $table->timestamp("locked_at")->nullable(); $table->string("title"); + $table->text("description")->nullable(); }); } diff --git a/database/migrations/2024_12_13_141755_add_is_local_to_quizzes_table.php b/database/migrations/2024_12_13_141755_add_is_local_to_quizzes_table.php new file mode 100644 index 00000000..b5fb11fe --- /dev/null +++ b/database/migrations/2024_12_13_141755_add_is_local_to_quizzes_table.php @@ -0,0 +1,23 @@ +boolean("is_local")->default(false); + }); + } + + public function down(): void + { + Schema::table("quizzes", function (Blueprint $table): void { + $table->dropColumn("is_local"); + }); + } +}; diff --git a/routes/web.php b/routes/web.php index ddf55108..38545359 100644 --- a/routes/web.php +++ b/routes/web.php @@ -44,6 +44,8 @@ Route::get("/quizzes", [QuizController::class, "index"])->name("admin.quizzes.index"); Route::get("/quizzes/{quiz}", [QuizController::class, "show"])->name("admin.quizzes.demo"); Route::post("/quizzes", [QuizController::class, "store"])->name("admin.quizzes.store"); + Route::post("/quizzes/{quiz}/local", [QuizController::class, "makeLocal"])->can("update,quiz,update")->name("admin.quizzes.make.local"); + Route::post("/quizzes/{quiz}/online", [QuizController::class, "makeOnline"])->can("update,quiz")->name("admin.quizzes.make.online"); Route::patch("/quizzes/{quiz}", [QuizController::class, "update"])->can("update,quiz")->name("admin.quizzes.update"); Route::delete("/quizzes/{quiz}", [QuizController::class, "destroy"])->can("delete,quiz")->name("admin.quizzes.destroy"); Route::post("/quizzes/{quiz}/clone", [QuizController::class, "clone"])->name("admin.quizzes.clone"); diff --git a/tests/Feature/QuizTest.php b/tests/Feature/QuizTest.php index 54ae7b0a..f30a6c16 100644 --- a/tests/Feature/QuizTest.php +++ b/tests/Feature/QuizTest.php @@ -432,7 +432,7 @@ public function testAdminCannotLockQuizThatHasQuestionsWithoutCorrectAnswer(): v $quiz->locked_at = null; $quiz->save(); - $this->actingAs($this->user) + $this->actingAs($this->admin) ->from("/") ->post("admin/quizzes/{$quiz->id}/lock") ->assertStatus(403); @@ -453,7 +453,7 @@ public function testAdminCannotUnlockQuizThatIsNotLocked(): void { $quiz = Quiz::factory()->create(); - $this->actingAs($this->user) + $this->actingAs($this->admin) ->from("/") ->post("/admin/quizzes/{$quiz->id}/unlock") ->assertStatus(403); @@ -465,12 +465,37 @@ public function testAdminCannotUnlockQuizWithPastScheduledTime(): void "scheduled_at" => Carbon::now()->subMinutes(60), ]); - $this->actingAs($this->user) + $this->actingAs($this->admin) ->from("/") ->post("/admin/quizzes/{$quiz->id}/unlock") ->assertStatus(403); } + public function testAdminCanSetUnlockedQuizToLocalAndOnline(): void + { + $quiz = Quiz::factory()->create(); + + $this->actingAs($this->admin) + ->post("/admin/quizzes/{$quiz->id}/local") + ->assertRedirect("/") + ->assertSessionHas(["status" => "Zmiana quizu na stacjonarny"]); + + $this->assertDatabaseHas("quizzes", [ + "is_local" => true, + "id" => $quiz->id, + ]); + + $this->actingAs($this->admin) + ->post("/admin/quizzes/{$quiz->id}/online") + ->assertRedirect("/") + ->assertSessionHas(["status" => "Zmiana quizu na online"]); + + $this->assertDatabaseHas("quizzes", [ + "is_local" => false, + "id" => $quiz->id, + ]); + } + public function testUserCanStartQuiz(): void { $quiz = Quiz::factory()->locked()->has(Question::factory()->locked())->create(); diff --git a/tests/Unit/SetQuizLocalActionTest.php b/tests/Unit/SetQuizLocalActionTest.php new file mode 100644 index 00000000..1e56a9ca --- /dev/null +++ b/tests/Unit/SetQuizLocalActionTest.php @@ -0,0 +1,35 @@ +action = new SetQuizLocalAction(); + } + + public function testActionSetQuizLocal(): void + { + $quiz = Quiz::factory()->create(); + $this->action->execute($quiz); + + $this->assertDatabaseHas("quizzes", [ + "id" => $quiz->id, + "is_local" => true, + ]); + } +} diff --git a/tests/Unit/SetQuizOnlineActionTest.php b/tests/Unit/SetQuizOnlineActionTest.php new file mode 100644 index 00000000..ccc52a77 --- /dev/null +++ b/tests/Unit/SetQuizOnlineActionTest.php @@ -0,0 +1,35 @@ +action = new SetQuizOnlineAction(); + } + + public function testActionSetQuizLocal(): void + { + $quiz = Quiz::factory()->local()->create(); + $this->action->execute($quiz); + + $this->assertDatabaseHas("quizzes", [ + "id" => $quiz->id, + "is_local" => false, + ]); + } +} From 68d43c8146d10444abb8c92aa3a842d9e92ab3cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Purga=C5=82?= Date: Sat, 14 Dec 2024 11:49:16 +0100 Subject: [PATCH 02/10] enable quiz lock policy --- app/Models/Quiz.php | 4 ++-- routes/web.php | 2 +- tests/Feature/QuizTest.php | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/Models/Quiz.php b/app/Models/Quiz.php index 0ebf482a..4c5e6c44 100644 --- a/app/Models/Quiz.php +++ b/app/Models/Quiz.php @@ -123,10 +123,10 @@ public function hasUserQuizzesFrom(User $user): bool public function isClosingToday(): bool { - return $this->isLocked && $this->closeAt->isFuture() && $this->closeAt->isToday(); + return $this->isLocked && $this->closeAt !== null && $this->closeAt->isFuture() && $this->closeAt->isToday(); } - protected function allQuestionsHaveCorrectAnswer(): bool + public function allQuestionsHaveCorrectAnswer(): bool { return $this->questions->every(fn(Question $question): bool => $question->hasCorrectAnswer); } diff --git a/routes/web.php b/routes/web.php index 38545359..eab23371 100644 --- a/routes/web.php +++ b/routes/web.php @@ -49,7 +49,7 @@ Route::patch("/quizzes/{quiz}", [QuizController::class, "update"])->can("update,quiz")->name("admin.quizzes.update"); Route::delete("/quizzes/{quiz}", [QuizController::class, "destroy"])->can("delete,quiz")->name("admin.quizzes.destroy"); Route::post("/quizzes/{quiz}/clone", [QuizController::class, "clone"])->name("admin.quizzes.clone"); - Route::post("/quizzes/{quiz}/lock", [QuizController::class, "lock"])->name("admin.quizzes.lock"); + Route::post("/quizzes/{quiz}/lock", [QuizController::class, "lock"])->can("lock,quiz")->name("admin.quizzes.lock"); Route::post("/quizzes/{quiz}/unlock", [QuizController::class, "unlock"])->can("unlock,quiz")->name("admin.quizzes.unlock"); Route::get("/quizzes/{quiz}/invite", [InviteController::class, "index"])->name("admin.quizzes.invite.index"); diff --git a/tests/Feature/QuizTest.php b/tests/Feature/QuizTest.php index f30a6c16..db681afc 100644 --- a/tests/Feature/QuizTest.php +++ b/tests/Feature/QuizTest.php @@ -380,7 +380,9 @@ public function testAdminCannotCopyQuizThatNotExisted(): void public function testAdminCanLockQuiz(): void { - $quiz = Quiz::factory()->has(Question::factory()->locked())->create(["scheduled_at" => Carbon::now(), "duration" => 30, "locked_at" => null]); + $quiz = Quiz::factory() + ->has(Question::factory()->locked()) + ->create(["scheduled_at" => Carbon::now()->addDay(), "duration" => 30, "locked_at" => null]); $this->actingAs($this->admin) ->from("/") @@ -429,6 +431,8 @@ public function testAdminCannotLockQuizThatHasQuestionsWithoutCorrectAnswer(): v $answer->question->save(); $quiz = $answer->question->quiz; + $quiz->duration = 30; + $quiz->scheduled_at = Carbon::now()->addDay(); $quiz->locked_at = null; $quiz->save(); From 33694a3a2eaa6a8ec862f0a9e59bccc2e30d341e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Purga=C5=82?= Date: Sat, 14 Dec 2024 12:38:07 +0100 Subject: [PATCH 03/10] allow local quizzes to be published without questions --- app/Models/Quiz.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/Models/Quiz.php b/app/Models/Quiz.php index 4c5e6c44..d1611f62 100644 --- a/app/Models/Quiz.php +++ b/app/Models/Quiz.php @@ -113,7 +113,12 @@ public function closeAt(): Attribute public function isReadyToBePublished(): bool { - return $this->scheduled_at !== null && $this->duration !== null && $this->questions->count() > 0 && $this->allQuestionsHaveCorrectAnswer(); + return $this->scheduled_at !== null && $this->duration !== null && $this->hasValidQuestionsForOnline(); + } + + protected function hasValidQuestionsForOnline(): bool + { + return $this->is_local || $this->questions->count() > 0 && $this->allQuestionsHaveCorrectAnswer(); } public function hasUserQuizzesFrom(User $user): bool @@ -126,7 +131,7 @@ public function isClosingToday(): bool return $this->isLocked && $this->closeAt !== null && $this->closeAt->isFuture() && $this->closeAt->isToday(); } - public function allQuestionsHaveCorrectAnswer(): bool + protected function allQuestionsHaveCorrectAnswer(): bool { return $this->questions->every(fn(Question $question): bool => $question->hasCorrectAnswer); } From b3cc970e83a7b97fac29eaac80d9d0e036f93171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Purga=C5=82?= Date: Sat, 14 Dec 2024 12:38:37 +0100 Subject: [PATCH 04/10] disable question editing for local quizzes --- app/Services/QuizUpdateService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/QuizUpdateService.php b/app/Services/QuizUpdateService.php index 5caa48b7..4de2ce96 100644 --- a/app/Services/QuizUpdateService.php +++ b/app/Services/QuizUpdateService.php @@ -13,7 +13,7 @@ public function update(Quiz $quiz, array $data): void { $quiz->fill($data); - if (array_key_exists("questions", $data)) { + if (array_key_exists("questions", $data) && !$quiz->is_local) { $questions = collect($data["questions"]); $quiz->questions()->whereNotIn("id", $questions->pluck("id")->whereNotNull())->delete(); From 797f7bfcf7c5982a9548d15c5b0531120494ee19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Purga=C5=82?= Date: Sat, 14 Dec 2024 12:38:43 +0100 Subject: [PATCH 05/10] update quiz type --- resources/js/Types/Quiz.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/js/Types/Quiz.d.ts b/resources/js/Types/Quiz.d.ts index 7197f616..11fbd438 100644 --- a/resources/js/Types/Quiz.d.ts +++ b/resources/js/Types/Quiz.d.ts @@ -7,6 +7,8 @@ interface Quiz { createdAt: string updatedAt: string state:'published' | 'locked' | 'unlocked' + isLocal: boolean + description?: string isUserAssigned: boolean isRankingPublished: boolean questions: Question[] From 7b0698ca2871d98bec83e35c490b8f53abffb8a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Purga=C5=82?= Date: Sat, 14 Dec 2024 13:02:01 +0100 Subject: [PATCH 06/10] update tests --- app/Http/Requests/QuizRequest.php | 1 + app/Http/Requests/UpdateQuizRequest.php | 13 +++--- app/Models/Quiz.php | 10 ++--- tests/Feature/QuizTest.php | 57 ++++++++++++++++++++++++- tests/Unit/QuizModelTest.php | 9 ++++ 5 files changed, 78 insertions(+), 12 deletions(-) diff --git a/app/Http/Requests/QuizRequest.php b/app/Http/Requests/QuizRequest.php index 716abe07..a17e24e9 100644 --- a/app/Http/Requests/QuizRequest.php +++ b/app/Http/Requests/QuizRequest.php @@ -30,6 +30,7 @@ public function rules(): array "title" => ["required", "string", "max:255"], "scheduled_at" => ["date", "after:now"], "duration" => ["numeric", "min:1", "max:2147483647"], + "description" => ["string", "nullable"], ]; } } diff --git a/app/Http/Requests/UpdateQuizRequest.php b/app/Http/Requests/UpdateQuizRequest.php index ac40815a..04c00e90 100644 --- a/app/Http/Requests/UpdateQuizRequest.php +++ b/app/Http/Requests/UpdateQuizRequest.php @@ -31,13 +31,14 @@ public function rules(): array "title" => ["required", "string", "max:255"], "scheduled_at" => ["date", "after:now"], "duration" => ["integer", "min:1", "max:2147483647"], + "description" => ["string", "nullable"], "questions" => ["array"], - "questions.*.id" => "integer|min:0", - "questions.*.text" => "required|string", - "questions.*.answers" => "array", - "questions.*.answers.*.id" => "integer|min:0", - "questions.*.answers.*.text" => "nullable|string", - "questions.*.answers.*.correct" => "boolean", + "questions.*.id" => ["integer", "min:0"], + "questions.*.text" => ["required", "string"], + "questions.*.answers" => ["array"], + "questions.*.answers.*.id" => ["integer", "min:0"], + "questions.*.answers.*.text" => ["nullable", "string"], + "questions.*.answers.*.correct" => ["boolean"], ]; } diff --git a/app/Models/Quiz.php b/app/Models/Quiz.php index d1611f62..7d8acc1b 100644 --- a/app/Models/Quiz.php +++ b/app/Models/Quiz.php @@ -116,11 +116,6 @@ public function isReadyToBePublished(): bool return $this->scheduled_at !== null && $this->duration !== null && $this->hasValidQuestionsForOnline(); } - protected function hasValidQuestionsForOnline(): bool - { - return $this->is_local || $this->questions->count() > 0 && $this->allQuestionsHaveCorrectAnswer(); - } - public function hasUserQuizzesFrom(User $user): bool { return $this->userQuizzes->where("user_id", $user->id)->isNotEmpty(); @@ -131,6 +126,11 @@ public function isClosingToday(): bool return $this->isLocked && $this->closeAt !== null && $this->closeAt->isFuture() && $this->closeAt->isToday(); } + protected function hasValidQuestionsForOnline(): bool + { + return $this->is_local || $this->questions->count() > 0 && $this->allQuestionsHaveCorrectAnswer(); + } + protected function allQuestionsHaveCorrectAnswer(): bool { return $this->questions->every(fn(Question $question): bool => $question->hasCorrectAnswer); diff --git a/tests/Feature/QuizTest.php b/tests/Feature/QuizTest.php index db681afc..d94c5919 100644 --- a/tests/Feature/QuizTest.php +++ b/tests/Feature/QuizTest.php @@ -60,12 +60,13 @@ public function testUserCanCreateQuiz(): void { $this->actingAs($this->admin) ->from("/") - ->post("/admin/quizzes", ["title" => "Example quiz", "scheduled_at" => "2024-02-10 11:40:00"]) + ->post("/admin/quizzes", ["title" => "Example quiz", "description" => "test", "scheduled_at" => "2024-02-10 11:40:00"]) ->assertRedirect("/"); $this->assertDatabaseHas("quizzes", [ "title" => "Example quiz", "scheduled_at" => "2024-02-10 11:40:00", + "description" => "test", ]); } @@ -159,6 +160,7 @@ public function testAdminCanEditQuiz(): void "title" => "Quiz Name", "scheduled_at" => "2024-08-28 15:00:00", "duration" => 120, + "description" => "test", "questions" => [ [ "id" => $question->id, @@ -175,6 +177,7 @@ public function testAdminCanEditQuiz(): void $this->assertDatabaseHas("quizzes", [ "id" => $quiz->id, "title" => "Quiz Name", + "description" => "test", "scheduled_at" => "2024-08-28 15:00:00", "duration" => 120, ]); @@ -241,6 +244,26 @@ public function testAdminCannotEditQuizWithQuestionThatHasMoreThanOneCorrectAnsw ); } + public function testAdminCannotEditLocalQuizQuestions(): void + { + $quiz = Quiz::factory()->local()->has(Question::factory()->locked())->create(["title" => "Old quiz", "scheduled_at" => "2024-02-10 11:40:00"]); + $this->assertDatabaseCount("questions", 2); + + $data = [ + "title" => "Quiz Name", + "scheduled_at" => "2024-08-28 15:00:00", + "duration" => 120, + "questions" => [], + ]; + + $this->actingAs($this->admin) + ->from("/") + ->patch("/admin/quizzes/{$quiz->id}", $data) + ->assertRedirect("/"); + + $this->assertDatabaseCount("questions", 2); + } + public function testAdminCannotEditQuizThatNotExisted(): void { $this->actingAs($this->admin) @@ -442,6 +465,26 @@ public function testAdminCannotLockQuizThatHasQuestionsWithoutCorrectAnswer(): v ->assertStatus(403); } + public function testAdminCanLockLocalQuizThatHasQuestionsWithoutCorrectAnswer(): void + { + $answer = Answer::factory()->locked()->create(); + $answer->question->correct_answer_id = null; + $answer->question->save(); + + $quiz = $answer->question->quiz; + $quiz->duration = 30; + $quiz->scheduled_at = Carbon::now()->addDay(); + $quiz->locked_at = null; + $quiz->is_local = true; + $quiz->save(); + + $this->actingAs($this->admin) + ->from("/") + ->post("/admin/quizzes/{$quiz->id}/lock") + ->assertRedirect("/") + ->assertSessionHas(["status" => "Test oznaczony jako gotowy do publikacji"]); + } + public function testAdminCanUnlockQuiz(): void { $quiz = Quiz::factory()->locked()->create(); @@ -528,6 +571,18 @@ public function testUserCannotStartAlreadyStartedQuiz(): void $this->assertDatabaseCount("user_quizzes", 1); } + public function testUserCannotStartLocalQuiz(): void + { + $quiz = Quiz::factory()->local()->locked()->create(); + + $this->actingAs($this->user) + ->from("/") + ->post("/quizzes/{$quiz->id}/start") + ->assertStatus(403); + + $this->assertDatabaseCount("user_quizzes", 0); + } + public function testUserCannotStartUnlockedQuiz(): void { $quiz = Quiz::factory()->create(); diff --git a/tests/Unit/QuizModelTest.php b/tests/Unit/QuizModelTest.php index d219a9f4..2a2aa7e3 100644 --- a/tests/Unit/QuizModelTest.php +++ b/tests/Unit/QuizModelTest.php @@ -128,6 +128,15 @@ public function testIsReadyToBePublished(): void $this->assertFalse($quiz4->isReadyToBePublished()); } + public function testLocalQuizIsReadyToBePublishedWithOrWithoutQuestions(): void + { + $quiz1 = Quiz::factory()->local()->has(Question::factory()->locked())->create(["scheduled_at" => Carbon::now()->addHour(), "duration" => 30]); + $quiz2 = Quiz::factory()->local()->create(["scheduled_at" => Carbon::now()->addHour(), "duration" => 30]); + + $this->assertTrue($quiz1->isReadyToBePublished()); + $this->assertTrue($quiz2->isReadyToBePublished()); + } + public function testHasUserQuizzesFrom(): void { $user1 = User::factory()->create(); From 0ebb586497de4e5c351cd4b36938da3c2a37fe89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Purga=C5=82?= Date: Sat, 14 Dec 2024 13:15:04 +0100 Subject: [PATCH 07/10] fix test --- tests/Unit/SortHelperTest.php | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/tests/Unit/SortHelperTest.php b/tests/Unit/SortHelperTest.php index 0d22a945..1cc2d268 100644 --- a/tests/Unit/SortHelperTest.php +++ b/tests/Unit/SortHelperTest.php @@ -5,34 +5,21 @@ namespace Tests\Unit; use App\Helpers\SortHelper; -use Illuminate\Container\Container; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Facades\Lang; use Mockery; -use Mockery\MockInterface; use Symfony\Component\HttpKernel\Exception\HttpException; use Tests\TestCase; +use Throwable; class SortHelperTest extends TestCase { - private Container $originalContainer; - - /** @var MockInterface */ - private MockInterface $container; - - protected function setUp(): void - { - $this->originalContainer = Container::getInstance(); - $this->container = Mockery::mock(Container::class); - Container::setInstance($this->container); - } - protected function tearDown(): void { + parent::tearDown(); Mockery::close(); - Container::setInstance($this->originalContainer); } public function testGetSortParametersReturnsDefaultValues(): void @@ -97,18 +84,21 @@ public function testSortAppliesOrderBy(): void public function testSortThrowsExceptionForUnsupportedField(): void { Lang::shouldReceive("get") - ->with("validation.custom.sorting.unsupported_field", ["attribute" => "invalid_field"]); - - $this->container->shouldReceive("abort")->once()->andThrow(HttpException::class, 400); - $this->expectException(HttpException::class); + ->with("validation.custom.sorting.unsupported_field", ["attribute" => "invalid_field"])->once()->andReturn("unsupported_field"); $request = Request::create("/?sort=invalid_field&order=asc", "GET"); $builderMock = Mockery::mock(Builder::class); $helper = new SortHelper($request); - $result = $helper->sort($builderMock, ["name", "email"], []); + $error = null; - $this->assertSame($builderMock, $result); + try { + $helper->sort($builderMock, ["name", "email"], []); + } catch (Throwable $e) { + $error = $e; + } + + $this->assertEquals(new HttpException(400, "unsupported_field"), $error); } public function testSearch(): void From 1f335de8fa10a095017150a087989c52a2bd3b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Purga=C5=82?= Date: Sat, 14 Dec 2024 14:25:19 +0100 Subject: [PATCH 08/10] update status texts --- app/Http/Controllers/QuizController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/QuizController.php b/app/Http/Controllers/QuizController.php index e7df1cb0..1e6ba771 100644 --- a/app/Http/Controllers/QuizController.php +++ b/app/Http/Controllers/QuizController.php @@ -117,7 +117,7 @@ public function makeLocal(SetQuizLocalAction $action, Quiz $quiz): RedirectRespo return redirect() ->back() - ->with("status", "Zmiana quizu na stacjonarny"); + ->with("status", "Tryb testu został zmieniony na stacjonarny."); } public function makeOnline(SetQuizOnlineAction $action, Quiz $quiz): RedirectResponse @@ -126,7 +126,7 @@ public function makeOnline(SetQuizOnlineAction $action, Quiz $quiz): RedirectRes return redirect() ->back() - ->with("status", "Zmiana quizu na online"); + ->with("status", "Tryb testu został zmieniony na zdalny."); } private function filterArchivedQuizzes(Builder $query, Request $request): Builder From ca8fb38737d3fe8b17f3fed4e95cbc043dd95ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Purga=C5=82?= Date: Sat, 14 Dec 2024 14:31:45 +0100 Subject: [PATCH 09/10] add test --- tests/Feature/QuizTest.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/Feature/QuizTest.php b/tests/Feature/QuizTest.php index d94c5919..ea7d11b7 100644 --- a/tests/Feature/QuizTest.php +++ b/tests/Feature/QuizTest.php @@ -56,6 +56,33 @@ public function testAdminCanViewQuizzes(): void ); } + public function testLocalQuizHasNoQuestionsVisible(): void + { + $quiz = Quiz::factory()->local()->create(); + Question::factory()->count(5)->create(["quiz_id" => $quiz->id]); + + $this->actingAs($this->admin) + ->get("/admin/quizzes") + ->assertInertia( + fn(Assert $page) => $page + ->component("Admin/Quizzes") + ->has("quizzes.data", 1) + ->has("quizzes.data.0.questions", 0), + ); + + $quiz->is_local = false; + $quiz->save(); + + $this->actingAs($this->admin) + ->get("/admin/quizzes") + ->assertInertia( + fn(Assert $page) => $page + ->component("Admin/Quizzes") + ->has("quizzes.data", 1) + ->has("quizzes.data.0.questions", 5), + ); + } + public function testUserCanCreateQuiz(): void { $this->actingAs($this->admin) From f511598cdb211b1e4d189530265d9a39883bb38c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Purga=C5=82?= Date: Sat, 14 Dec 2024 14:34:38 +0100 Subject: [PATCH 10/10] fix test --- tests/Feature/QuizTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Feature/QuizTest.php b/tests/Feature/QuizTest.php index ea7d11b7..075f1d1b 100644 --- a/tests/Feature/QuizTest.php +++ b/tests/Feature/QuizTest.php @@ -552,7 +552,7 @@ public function testAdminCanSetUnlockedQuizToLocalAndOnline(): void $this->actingAs($this->admin) ->post("/admin/quizzes/{$quiz->id}/local") ->assertRedirect("/") - ->assertSessionHas(["status" => "Zmiana quizu na stacjonarny"]); + ->assertSessionHas(["status" => "Tryb testu został zmieniony na stacjonarny."]); $this->assertDatabaseHas("quizzes", [ "is_local" => true, @@ -562,7 +562,7 @@ public function testAdminCanSetUnlockedQuizToLocalAndOnline(): void $this->actingAs($this->admin) ->post("/admin/quizzes/{$quiz->id}/online") ->assertRedirect("/") - ->assertSessionHas(["status" => "Zmiana quizu na online"]); + ->assertSessionHas(["status" => "Tryb testu został zmieniony na zdalny."]); $this->assertDatabaseHas("quizzes", [ "is_local" => false,