Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

- Private tests #129

Merged
merged 34 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
87c84cf
create mail for close quiz action
AmonDeShir Dec 10, 2024
3c574d7
add queue:listen to makefile
AmonDeShir Dec 10, 2024
20a334b
remove duplicate recipes
AmonDeShir Dec 10, 2024
c075f49
split quiz controller logic into action files
AmonDeShir Dec 10, 2024
5eeeeba
add tests for actions
AmonDeShir Dec 10, 2024
6aa38f6
test user model
AmonDeShir Dec 10, 2024
e10768f
remove randomness from test
AmonDeShir Dec 10, 2024
364a5a6
fix command name
AmonDeShir Dec 10, 2024
c854524
implement sending notification when quiz is closed
AmonDeShir Dec 10, 2024
aedc2a3
fix wasClosedManually method
AmonDeShir Dec 11, 2024
029c95e
prepare quizzes for testing
AmonDeShir Dec 11, 2024
bad26cd
fix filterArchivedQuizzes method
AmonDeShir Dec 11, 2024
3a18bfa
add archived quiz to seeder
AmonDeShir Dec 11, 2024
0cc5238
fix code style
AmonDeShir Dec 11, 2024
95ffc26
fix no answer warning
AmonDeShir Dec 11, 2024
4d2b34b
change number to int
AmonDeShir Dec 12, 2024
0c02cfe
add return type
AmonDeShir Dec 12, 2024
01a65da
move isClosingToday logic to sql
AmonDeShir Dec 12, 2024
a867f29
Merge branch 'main' into 37-tests-after-filling-test-the-user-should-…
AmonDeShir Dec 12, 2024
5849139
Merge branch 'main' into 37-tests-after-filling-test-the-user-should-…
AmonDeShir Dec 12, 2024
f00b874
revert to non-SQL version
AmonDeShir Dec 12, 2024
ebed49f
Merge branch '37-tests-after-filling-test-the-user-should-receive-ema…
AmonDeShir Dec 12, 2024
1b876fb
Merge branch 'main' of https://github.com/blumilksoftware/interns2024…
AmonDeShir Dec 12, 2024
6edda89
Revert "- Update all non-major dependencies with digest and pinDigest…
AmonDeShir Dec 12, 2024
ce51f7e
fix namespaces
AmonDeShir Dec 12, 2024
04796ad
fix code style
AmonDeShir Dec 12, 2024
93eac0e
replace nunomaduro/larastan with larastan/larastan
AmonDeShir Dec 12, 2024
f527b9a
wip
AmonDeShir Dec 13, 2024
39ba963
fix test
AmonDeShir Dec 14, 2024
8f74e7e
fix test
AmonDeShir Dec 14, 2024
2e42985
fix code style
AmonDeShir Dec 14, 2024
5f60a17
Merge branch 'main' of https://github.com/blumilksoftware/interns2024…
AmonDeShir Dec 14, 2024
5c6fede
fix quiz header
AmonDeShir Dec 14, 2024
4015cb9
update isPublic translation
AmonDeShir Dec 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/Http/Controllers/ContestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,12 @@ public function create(Request $request): RedirectResponse|Response
->with(["userQuestions.question.answers", "quiz"])
->get();

$assignedQuizzes = $user->assignedQuizzes->pluck("id");

$quizzes = Quiz::query()
->whereNotNull("locked_at")
->where("is_public", true)
->orWhereIn("id", $assignedQuizzes)
->with("questions.answers")
->get();

Expand Down
5 changes: 5 additions & 0 deletions app/Http/Requests/QuizRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ public function prepareForValidation(): void
if ($this->has("scheduledAt")) {
$this->merge(["scheduled_at" => $this->input("scheduledAt")]);
}

if ($this->has("isPublic")) {
$this->merge(["is_public" => $this->input("isPublic")]);
}
}

/**
Expand All @@ -31,6 +35,7 @@ public function rules(): array
"scheduled_at" => ["date", "after:now"],
"duration" => ["numeric", "min:1", "max:2147483647"],
"description" => ["string", "nullable"],
"is_public" => ["boolean"],
];
}
}
5 changes: 5 additions & 0 deletions app/Http/Requests/UpdateQuizRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ public function prepareForValidation(): void
if ($this->has("scheduledAt")) {
$this->merge(["scheduled_at" => $this->input("scheduledAt")]);
}

if ($this->has("isPublic")) {
$this->merge(["is_public" => $this->input("isPublic")]);
}
}

/**
Expand All @@ -30,6 +34,7 @@ public function rules(): array
return [
"title" => ["required", "string", "max:255"],
"scheduled_at" => ["date", "after:now"],
"is_public" => ["boolean"],
"duration" => ["integer", "min:1", "max:2147483647"],
"description" => ["string", "nullable"],
"questions" => ["array"],
Expand Down
1 change: 1 addition & 0 deletions app/Http/Resources/QuizResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public function toArray($request): array
"scheduledAt" => $this->scheduled_at,
"duration" => $this->duration,
"state" => $this->state,
"isPublic" => $this->is_public,
"canBeLocked" => $this->canBeLocked,
"canBeUnlocked" => $this->canBeUnlocked,
"questions" => $this->is_local ? [] : QuestionResource::collection($this->questions),
Expand Down
4 changes: 3 additions & 1 deletion app/Models/Quiz.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@
* @property ?Carbon $ranking_published_at
* @property ?Carbon $locked_at
* @property bool $is_local
* @property bool $is_public
* @property ?int $duration
* @property ?string $description
* @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<Question> $questions
Expand All @@ -46,6 +47,7 @@ class Quiz extends Model
"duration",
"ranking_published_at",
"description",
"is_public",
];
protected $guarded = [];

Expand Down
4 changes: 2 additions & 2 deletions app/Policies/QuizPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function delete(User $user, Quiz $quiz): bool

public function submit(User $user, Quiz $quiz): bool
{
return $quiz->isLocked && !$quiz->is_local;
return $quiz->isLocked && !$quiz->is_local && ($quiz->is_public || $quiz->assignedUsers()->where("user_id", $user->id)->exists());
}

public function lock(User $user, Quiz $quiz): bool
Expand All @@ -42,7 +42,7 @@ public function unlock(User $user, Quiz $quiz): bool

public function assign(User $user, Quiz $quiz): bool
{
return $quiz->isLocked && !$quiz->isPublished && !$quiz->hasUserQuizzesFrom($user);
return $quiz->isLocked && !$quiz->isPublished && $quiz->is_public && !$quiz->hasUserQuizzesFrom($user);
}

public function viewAdminRanking(User $user, Quiz $quiz): Response
Expand Down
8 changes: 8 additions & 0 deletions database/factories/QuizFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public function definition(): array
{
return [
"title" => fake()->name(),
"is_public" => true,
"duration" => fake()->numberBetween(60, 120),
];
}
Expand Down Expand Up @@ -47,6 +48,13 @@ public function withRanking(): static
]);
}

public function private(): static
{
return $this->state(fn(array $attributes): array => [
"is_public" => false,
]);
}

public function local(): static
{
return $this->state(fn(array $attributes): array => [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public function up(): void
$table->timestamp("locked_at")->nullable();
$table->string("title");
$table->text("description")->nullable();
$table->boolean("is_public")->default(false);
});
}

Expand Down
1 change: 1 addition & 0 deletions resources/js/Types/Quiz.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface Quiz {
description?: string
isUserAssigned: boolean
isRankingPublished: boolean
isPublic: boolean
questions: Question[]
}

Expand Down
49 changes: 41 additions & 8 deletions resources/js/components/QuizzesPanel/QuizHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,50 @@ const quiz = defineModel<Quiz>({ required: true })
</InputWrapper>
</div>

<CrudInput
v-model="quiz.duration"
:hide-content="!quiz.duration && !editing"
name="duration"
label="Czas trwania testu (min):"
:error="errors.duration"
:editing="editing"
class="min-h-6.5 duration-200"
:class="{ 'text-sm text-gray-600' : !editing }"
/>

<div
class="flex gap-1 duration-200 min-h-6.5"
class="flex items-center duration-200 min-h-6.5"
:class="{ 'text-sm text-gray-600' : !selected }"
>
<CrudInput
v-model="quiz.duration"
name="duration"
label="Czas trwania testu (min):"
:error="errors.duration"
:editing="editing"
/>
<p>Widoczność: </p>
<label
class="inline-flex items-center cursor-pointer border-b border-transparent bg-transparent focus:border-b-primary"
:class="{'pl-2 border-b-primary/30 hover:border-b-primary/60 text-primary text-center duration-200 transition-colors': editing}"
>
<input v-if="editing" type="checkbox" class="sr-only peer" :checked="quiz.isPublic" @click="(event: any) => quiz.isPublic = event.currentTarget.checked">
<div v-if="editing" class="
relative
w-7
h-4
bg-primary
peer-focus:outline-none
peer-focus:ring-2
rounded-full

after:bg-white
after:content-['']
after:start-0.5
after:absolute after:top-0.5
after:border
after:rounded-full
after:size-3
peer-checked:after:translate-x-full
rtl:peer-checked:after:-translate-x-full
after:transition-all
"
/>
<b class="select-none" :class="{'pl-2 text-primary': editing, 'pl-1': !editing}">{{ quiz.isPublic ? 'Dostępny dla wszystkich' : 'Dostępny tylko dla zaproszonych' }}</b>
</label>
</div>
</div>
</template>
80 changes: 78 additions & 2 deletions tests/Feature/QuizTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public function testLocalQuizHasNoQuestionsVisible(): void
);
}

public function testUserCanCreateQuiz(): void
public function testAdminCanCreateQuiz(): void
{
$this->actingAs($this->admin)
->from("/")
Expand Down Expand Up @@ -180,13 +180,14 @@ public function testAdminCannotCreateInvalidQuiz(): void

public function testAdminCanEditQuiz(): void
{
$quiz = Quiz::factory()->create(["title" => "Old quiz", "scheduled_at" => "2024-02-10 11:40:00"]);
$quiz = Quiz::factory()->create(["title" => "Old quiz", "is_public" => false, "scheduled_at" => "2024-02-10 11:40:00"]);
$question = Question::factory()->create(["quiz_id" => $quiz->id]);

$data = [
"title" => "Quiz Name",
"scheduled_at" => "2024-08-28 15:00:00",
"duration" => 120,
"is_public" => true,
"description" => "test",
"questions" => [
[
Expand All @@ -207,6 +208,7 @@ public function testAdminCanEditQuiz(): void
"description" => "test",
"scheduled_at" => "2024-08-28 15:00:00",
"duration" => 120,
"is_public" => true,
]);

$this->assertDatabaseHas("questions", [
Expand Down Expand Up @@ -586,6 +588,80 @@ public function testUserCanStartQuiz(): void
$response->assertRedirect("/quizzes/$userQuiz->id/");
}

public function testUninvitedUserCannotStartPrivateQuiz(): void
{
$quiz = Quiz::factory()->locked()->private()->create([
"scheduled_at" => Carbon::now()->subMinutes(60),
]);

$this->actingAs($this->user)
->from("/")
->post("/quizzes/{$quiz->id}/start")
->assertStatus(403);

$this->assertDatabaseCount("user_quizzes", 0);
}

public function testInvitedUserCanStartPrivateQuiz(): void
{
$quiz = Quiz::factory()->locked()->private()->create([
"scheduled_at" => Carbon::now()->subMinutes(60),
]);

$quiz->assignedUsers()->attach($this->user);

$response = $this->actingAs($this->user)
->from("/")
->post("/quizzes/{$quiz->id}/start");

$userQuiz = UserQuiz::query()->where([
"user_id" => $this->user->id,
"quiz_id" => $quiz->id,
])->firstOrFail();

$response->assertRedirect("/quizzes/$userQuiz->id/");
}

public function testInvitedUserCanViewPublicQuiz(): void
{
Quiz::factory()->locked()->create();

$this->actingAs($this->user)
->from("/")
->get("/dashboard")
->assertInertia(
fn(Assert $page) => $page->component("User/Dashboard")
->has("quizzes", 1),
);
}

public function testInvitedUserCanViewPrivateQuiz(): void
{
$quiz = Quiz::factory()->locked()->private()->create();
$quiz->assignedUsers()->attach($this->user);

$this->actingAs($this->user)
->from("/")
->get("/dashboard")
->assertInertia(
fn(Assert $page) => $page->component("User/Dashboard")
->has("quizzes", 1),
);
}

public function testUninvitedUserCannotViewPrivateQuiz(): void
{
Quiz::factory()->private()->create();

$this->actingAs($this->user)
->from("/")
->get("/dashboard")
->assertInertia(
fn(Assert $page) => $page->component("User/Dashboard")
->has("quizzes", 0),
);
}

public function testUserCannotStartAlreadyStartedQuiz(): void
{
$userQuiz = UserQuiz::factory()->create(["user_id" => $this->user->id]);
Expand Down
Loading