Compare commits

...

1 Commits

Author SHA1 Message Date
Marijn 0aa15415de test: expand coverage, dedupe data-driven tests, extract shared WebTestCase base
- Add #[CoversClass] to Base64Test and FilenameSanitizerTest
- Merge near-duplicate test methods into #[DataProvider] cases across
  ResetPasswordControllerTest, SettingsControllerTest, ClaimSeasonCommandTest,
  FilenameSanitizerTest, Base64Test, and SeasonRepositoryTest
- Add integration tests for previously untested controllers: public
  QuizController (quiz-taking flow), LoginController, RegistrationController,
  EliminationController, and PrepareEliminationController
- Add unit tests for Elimination and BankQuestion entity logic
- Extract shared WebTestCase setup/helpers (client, entityManager, login,
  entity lookups, CSRF token scraping) into AbstractControllerWebTestCase,
  removing duplicated boilerplate from all 14 WebTestCase files
2026-07-10 00:14:00 +02:00
21 changed files with 1000 additions and 415 deletions
+9 -10
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Tvdt\Tests\Command; namespace Tvdt\Tests\Command;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
@@ -48,21 +49,19 @@ final class ClaimSeasonCommandTest extends KernelTestCase
$this->assertCount(3, $season->owners); $this->assertCount(3, $season->owners);
} }
public function testInvalidEmailFails(): void /** @return iterable<string, array{string, string}> */
public static function invalidArgumentsProvider(): iterable
{ {
$this->commandTester->execute([ yield 'unknown email' => ['krtek', 'nonexisting@example.org'];
'season-code' => 'krtek', yield 'unknown season' => ['dhadk', 'test@example.org'];
'email' => 'nonexisting@example.org',
]);
$this->assertSame(Command::FAILURE, $this->commandTester->getStatusCode());
} }
public function testInvalidSeasonCodeFails(): void #[DataProvider('invalidArgumentsProvider')]
public function testInvalidArgumentFails(string $seasonCode, string $email): void
{ {
$this->commandTester->execute([ $this->commandTester->execute([
'season-code' => 'dhadk', 'season-code' => $seasonCode,
'email' => 'test@example.org', 'email' => $email,
]); ]);
$this->assertSame(Command::FAILURE, $this->commandTester->getStatusCode()); $this->assertSame(Command::FAILURE, $this->commandTester->getStatusCode());
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Tvdt\Tests\Controller;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\HttpFoundation\Request;
use Tvdt\Entity\Candidate;
use Tvdt\Entity\Quiz;
use Tvdt\Entity\Season;
use Tvdt\Entity\User;
abstract class AbstractControllerWebTestCase extends WebTestCase
{
protected KernelBrowser $client;
protected EntityManagerInterface $entityManager;
protected function setUp(): void
{
$this->client = self::createClient();
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
}
protected function getUserByEmail(string $email): User
{
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $email]);
$this->assertInstanceOf(User::class, $user);
return $user;
}
protected function loginAs(string $email): void
{
$this->client->loginUser($this->getUserByEmail($email));
}
protected function getQuizByName(string $name): Quiz
{
$quiz = $this->entityManager->getRepository(Quiz::class)->findOneBy(['name' => $name]);
$this->assertInstanceOf(Quiz::class, $quiz);
return $quiz;
}
protected function getCandidate(string $name): Candidate
{
$candidate = $this->entityManager->getRepository(Candidate::class)->findOneBy(['name' => $name]);
$this->assertInstanceOf(Candidate::class, $candidate);
return $candidate;
}
protected function getSeasonByCode(string $seasonCode): Season
{
$season = $this->entityManager->getRepository(Season::class)->findOneBy(['seasonCode' => $seasonCode]);
$this->assertInstanceOf(Season::class, $season);
return $season;
}
/** GETs $url and extracts the CSRF token from a form whose action contains $formActionContains. */
protected function getCsrfTokenFromPage(string $url, string $formActionContains, string $tokenFieldName = '_token'): string
{
$crawler = $this->client->request(Request::METHOD_GET, $url);
self::assertResponseIsSuccessful();
return $this->getCsrfTokenFromCrawler($crawler, $formActionContains, $tokenFieldName);
}
/** Extracts the CSRF token from a form on the page already loaded in the client. */
protected function getCsrfTokenFromCurrentPage(string $formActionContains, string $tokenFieldName = '_token'): string
{
return $this->getCsrfTokenFromCrawler($this->client->getCrawler(), $formActionContains, $tokenFieldName);
}
/** GETs $url and extracts the CSRF token input, regardless of which form it belongs to. */
protected function getTokenFromPage(string $url, string $tokenFieldName = '_token'): string
{
$crawler = $this->client->request(Request::METHOD_GET, $url);
self::assertResponseIsSuccessful();
$input = $crawler->filter(\sprintf('input[name="%s"]', $tokenFieldName));
$this->assertGreaterThan(0, $input->count(), \sprintf('No input named "%s" found on the page', $tokenFieldName));
return (string) $input->first()->attr('value');
}
private function getCsrfTokenFromCrawler(Crawler $crawler, string $formActionContains, string $tokenFieldName): string
{
$input = $crawler->filter(\sprintf('form[action*="%s"] input[name="%s"]', $formActionContains, $tokenFieldName));
$this->assertGreaterThan(0, $input->count(), \sprintf('No form found with action containing "%s"', $formActionContains));
return (string) $input->first()->attr('value');
}
}
@@ -4,53 +4,41 @@ declare(strict_types=1);
namespace Tvdt\Tests\Controller\Backoffice; namespace Tvdt\Tests\Controller\Backoffice;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Tvdt\Controller\Backoffice\BackofficeController; use Tvdt\Controller\Backoffice\BackofficeController;
use Tvdt\Entity\Quiz; use Tvdt\Tests\Controller\AbstractControllerWebTestCase;
use Tvdt\Entity\User;
#[CoversClass(BackofficeController::class)] #[CoversClass(BackofficeController::class)]
final class BackofficeControllerTest extends WebTestCase final class BackofficeControllerTest extends AbstractControllerWebTestCase
{ {
public function testExportQuizFilenameIsSanitized(): void public function testExportQuizFilenameIsSanitized(): void
{ {
$client = self::createClient(); $user = $this->getUserByEmail('user2@example.org');
$entityManager = self::getContainer()->get(EntityManagerInterface::class);
$user = $entityManager->getRepository(User::class)->findOneBy(['email' => 'user2@example.org']);
$this->assertInstanceOf(User::class, $user);
$user->isVerified = true; $user->isVerified = true;
$entityManager->flush();
$client->loginUser($user);
$quiz = $entityManager->getRepository(Quiz::class)->findOneBy(['name' => 'Quiz 1']); $this->entityManager->flush();
$this->assertInstanceOf(Quiz::class, $quiz); $this->client->loginUser($user);
$client->request(Request::METHOD_GET, \sprintf('/backoffice/quiz/%s/export', $quiz->id)); $quiz = $this->getQuizByName('Quiz 1');
$this->client->request(Request::METHOD_GET, \sprintf('/backoffice/quiz/%s/export', $quiz->id));
self::assertResponseIsSuccessful(); self::assertResponseIsSuccessful();
$disposition = (string) $client->getResponse()->headers->get('Content-Disposition'); $disposition = (string) $this->client->getResponse()->headers->get('Content-Disposition');
$this->assertStringContainsString('filename=Quiz-1.xlsx', $disposition); $this->assertStringContainsString('filename=Quiz-1.xlsx', $disposition);
$this->assertStringNotContainsString('Quiz 1.xlsx', $disposition); $this->assertStringNotContainsString('Quiz 1.xlsx', $disposition);
} }
public function testExportQuizRequiresVerifiedEmail(): void public function testExportQuizRequiresVerifiedEmail(): void
{ {
$client = self::createClient(); $user = $this->getUserByEmail('user2@example.org');
$entityManager = self::getContainer()->get(EntityManagerInterface::class);
$user = $entityManager->getRepository(User::class)->findOneBy(['email' => 'user2@example.org']);
$this->assertInstanceOf(User::class, $user);
$this->assertFalse($user->isVerified); $this->assertFalse($user->isVerified);
$client->loginUser($user); $this->client->loginUser($user);
$quiz = $entityManager->getRepository(Quiz::class)->findOneBy(['name' => 'Quiz 1']); $quiz = $this->getQuizByName('Quiz 1');
$this->assertInstanceOf(Quiz::class, $quiz);
$client->request(Request::METHOD_GET, \sprintf('/backoffice/quiz/%s/export', $quiz->id)); $this->client->request(Request::METHOD_GET, \sprintf('/backoffice/quiz/%s/export', $quiz->id));
self::assertResponseRedirects(\sprintf('/backoffice/season/%s', $quiz->season->seasonCode)); self::assertResponseRedirects(\sprintf('/backoffice/season/%s', $quiz->season->seasonCode));
} }
@@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
namespace Tvdt\Tests\Controller\Backoffice;
use PHPUnit\Framework\Attributes\CoversClass;
use Safe\DateTimeImmutable;
use Symfony\Component\HttpFoundation\Request;
use Tvdt\Controller\Backoffice\PrepareEliminationController;
use Tvdt\Entity\Answer;
use Tvdt\Entity\Elimination;
use Tvdt\Entity\GivenAnswer;
use Tvdt\Entity\Question;
use Tvdt\Entity\QuizCandidate;
use Tvdt\Tests\Controller\AbstractControllerWebTestCase;
#[CoversClass(PrepareEliminationController::class)]
final class PrepareEliminationControllerTest extends AbstractControllerWebTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->loginAs('krtek-admin@example.org');
}
public function testIndexCreatesEliminationAndRedirectsToView(): void
{
$quiz = $this->getQuizByName('Quiz 1');
$candidate = $this->getCandidate('Tom');
$quizCandidate = new QuizCandidate($quiz, $candidate);
$quizCandidate->started = new DateTimeImmutable();
$this->entityManager->persist($quizCandidate);
$firstQuestion = $quiz->questions->first();
$this->assertInstanceOf(Question::class, $firstQuestion);
$answer = $firstQuestion->answers->first();
$this->assertInstanceOf(Answer::class, $answer);
$this->entityManager->persist(new GivenAnswer($candidate, $quiz, $answer));
$this->entityManager->flush();
$token = $this->getCsrfTokenFromPage(\sprintf('/backoffice/season/krtek/quiz/%s/result', $quiz->id), '/elimination/prepare');
$this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/quiz/%s/elimination/prepare', $quiz->id), [
'_token' => $token,
]);
$response = $this->client->getResponse();
$this->assertTrue($response->isRedirect());
$this->assertStringContainsString('/backoffice/elimination/', (string) $response->headers->get('Location'));
$elimination = $this->entityManager->getRepository(Elimination::class)->findOneBy(['quiz' => $quiz]);
$this->assertInstanceOf(Elimination::class, $elimination);
$this->assertArrayHasKey('Tom', $elimination->data);
}
public function testViewEliminationPageLoads(): void
{
$quiz = $this->getQuizByName('Quiz 1');
$elimination = new Elimination($quiz);
$elimination->data = ['Tom' => Elimination::SCREEN_GREEN];
$this->entityManager->persist($elimination);
$this->entityManager->flush();
$this->client->request(Request::METHOD_GET, \sprintf('/backoffice/elimination/%s', $elimination->id));
self::assertResponseIsSuccessful();
self::assertSelectorExists('form');
}
public function testViewEliminationSavesUpdatedColours(): void
{
$quiz = $this->getQuizByName('Quiz 1');
$elimination = new Elimination($quiz);
$elimination->data = ['Tom' => Elimination::SCREEN_GREEN];
$this->entityManager->persist($elimination);
$this->entityManager->flush();
$token = $this->getTokenFromPage(\sprintf('/backoffice/elimination/%s', $elimination->id));
$this->client->request(Request::METHOD_POST, \sprintf('/backoffice/elimination/%s', $elimination->id), [
'_token' => $token,
'colour-tom' => Elimination::SCREEN_RED,
'start' => '0',
]);
self::assertResponseRedirects(\sprintf('/backoffice/elimination/%s', $elimination->id));
$this->entityManager->clear();
$updated = $this->entityManager->getRepository(Elimination::class)->find($elimination->id);
$this->assertInstanceOf(Elimination::class, $updated);
$this->assertSame(Elimination::SCREEN_RED, $updated->data['Tom']);
}
public function testViewEliminationWithStartRedirectsToPublicElimination(): void
{
$quiz = $this->getQuizByName('Quiz 1');
$elimination = new Elimination($quiz);
$elimination->data = ['Tom' => Elimination::SCREEN_GREEN];
$this->entityManager->persist($elimination);
$this->entityManager->flush();
$token = $this->getTokenFromPage(\sprintf('/backoffice/elimination/%s', $elimination->id));
$this->client->request(Request::METHOD_POST, \sprintf('/backoffice/elimination/%s', $elimination->id), [
'_token' => $token,
'start' => '1',
]);
self::assertResponseRedirects(\sprintf('/elimination/%s', $elimination->id));
}
}
@@ -4,39 +4,18 @@ declare(strict_types=1);
namespace Tvdt\Tests\Controller\Backoffice; namespace Tvdt\Tests\Controller\Backoffice;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Tvdt\Controller\Backoffice\QuestionBankController; use Tvdt\Controller\Backoffice\QuestionBankController;
use Tvdt\Entity\BankAnswer; use Tvdt\Entity\BankAnswer;
use Tvdt\Entity\BankQuestion; use Tvdt\Entity\BankQuestion;
use Tvdt\Entity\Question; use Tvdt\Entity\Question;
use Tvdt\Entity\QuestionLabel; use Tvdt\Entity\QuestionLabel;
use Tvdt\Entity\Quiz; use Tvdt\Tests\Controller\AbstractControllerWebTestCase;
use Tvdt\Entity\User;
#[CoversClass(QuestionBankController::class)] #[CoversClass(QuestionBankController::class)]
final class QuestionBankControllerTest extends WebTestCase final class QuestionBankControllerTest extends AbstractControllerWebTestCase
{ {
private KernelBrowser $client;
private EntityManagerInterface $entityManager;
protected function setUp(): void
{
$this->client = self::createClient();
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
}
private function loginAsOwner(): void
{
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'krtek-admin@example.org']);
$this->assertInstanceOf(User::class, $user);
$this->client->loginUser($user);
}
private function getBankQuestion(string $question): BankQuestion private function getBankQuestion(string $question): BankQuestion
{ {
$bankQuestion = $this->entityManager->getRepository(BankQuestion::class)->findOneBy(['question' => $question]); $bankQuestion = $this->entityManager->getRepository(BankQuestion::class)->findOneBy(['question' => $question]);
@@ -45,26 +24,9 @@ final class QuestionBankControllerTest extends WebTestCase
return $bankQuestion; return $bankQuestion;
} }
private function getQuizByName(string $name): Quiz
{
$quiz = $this->entityManager->getRepository(Quiz::class)->findOneBy(['name' => $name]);
$this->assertInstanceOf(Quiz::class, $quiz);
return $quiz;
}
private function getCsrfToken(string $formActionContains): string
{
$crawler = $this->client->getCrawler();
$input = $crawler->filter(\sprintf('form[action*="%s"] input[name="_token"]', $formActionContains));
$this->assertGreaterThan(0, $input->count(), \sprintf('No form found with action containing "%s"', $formActionContains));
return (string) $input->first()->attr('value');
}
public function testIndexListsBankQuestions(): void public function testIndexListsBankQuestions(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank'); $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank');
$this->assertResponseIsSuccessful(); $this->assertResponseIsSuccessful();
@@ -75,7 +37,7 @@ final class QuestionBankControllerTest extends WebTestCase
public function testIndexFiltersByLabel(): void public function testIndexFiltersByLabel(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$label = $this->entityManager->getRepository(QuestionLabel::class)->findOneBy(['name' => 'Locatie']); $label = $this->entityManager->getRepository(QuestionLabel::class)->findOneBy(['name' => 'Locatie']);
$this->assertInstanceOf(QuestionLabel::class, $label); $this->assertInstanceOf(QuestionLabel::class, $label);
@@ -89,9 +51,7 @@ final class QuestionBankControllerTest extends WebTestCase
public function testNonOwnerIsDenied(): void public function testNonOwnerIsDenied(): void
{ {
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'test@example.org']); $this->loginAs('test@example.org');
$this->assertInstanceOf(User::class, $user);
$this->client->loginUser($user);
$this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank'); $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank');
@@ -100,7 +60,7 @@ final class QuestionBankControllerTest extends WebTestCase
public function testCreateBankQuestion(): void public function testCreateBankQuestion(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$crawler = $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank/new'); $crawler = $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank/new');
$this->assertResponseIsSuccessful(); $this->assertResponseIsSuccessful();
@@ -129,7 +89,7 @@ final class QuestionBankControllerTest extends WebTestCase
public function testCreateAllowedWithoutCorrectAnswer(): void public function testCreateAllowedWithoutCorrectAnswer(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$crawler = $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank/new'); $crawler = $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank/new');
$token = (string) $crawler->filter('input[name="bank_question_form[_token]"]')->attr('value'); $token = (string) $crawler->filter('input[name="bank_question_form[_token]"]')->attr('value');
@@ -152,7 +112,7 @@ final class QuestionBankControllerTest extends WebTestCase
public function testEditBankQuestion(): void public function testEditBankQuestion(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$bankQuestion = $this->getBankQuestion('Wat at de Krtek als ontbijt?'); $bankQuestion = $this->getBankQuestion('Wat at de Krtek als ontbijt?');
$url = \sprintf('/backoffice/season/krtek/question-bank/%s/edit', $bankQuestion->id); $url = \sprintf('/backoffice/season/krtek/question-bank/%s/edit', $bankQuestion->id);
@@ -181,12 +141,12 @@ final class QuestionBankControllerTest extends WebTestCase
public function testDeleteUsedBankQuestionLeavesQuizIntact(): void public function testDeleteUsedBankQuestionLeavesQuizIntact(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$bankQuestion = $this->getBankQuestion('Waar sliep de Krtek?'); $bankQuestion = $this->getBankQuestion('Waar sliep de Krtek?');
$quiz2QuestionCount = $this->getQuizByName('Quiz 2')->questions->count(); $quiz2QuestionCount = $this->getQuizByName('Quiz 2')->questions->count();
$this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank'); $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank');
$token = $this->getCsrfToken(\sprintf('%s/delete', $bankQuestion->id)); $token = $this->getCsrfTokenFromCurrentPage(\sprintf('%s/delete', $bankQuestion->id));
$this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/question-bank/%s/delete', $bankQuestion->id), [ $this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/question-bank/%s/delete', $bankQuestion->id), [
'_token' => $token, '_token' => $token,
@@ -201,13 +161,13 @@ final class QuestionBankControllerTest extends WebTestCase
public function testAssignCopiesQuestionIntoQuiz(): void public function testAssignCopiesQuestionIntoQuiz(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$bankQuestion = $this->getBankQuestion('Wat at de Krtek als ontbijt?'); $bankQuestion = $this->getBankQuestion('Wat at de Krtek als ontbijt?');
$quiz = $this->getQuizByName('Quiz 2'); $quiz = $this->getQuizByName('Quiz 2');
$questionCount = $quiz->questions->count(); $questionCount = $quiz->questions->count();
$this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank'); $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank');
$token = $this->getCsrfToken(\sprintf('%s/assign', $bankQuestion->id)); $token = $this->getCsrfTokenFromCurrentPage(\sprintf('%s/assign', $bankQuestion->id));
$this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/question-bank/%s/assign', $bankQuestion->id), [ $this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/question-bank/%s/assign', $bankQuestion->id), [
'_token' => $token, '_token' => $token,
@@ -240,7 +200,7 @@ final class QuestionBankControllerTest extends WebTestCase
public function testAssignUsedNonReusableQuestionIsRefused(): void public function testAssignUsedNonReusableQuestionIsRefused(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$bankQuestion = $this->getBankQuestion('Waar sliep de Krtek?'); $bankQuestion = $this->getBankQuestion('Waar sliep de Krtek?');
$quiz = $this->getQuizByName('Quiz 2'); $quiz = $this->getQuizByName('Quiz 2');
$questionCount = $quiz->questions->count(); $questionCount = $quiz->questions->count();
@@ -248,7 +208,7 @@ final class QuestionBankControllerTest extends WebTestCase
$this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank'); $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank');
// The assign form is not rendered for used questions, so post with another form's token // The assign form is not rendered for used questions, so post with another form's token
$token = $this->getCsrfToken('/assign'); $token = $this->getCsrfTokenFromCurrentPage('/assign');
$this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/question-bank/%s/assign', $bankQuestion->id), [ $this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/question-bank/%s/assign', $bankQuestion->id), [
'_token' => $token, '_token' => $token,
'quiz' => (string) $quiz->id, 'quiz' => (string) $quiz->id,
@@ -262,13 +222,13 @@ final class QuestionBankControllerTest extends WebTestCase
public function testAssignSameReusableQuestionTwiceToSameQuizIsRefused(): void public function testAssignSameReusableQuestionTwiceToSameQuizIsRefused(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$bankQuestion = $this->getBankQuestion('Wie is de Krtek?'); $bankQuestion = $this->getBankQuestion('Wie is de Krtek?');
$quiz = $this->getQuizByName('Quiz 2'); $quiz = $this->getQuizByName('Quiz 2');
$questionCount = $quiz->questions->count(); $questionCount = $quiz->questions->count();
$this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank'); $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank');
$token = $this->getCsrfToken(\sprintf('%s/assign', $bankQuestion->id)); $token = $this->getCsrfTokenFromCurrentPage(\sprintf('%s/assign', $bankQuestion->id));
$url = \sprintf('/backoffice/season/krtek/question-bank/%s/assign', $bankQuestion->id); $url = \sprintf('/backoffice/season/krtek/question-bank/%s/assign', $bankQuestion->id);
$this->client->request(Request::METHOD_POST, $url, ['_token' => $token, 'quiz' => (string) $quiz->id]); $this->client->request(Request::METHOD_POST, $url, ['_token' => $token, 'quiz' => (string) $quiz->id]);
@@ -283,13 +243,13 @@ final class QuestionBankControllerTest extends WebTestCase
public function testAssignIntoFinalizedQuizIsDenied(): void public function testAssignIntoFinalizedQuizIsDenied(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$bankQuestion = $this->getBankQuestion('Wie is de Krtek?'); $bankQuestion = $this->getBankQuestion('Wie is de Krtek?');
$finalizedQuiz = $this->getQuizByName('Quiz 1'); $finalizedQuiz = $this->getQuizByName('Quiz 1');
$this->assertTrue($finalizedQuiz->isFinalized); $this->assertTrue($finalizedQuiz->isFinalized);
$this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank'); $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank');
$token = $this->getCsrfToken(\sprintf('%s/assign', $bankQuestion->id)); $token = $this->getCsrfTokenFromCurrentPage(\sprintf('%s/assign', $bankQuestion->id));
$this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/question-bank/%s/assign', $bankQuestion->id), [ $this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/question-bank/%s/assign', $bankQuestion->id), [
'_token' => $token, '_token' => $token,
@@ -301,7 +261,7 @@ final class QuestionBankControllerTest extends WebTestCase
public function testCreateBankQuestionPreservesAnswerOrdering(): void public function testCreateBankQuestionPreservesAnswerOrdering(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$crawler = $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank/new'); $crawler = $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank/new');
$this->assertResponseIsSuccessful(); $this->assertResponseIsSuccessful();
$token = (string) $crawler->filter('input[name="bank_question_form[_token]"]')->attr('value'); $token = (string) $crawler->filter('input[name="bank_question_form[_token]"]')->attr('value');
@@ -334,7 +294,7 @@ final class QuestionBankControllerTest extends WebTestCase
public function testEditBankQuestionPreservesAnswerOrdering(): void public function testEditBankQuestionPreservesAnswerOrdering(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$bankQuestion = $this->getBankQuestion('Wat at de Krtek als ontbijt?'); $bankQuestion = $this->getBankQuestion('Wat at de Krtek als ontbijt?');
// Fixture answers in insertion order (all have ordering=0): Brood (correct), Yoghurt, Niks // Fixture answers in insertion order (all have ordering=0): Brood (correct), Yoghurt, Niks
@@ -374,7 +334,7 @@ final class QuestionBankControllerTest extends WebTestCase
public function testAddAndDeleteLabel(): void public function testAddAndDeleteLabel(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$crawler = $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank'); $crawler = $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank');
$token = (string) $crawler->filter('form[action$="/question-bank/labels"] input[name="_token"]')->attr('value'); $token = (string) $crawler->filter('form[action$="/question-bank/labels"] input[name="_token"]')->attr('value');
@@ -389,7 +349,7 @@ final class QuestionBankControllerTest extends WebTestCase
$this->assertInstanceOf(QuestionLabel::class, $label); $this->assertInstanceOf(QuestionLabel::class, $label);
$this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank'); $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank');
$deleteToken = $this->getCsrfToken(\sprintf('labels/%s/delete', $label->slug)); $deleteToken = $this->getCsrfTokenFromCurrentPage(\sprintf('labels/%s/delete', $label->slug));
$this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/question-bank/labels/%s/delete', $label->slug), [ $this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/question-bank/labels/%s/delete', $label->slug), [
'_token' => $deleteToken, '_token' => $deleteToken,
@@ -4,11 +4,8 @@ declare(strict_types=1);
namespace Tvdt\Tests\Controller\Backoffice; namespace Tvdt\Tests\Controller\Backoffice;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use Safe\DateTimeImmutable; use Safe\DateTimeImmutable;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Tvdt\Controller\Backoffice\QuizController; use Tvdt\Controller\Backoffice\QuizController;
use Tvdt\Entity\Answer; use Tvdt\Entity\Answer;
@@ -17,51 +14,21 @@ use Tvdt\Entity\GivenAnswer;
use Tvdt\Entity\Question; use Tvdt\Entity\Question;
use Tvdt\Entity\Quiz; use Tvdt\Entity\Quiz;
use Tvdt\Entity\QuizCandidate; use Tvdt\Entity\QuizCandidate;
use Tvdt\Entity\Season; use Tvdt\Tests\Controller\AbstractControllerWebTestCase;
use Tvdt\Entity\User;
#[CoversClass(QuizController::class)] #[CoversClass(QuizController::class)]
final class QuizControllerTest extends WebTestCase final class QuizControllerTest extends AbstractControllerWebTestCase
{ {
private KernelBrowser $client;
private EntityManagerInterface $entityManager;
protected function setUp(): void protected function setUp(): void
{ {
$this->client = self::createClient(); parent::setUp();
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'krtek-admin@example.org']); $this->loginAs('krtek-admin@example.org');
$this->assertInstanceOf(User::class, $user);
$this->client->loginUser($user);
}
private function getQuizByName(string $name): Quiz
{
$quiz = $this->entityManager->getRepository(Quiz::class)->findOneBy(['name' => $name]);
$this->assertInstanceOf(Quiz::class, $quiz);
return $quiz;
}
private function getCandidate(string $name): Candidate
{
$candidate = $this->entityManager->getRepository(Candidate::class)->findOneBy(['name' => $name]);
$this->assertInstanceOf(Candidate::class, $candidate);
return $candidate;
} }
private function getCsrfTokenFromOverview(Quiz $quiz, string $formActionContains): string private function getCsrfTokenFromOverview(Quiz $quiz, string $formActionContains): string
{ {
$crawler = $this->client->request(Request::METHOD_GET, \sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz->id)); return $this->getCsrfTokenFromPage(\sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz->id), $formActionContains);
self::assertResponseIsSuccessful();
$input = $crawler->filter(\sprintf('form[action*="%s"] input[name="_token"]', $formActionContains));
$this->assertGreaterThan(0, $input->count(), \sprintf('No form found with action containing "%s"', $formActionContains));
return (string) $input->first()->attr('value');
} }
public function testIndexRedirectsToOverview(): void public function testIndexRedirectsToOverview(): void
@@ -296,9 +263,7 @@ final class QuizControllerTest extends WebTestCase
public function testNonOwnerIsDenied(): void public function testNonOwnerIsDenied(): void
{ {
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'test@example.org']); $this->loginAs('test@example.org');
$this->assertInstanceOf(User::class, $user);
$this->client->loginUser($user);
$quiz = $this->getQuizByName('Quiz 1'); $quiz = $this->getQuizByName('Quiz 1');
$this->client->request(Request::METHOD_GET, \sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz->id)); $this->client->request(Request::METHOD_GET, \sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz->id));
@@ -308,8 +273,7 @@ final class QuizControllerTest extends WebTestCase
public function testOverviewLoadsForEmptyQuiz(): void public function testOverviewLoadsForEmptyQuiz(): void
{ {
$season = $this->entityManager->getRepository(Season::class)->findOneBy(['seasonCode' => 'krtek']); $season = $this->getSeasonByCode('krtek');
$this->assertInstanceOf(Season::class, $season);
$emptyQuiz = new Quiz(); $emptyQuiz = new Quiz();
$emptyQuiz->name = 'Empty Quiz'; $emptyQuiz->name = 'Empty Quiz';
@@ -326,8 +290,7 @@ final class QuizControllerTest extends WebTestCase
public function testAnswerMappingRedirectsWithFlashWhenNoQuestions(): void public function testAnswerMappingRedirectsWithFlashWhenNoQuestions(): void
{ {
$season = $this->entityManager->getRepository(Season::class)->findOneBy(['seasonCode' => 'krtek']); $season = $this->getSeasonByCode('krtek');
$this->assertInstanceOf(Season::class, $season);
$emptyQuiz = new Quiz(); $emptyQuiz = new Quiz();
$emptyQuiz->name = 'Empty Quiz'; $emptyQuiz->name = 'Empty Quiz';
@@ -4,63 +4,29 @@ declare(strict_types=1);
namespace Tvdt\Tests\Controller\Backoffice; namespace Tvdt\Tests\Controller\Backoffice;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use Safe\DateTimeImmutable; use Safe\DateTimeImmutable;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Tvdt\Controller\Backoffice\QuizController; use Tvdt\Controller\Backoffice\QuizController;
use Tvdt\Entity\Answer; use Tvdt\Entity\Answer;
use Tvdt\Entity\Candidate;
use Tvdt\Entity\Question; use Tvdt\Entity\Question;
use Tvdt\Entity\Quiz; use Tvdt\Entity\Quiz;
use Tvdt\Entity\QuizCandidate; use Tvdt\Entity\QuizCandidate;
use Tvdt\Entity\Season; use Tvdt\Tests\Controller\AbstractControllerWebTestCase;
use Tvdt\Entity\User;
#[CoversClass(QuizController::class)] #[CoversClass(QuizController::class)]
final class QuizFinalizeTest extends WebTestCase final class QuizFinalizeTest extends AbstractControllerWebTestCase
{ {
private KernelBrowser $client;
private EntityManagerInterface $entityManager;
protected function setUp(): void protected function setUp(): void
{ {
$this->client = self::createClient(); parent::setUp();
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'krtek-admin@example.org']); $this->loginAs('krtek-admin@example.org');
$this->assertInstanceOf(User::class, $user);
$this->client->loginUser($user);
}
private function getQuizByName(string $name): Quiz
{
$quiz = $this->entityManager->getRepository(Quiz::class)->findOneBy(['name' => $name]);
$this->assertInstanceOf(Quiz::class, $quiz);
return $quiz;
}
private function getKrtekSeason(): Season
{
$season = $this->entityManager->getRepository(Season::class)->findOneBy(['seasonCode' => 'krtek']);
$this->assertInstanceOf(Season::class, $season);
return $season;
} }
private function getCsrfTokenFromOverview(Quiz $quiz, string $formActionContains): string private function getCsrfTokenFromOverview(Quiz $quiz, string $formActionContains): string
{ {
$crawler = $this->client->request(Request::METHOD_GET, \sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz->id)); return $this->getCsrfTokenFromPage(\sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz->id), $formActionContains);
$this->assertResponseIsSuccessful();
$input = $crawler->filter(\sprintf('form[action*="%s"] input[name="_token"]', $formActionContains));
$this->assertGreaterThan(0, $input->count(), \sprintf('No form found with action containing "%s"', $formActionContains));
return (string) $input->first()->attr('value');
} }
public function testFinalizeSetsFinalizedAt(): void public function testFinalizeSetsFinalizedAt(): void
@@ -79,7 +45,7 @@ final class QuizFinalizeTest extends WebTestCase
public function testFinalizeRefusedWhenQuizHasErrors(): void public function testFinalizeRefusedWhenQuizHasErrors(): void
{ {
$season = $this->getKrtekSeason(); $season = $this->getSeasonByCode('krtek');
$invalidQuiz = new Quiz(); $invalidQuiz = new Quiz();
$invalidQuiz->name = 'Invalid Quiz'; $invalidQuiz->name = 'Invalid Quiz';
@@ -116,7 +82,7 @@ final class QuizFinalizeTest extends WebTestCase
$this->assertResponseRedirects(); $this->assertResponseRedirects();
$this->entityManager->clear(); $this->entityManager->clear();
$season = $this->getKrtekSeason(); $season = $this->getSeasonByCode('krtek');
$this->assertInstanceOf(Quiz::class, $season->activeQuiz); $this->assertInstanceOf(Quiz::class, $season->activeQuiz);
$this->assertSame('Quiz 1', $season->activeQuiz->name); $this->assertSame('Quiz 1', $season->activeQuiz->name);
} }
@@ -134,7 +100,7 @@ final class QuizFinalizeTest extends WebTestCase
$this->assertResponseRedirects(); $this->assertResponseRedirects();
$this->entityManager->clear(); $this->entityManager->clear();
$season = $this->getKrtekSeason(); $season = $this->getSeasonByCode('krtek');
$this->assertInstanceOf(Quiz::class, $season->activeQuiz); $this->assertInstanceOf(Quiz::class, $season->activeQuiz);
$this->assertSame('Quiz 2', $season->activeQuiz->name); $this->assertSame('Quiz 2', $season->activeQuiz->name);
} }
@@ -183,8 +149,7 @@ final class QuizFinalizeTest extends WebTestCase
// Scrape the token before a candidate starts, since the button disappears afterwards // Scrape the token before a candidate starts, since the button disappears afterwards
$token = $this->getCsrfTokenFromOverview($quiz, '/unfinalize'); $token = $this->getCsrfTokenFromOverview($quiz, '/unfinalize');
$candidate = $this->entityManager->getRepository(Candidate::class)->findOneBy(['name' => 'Tom']); $candidate = $this->getCandidate('Tom');
$this->assertInstanceOf(Candidate::class, $candidate);
$quizCandidate = new QuizCandidate($quiz, $candidate); $quizCandidate = new QuizCandidate($quiz, $candidate);
$quizCandidate->started = new DateTimeImmutable(); $quizCandidate->started = new DateTimeImmutable();
@@ -229,6 +194,6 @@ final class QuizFinalizeTest extends WebTestCase
self::assertResponseRedirects(\sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz2->id)); self::assertResponseRedirects(\sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz2->id));
$this->entityManager->clear(); $this->entityManager->clear();
$this->assertNotInstanceOf(Quiz::class, $this->getKrtekSeason()->activeQuiz); $this->assertNotInstanceOf(Quiz::class, $this->getSeasonByCode('krtek')->activeQuiz);
} }
} }
@@ -4,47 +4,18 @@ declare(strict_types=1);
namespace Tvdt\Tests\Controller\Backoffice; namespace Tvdt\Tests\Controller\Backoffice;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Tvdt\Controller\Backoffice\QuizQuestionController; use Tvdt\Controller\Backoffice\QuizQuestionController;
use Tvdt\Entity\Question; use Tvdt\Entity\Question;
use Tvdt\Entity\Quiz; use Tvdt\Tests\Controller\AbstractControllerWebTestCase;
use Tvdt\Entity\User;
#[CoversClass(QuizQuestionController::class)] #[CoversClass(QuizQuestionController::class)]
final class QuizQuestionControllerTest extends WebTestCase final class QuizQuestionControllerTest extends AbstractControllerWebTestCase
{ {
private KernelBrowser $client;
private EntityManagerInterface $entityManager;
protected function setUp(): void
{
$this->client = self::createClient();
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
}
private function loginAsOwner(): void
{
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'krtek-admin@example.org']);
$this->assertInstanceOf(User::class, $user);
$this->client->loginUser($user);
}
private function getQuizByName(string $name): Quiz
{
$quiz = $this->entityManager->getRepository(Quiz::class)->findOneBy(['name' => $name]);
$this->assertInstanceOf(Quiz::class, $quiz);
return $quiz;
}
public function testEditPreservesAnswerOrdering(): void public function testEditPreservesAnswerOrdering(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$quiz = $this->getQuizByName('Quiz 2'); $quiz = $this->getQuizByName('Quiz 2');
$question = null; $question = null;
@@ -111,7 +82,7 @@ final class QuizQuestionControllerTest extends WebTestCase
public function testReorderQuestionsWithinQuiz(): void public function testReorderQuestionsWithinQuiz(): void
{ {
$this->loginAsOwner(); $this->loginAs('krtek-admin@example.org');
$quiz = $this->getQuizByName('Quiz 2'); $quiz = $this->getQuizByName('Quiz 2');
$originalQuestions = $quiz->questions->toArray(); $originalQuestions = $quiz->questions->toArray();
@@ -4,40 +4,27 @@ declare(strict_types=1);
namespace Tvdt\Tests\Controller\Backoffice; namespace Tvdt\Tests\Controller\Backoffice;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Tvdt\Controller\Backoffice\SeasonController; use Tvdt\Controller\Backoffice\SeasonController;
use Tvdt\Entity\Candidate; use Tvdt\Entity\Candidate;
use Tvdt\Entity\Season; use Tvdt\Entity\Season;
use Tvdt\Entity\User; use Tvdt\Tests\Controller\AbstractControllerWebTestCase;
#[CoversClass(SeasonController::class)] #[CoversClass(SeasonController::class)]
final class SeasonControllerTest extends WebTestCase final class SeasonControllerTest extends AbstractControllerWebTestCase
{ {
private KernelBrowser $client;
private EntityManagerInterface $entityManager;
protected function setUp(): void protected function setUp(): void
{ {
$this->client = self::createClient(); parent::setUp();
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'krtek-admin@example.org']); $this->loginAs('krtek-admin@example.org');
$this->assertInstanceOf(User::class, $user);
$this->client->loginUser($user);
} }
public function testRegenerateSeasonCodeChangesTheCode(): void public function testRegenerateSeasonCodeChangesTheCode(): void
{ {
$oldCode = 'krtek'; $oldCode = 'krtek';
$crawler = $this->client->request(Request::METHOD_GET, \sprintf('/backoffice/season/%s/settings', $oldCode)); $token = $this->getCsrfTokenFromPage(\sprintf('/backoffice/season/%s/settings', $oldCode), '/regenerate-code');
self::assertResponseIsSuccessful();
$token = (string) $crawler->filter('form[action*="/regenerate-code"] input[name="_token"]')->first()->attr('value');
$this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/%s/settings/regenerate-code', $oldCode), [ $this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/%s/settings/regenerate-code', $oldCode), [
'_token' => $token, '_token' => $token,
@@ -54,13 +41,9 @@ final class SeasonControllerTest extends WebTestCase
public function testRegenerateSeasonCodeIsDeniedForNonOwner(): void public function testRegenerateSeasonCodeIsDeniedForNonOwner(): void
{ {
$crawler = $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/settings'); $token = $this->getCsrfTokenFromPage('/backoffice/season/krtek/settings', '/regenerate-code');
self::assertResponseIsSuccessful();
$token = (string) $crawler->filter('form[action*="/regenerate-code"] input[name="_token"]')->first()->attr('value');
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'test@example.org']); $this->loginAs('test@example.org');
$this->assertInstanceOf(User::class, $user);
$this->client->loginUser($user);
$this->client->request(Request::METHOD_POST, '/backoffice/season/krtek/settings/regenerate-code', [ $this->client->request(Request::METHOD_POST, '/backoffice/season/krtek/settings/regenerate-code', [
'_token' => $token, '_token' => $token,
@@ -69,29 +52,10 @@ final class SeasonControllerTest extends WebTestCase
self::assertResponseStatusCodeSame(403); self::assertResponseStatusCodeSame(403);
} }
private function getCandidate(string $name): Candidate
{
$candidate = $this->entityManager->getRepository(Candidate::class)->findOneBy(['name' => $name]);
$this->assertInstanceOf(Candidate::class, $candidate);
return $candidate;
}
private function getCsrfTokenFromCandidatesTab(string $formActionContains): string
{
$crawler = $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/candidates');
self::assertResponseIsSuccessful();
$input = $crawler->filter(\sprintf('form[action*="%s"] input[name="_token"]', $formActionContains));
$this->assertGreaterThan(0, $input->count(), \sprintf('No form found with action containing "%s"', $formActionContains));
return (string) $input->first()->attr('value');
}
public function testRenameCandidate(): void public function testRenameCandidate(): void
{ {
$candidate = $this->getCandidate('Tom'); $candidate = $this->getCandidate('Tom');
$token = $this->getCsrfTokenFromCandidatesTab(\sprintf('/candidate/%s/rename', $candidate->id)); $token = $this->getCsrfTokenFromPage('/backoffice/season/krtek/candidates', \sprintf('/candidate/%s/rename', $candidate->id));
$this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/candidate/%s/rename', $candidate->id), [ $this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/candidate/%s/rename', $candidate->id), [
'_token' => $token, '_token' => $token,
@@ -109,7 +73,7 @@ final class SeasonControllerTest extends WebTestCase
public function testRenameCandidateToExistingNameShowsError(): void public function testRenameCandidateToExistingNameShowsError(): void
{ {
$candidate = $this->getCandidate('Tom'); $candidate = $this->getCandidate('Tom');
$token = $this->getCsrfTokenFromCandidatesTab(\sprintf('/candidate/%s/rename', $candidate->id)); $token = $this->getCsrfTokenFromPage('/backoffice/season/krtek/candidates', \sprintf('/candidate/%s/rename', $candidate->id));
$this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/candidate/%s/rename', $candidate->id), [ $this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/candidate/%s/rename', $candidate->id), [
'_token' => $token, '_token' => $token,
@@ -128,7 +92,7 @@ final class SeasonControllerTest extends WebTestCase
{ {
$candidate = $this->getCandidate('Tom'); $candidate = $this->getCandidate('Tom');
$candidateId = $candidate->id; $candidateId = $candidate->id;
$token = $this->getCsrfTokenFromCandidatesTab(\sprintf('/candidate/%s/delete', $candidate->id)); $token = $this->getCsrfTokenFromPage('/backoffice/season/krtek/candidates', \sprintf('/candidate/%s/delete', $candidate->id));
$this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/candidate/%s/delete', $candidate->id), [ $this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/candidate/%s/delete', $candidate->id), [
'_token' => $token, '_token' => $token,
@@ -143,11 +107,9 @@ final class SeasonControllerTest extends WebTestCase
public function testRenameCandidateIsDeniedForNonOwner(): void public function testRenameCandidateIsDeniedForNonOwner(): void
{ {
$candidate = $this->getCandidate('Tom'); $candidate = $this->getCandidate('Tom');
$token = $this->getCsrfTokenFromCandidatesTab(\sprintf('/candidate/%s/rename', $candidate->id)); $token = $this->getCsrfTokenFromPage('/backoffice/season/krtek/candidates', \sprintf('/candidate/%s/rename', $candidate->id));
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'test@example.org']); $this->loginAs('test@example.org');
$this->assertInstanceOf(User::class, $user);
$this->client->loginUser($user);
$this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/candidate/%s/rename', $candidate->id), [ $this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/candidate/%s/rename', $candidate->id), [
'_token' => $token, '_token' => $token,
@@ -4,11 +4,9 @@ declare(strict_types=1);
namespace Tvdt\Tests\Controller\Backoffice; namespace Tvdt\Tests\Controller\Backoffice;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use Safe\DateTimeImmutable; use Safe\DateTimeImmutable;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Tvdt\Controller\Backoffice\SettingsController; use Tvdt\Controller\Backoffice\SettingsController;
@@ -17,43 +15,21 @@ use Tvdt\Entity\Quiz;
use Tvdt\Entity\ResetPasswordRequest; use Tvdt\Entity\ResetPasswordRequest;
use Tvdt\Entity\Season; use Tvdt\Entity\Season;
use Tvdt\Entity\User; use Tvdt\Entity\User;
use Tvdt\Tests\Controller\AbstractControllerWebTestCase;
#[CoversClass(SettingsController::class)] #[CoversClass(SettingsController::class)]
final class SettingsControllerTest extends WebTestCase final class SettingsControllerTest extends AbstractControllerWebTestCase
{ {
private KernelBrowser $client;
private EntityManagerInterface $entityManager;
protected function setUp(): void protected function setUp(): void
{ {
$this->client = self::createClient(); parent::setUp();
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
$this->loginAs('test@example.org'); $this->loginAs('test@example.org');
} }
private function loginAs(string $email): void
{
$user = $this->getUserByEmail($email);
$this->assertInstanceOf(User::class, $user);
$this->client->loginUser($user);
}
private function getUserByEmail(string $email): ?User
{
return $this->entityManager->getRepository(User::class)->findOneBy(['email' => $email]);
}
private function getCsrfTokenFromSettings(string $formActionContains): string private function getCsrfTokenFromSettings(string $formActionContains): string
{ {
$crawler = $this->client->request(Request::METHOD_GET, '/backoffice/settings'); return $this->getCsrfTokenFromPage('/backoffice/settings', $formActionContains);
self::assertResponseIsSuccessful();
$input = $crawler->filter(\sprintf('form[action*="%s"] input[name="_token"]', $formActionContains));
$this->assertGreaterThan(0, $input->count(), \sprintf('No form found with action containing "%s"', $formActionContains));
return (string) $input->first()->attr('value');
} }
public function testSettingsPageLoadsAndNavContainsSettingsLink(): void public function testSettingsPageLoadsAndNavContainsSettingsLink(): void
@@ -98,7 +74,6 @@ final class SettingsControllerTest extends WebTestCase
$this->entityManager->clear(); $this->entityManager->clear();
$user = $this->getUserByEmail('test@example.org'); $user = $this->getUserByEmail('test@example.org');
$this->assertInstanceOf(User::class, $user);
$hasher = self::getContainer()->get(UserPasswordHasherInterface::class); $hasher = self::getContainer()->get(UserPasswordHasherInterface::class);
$this->assertTrue($hasher->isPasswordValid($user, 'NewPass123!')); $this->assertTrue($hasher->isPasswordValid($user, 'NewPass123!'));
@@ -107,32 +82,21 @@ final class SettingsControllerTest extends WebTestCase
self::assertResponseIsSuccessful(); self::assertResponseIsSuccessful();
} }
public function testChangePasswordWithWrongCurrentPasswordIsRejected(): void /** @return iterable<string, array{string, string, string}> */
public static function invalidPasswordChangeProvider(): iterable
{ {
$this->client->request(Request::METHOD_GET, '/backoffice/settings'); yield 'wrong current password' => ['wrong-password', 'NewPass123!', 'NewPass123!'];
$form = $this->client->getCrawler()->filter('form[action*="/backoffice/settings/password"]')->form([ yield 'mismatched repeat' => [TestFixtures::PASSWORD, 'NewPass123!', 'SomethingElse!'];
'change_user_password_form[currentPassword]' => 'wrong-password',
'change_user_password_form[plainPassword][first]' => 'NewPass123!',
'change_user_password_form[plainPassword][second]' => 'NewPass123!',
]);
$this->client->submit($form);
self::assertResponseStatusCodeSame(422);
$this->entityManager->clear();
$user = $this->getUserByEmail('test@example.org');
$this->assertInstanceOf(User::class, $user);
$hasher = self::getContainer()->get(UserPasswordHasherInterface::class);
$this->assertTrue($hasher->isPasswordValid($user, TestFixtures::PASSWORD));
} }
public function testChangePasswordWithMismatchedRepeatIsRejected(): void #[DataProvider('invalidPasswordChangeProvider')]
public function testChangePasswordIsRejected(string $currentPassword, string $first, string $second): void
{ {
$this->client->request(Request::METHOD_GET, '/backoffice/settings'); $this->client->request(Request::METHOD_GET, '/backoffice/settings');
$form = $this->client->getCrawler()->filter('form[action*="/backoffice/settings/password"]')->form([ $form = $this->client->getCrawler()->filter('form[action*="/backoffice/settings/password"]')->form([
'change_user_password_form[currentPassword]' => TestFixtures::PASSWORD, 'change_user_password_form[currentPassword]' => $currentPassword,
'change_user_password_form[plainPassword][first]' => 'NewPass123!', 'change_user_password_form[plainPassword][first]' => $first,
'change_user_password_form[plainPassword][second]' => 'SomethingElse!', 'change_user_password_form[plainPassword][second]' => $second,
]); ]);
$this->client->submit($form); $this->client->submit($form);
@@ -140,7 +104,6 @@ final class SettingsControllerTest extends WebTestCase
$this->entityManager->clear(); $this->entityManager->clear();
$user = $this->getUserByEmail('test@example.org'); $user = $this->getUserByEmail('test@example.org');
$this->assertInstanceOf(User::class, $user);
$hasher = self::getContainer()->get(UserPasswordHasherInterface::class); $hasher = self::getContainer()->get(UserPasswordHasherInterface::class);
$this->assertTrue($hasher->isPasswordValid($user, TestFixtures::PASSWORD)); $this->assertTrue($hasher->isPasswordValid($user, TestFixtures::PASSWORD));
} }
@@ -157,9 +120,8 @@ final class SettingsControllerTest extends WebTestCase
self::assertEmailCount(1); self::assertEmailCount(1);
$this->entityManager->clear(); $this->entityManager->clear();
$this->assertNotInstanceOf(User::class, $this->getUserByEmail('test@example.org')); $this->assertNotInstanceOf(User::class, $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'test@example.org']));
$user = $this->getUserByEmail('new-address@example.org'); $user = $this->getUserByEmail('new-address@example.org');
$this->assertInstanceOf(User::class, $user);
$this->assertFalse($user->isVerified); $this->assertFalse($user->isVerified);
// User stays logged in // User stays logged in
@@ -179,7 +141,7 @@ final class SettingsControllerTest extends WebTestCase
self::assertEmailCount(0); self::assertEmailCount(0);
$this->entityManager->clear(); $this->entityManager->clear();
$this->assertInstanceOf(User::class, $this->getUserByEmail('test@example.org')); $this->getUserByEmail('test@example.org');
} }
public function testResendConfirmationEmailSendsEmail(): void public function testResendConfirmationEmailSendsEmail(): void
@@ -200,8 +162,8 @@ final class SettingsControllerTest extends WebTestCase
$token = $this->getCsrfTokenFromSettings('/backoffice/settings/resend-confirmation'); $token = $this->getCsrfTokenFromSettings('/backoffice/settings/resend-confirmation');
$user = $this->getUserByEmail('test@example.org'); $user = $this->getUserByEmail('test@example.org');
$this->assertInstanceOf(User::class, $user);
$user->isVerified = true; $user->isVerified = true;
$this->entityManager->flush(); $this->entityManager->flush();
$crawler = $this->client->request(Request::METHOD_GET, '/backoffice/settings'); $crawler = $this->client->request(Request::METHOD_GET, '/backoffice/settings');
@@ -242,7 +204,6 @@ final class SettingsControllerTest extends WebTestCase
public function testChangePasswordInvalidatesResetPasswordRequests(): void public function testChangePasswordInvalidatesResetPasswordRequests(): void
{ {
$user = $this->getUserByEmail('test@example.org'); $user = $this->getUserByEmail('test@example.org');
$this->assertInstanceOf(User::class, $user);
$this->createResetPasswordRequest($user); $this->createResetPasswordRequest($user);
$this->client->request(Request::METHOD_GET, '/backoffice/settings'); $this->client->request(Request::METHOD_GET, '/backoffice/settings');
@@ -257,14 +218,12 @@ final class SettingsControllerTest extends WebTestCase
$this->entityManager->clear(); $this->entityManager->clear();
$user = $this->getUserByEmail('test@example.org'); $user = $this->getUserByEmail('test@example.org');
$this->assertInstanceOf(User::class, $user);
$this->assertSame(0, $this->entityManager->getRepository(ResetPasswordRequest::class)->count(['user' => $user])); $this->assertSame(0, $this->entityManager->getRepository(ResetPasswordRequest::class)->count(['user' => $user]));
} }
public function testChangeEmailInvalidatesResetPasswordRequests(): void public function testChangeEmailInvalidatesResetPasswordRequests(): void
{ {
$user = $this->getUserByEmail('test@example.org'); $user = $this->getUserByEmail('test@example.org');
$this->assertInstanceOf(User::class, $user);
$this->createResetPasswordRequest($user); $this->createResetPasswordRequest($user);
$this->client->request(Request::METHOD_GET, '/backoffice/settings'); $this->client->request(Request::METHOD_GET, '/backoffice/settings');
@@ -277,7 +236,6 @@ final class SettingsControllerTest extends WebTestCase
$this->entityManager->clear(); $this->entityManager->clear();
$user = $this->getUserByEmail('new-address@example.org'); $user = $this->getUserByEmail('new-address@example.org');
$this->assertInstanceOf(User::class, $user);
$this->assertSame(0, $this->entityManager->getRepository(ResetPasswordRequest::class)->count(['user' => $user])); $this->assertSame(0, $this->entityManager->getRepository(ResetPasswordRequest::class)->count(['user' => $user]));
} }
@@ -293,7 +251,7 @@ final class SettingsControllerTest extends WebTestCase
self::assertResponseRedirects('/backoffice/settings'); self::assertResponseRedirects('/backoffice/settings');
$this->entityManager->clear(); $this->entityManager->clear();
$this->assertInstanceOf(User::class, $this->getUserByEmail('test@example.org')); $this->getUserByEmail('test@example.org');
} }
public function testDeleteAccountRemovesSoleOwnerSeasonsAndKeepsSharedSeasons(): void public function testDeleteAccountRemovesSoleOwnerSeasonsAndKeepsSharedSeasons(): void
@@ -309,7 +267,7 @@ final class SettingsControllerTest extends WebTestCase
self::assertResponseRedirects(); self::assertResponseRedirects();
$this->entityManager->clear(); $this->entityManager->clear();
$this->assertNotInstanceOf(User::class, $this->getUserByEmail('sole-owner@example.org')); $this->assertNotInstanceOf(User::class, $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'sole-owner@example.org']));
// Sole-owner season is removed, including its quiz // Sole-owner season is removed, including its quiz
$this->assertNotInstanceOf(Season::class, $this->entityManager->getRepository(Season::class)->findOneBy(['seasonCode' => 'doomd'])); $this->assertNotInstanceOf(Season::class, $this->entityManager->getRepository(Season::class)->findOneBy(['seasonCode' => 'doomd']));
@@ -340,7 +298,7 @@ final class SettingsControllerTest extends WebTestCase
self::assertResponseRedirects(); self::assertResponseRedirects();
$this->entityManager->clear(); $this->entityManager->clear();
$this->assertNotInstanceOf(User::class, $this->getUserByEmail('user2@example.org')); $this->assertNotInstanceOf(User::class, $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'user2@example.org']));
foreach (['krtek', 'bbbbb'] as $seasonCode) { foreach (['krtek', 'bbbbb'] as $seasonCode) {
$season = $this->entityManager->getRepository(Season::class)->findOneBy(['seasonCode' => $seasonCode]); $season = $this->entityManager->getRepository(Season::class)->findOneBy(['seasonCode' => $seasonCode]);
@@ -378,7 +336,6 @@ final class SettingsControllerTest extends WebTestCase
public function testDownloadDataRequiresVerifiedEmail(): void public function testDownloadDataRequiresVerifiedEmail(): void
{ {
$user = $this->getUserByEmail('test@example.org'); $user = $this->getUserByEmail('test@example.org');
$this->assertInstanceOf(User::class, $user);
$this->assertFalse($user->isVerified); $this->assertFalse($user->isVerified);
$this->client->request(Request::METHOD_GET, '/backoffice/settings/download-data'); $this->client->request(Request::METHOD_GET, '/backoffice/settings/download-data');
@@ -389,8 +346,8 @@ final class SettingsControllerTest extends WebTestCase
private function markUserVerified(string $email): void private function markUserVerified(string $email): void
{ {
$user = $this->getUserByEmail($email); $user = $this->getUserByEmail($email);
$this->assertInstanceOf(User::class, $user);
$user->isVerified = true; $user->isVerified = true;
$this->entityManager->flush(); $this->entityManager->flush();
} }
} }
@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace Tvdt\Tests\Controller;
use PHPUnit\Framework\Attributes\CoversClass;
use Symfony\Component\HttpFoundation\Request;
use Tvdt\Controller\EliminationController;
use Tvdt\Entity\Elimination;
use Tvdt\Helpers\Base64;
#[CoversClass(EliminationController::class)]
final class EliminationControllerTest extends AbstractControllerWebTestCase
{
private Elimination $elimination;
protected function setUp(): void
{
parent::setUp();
$quiz = $this->getQuizByName('Quiz 1');
$this->elimination = new Elimination($quiz);
$this->elimination->data = ['Tom' => Elimination::SCREEN_GREEN];
$this->entityManager->persist($this->elimination);
$this->entityManager->flush();
$this->loginAs('krtek-admin@example.org');
}
public function testIndexIsDeniedForNonOwner(): void
{
$this->loginAs('test@example.org');
$this->client->request(Request::METHOD_GET, \sprintf('/elimination/%s', $this->elimination->id));
self::assertResponseStatusCodeSame(403);
}
public function testIndexPageLoads(): void
{
$this->client->request(Request::METHOD_GET, \sprintf('/elimination/%s', $this->elimination->id));
self::assertResponseIsSuccessful();
self::assertSelectorExists('form');
}
public function testIndexRedirectsToCandidateScreen(): void
{
$crawler = $this->client->request(Request::METHOD_GET, \sprintf('/elimination/%s', $this->elimination->id));
$form = $crawler->filter('form')->form([
'elimination_enter_name[name]' => 'Tom',
]);
$this->client->submit($form);
self::assertResponseRedirects(\sprintf('/elimination/%s/%s', $this->elimination->id, Base64::base64UrlEncode('Tom')));
}
public function testCandidateScreenUnknownCandidateRedirectsWithFlash(): void
{
$this->client->request(Request::METHOD_GET, \sprintf('/elimination/%s/%s', $this->elimination->id, Base64::base64UrlEncode('Nobody')));
self::assertResponseRedirects(\sprintf('/elimination/%s', $this->elimination->id));
$this->client->followRedirect();
self::assertSelectorTextContains('body', 'Kon kandidaat met naam Nobody niet vinden');
}
public function testCandidateScreenCandidateNotInEliminationDataRedirectsWithFlash(): void
{
$this->client->request(Request::METHOD_GET, \sprintf('/elimination/%s/%s', $this->elimination->id, Base64::base64UrlEncode('Claudia')));
self::assertResponseRedirects(\sprintf('/elimination/%s', $this->elimination->id));
$this->client->followRedirect();
self::assertSelectorTextContains('body', 'Kon geen kandidaat vinden met de naam Claudia in de eliminatie');
}
public function testCandidateScreenRendersColour(): void
{
$this->client->request(Request::METHOD_GET, \sprintf('/elimination/%s/%s', $this->elimination->id, Base64::base64UrlEncode('Tom')));
self::assertResponseIsSuccessful();
self::assertSelectorExists(\sprintf('#%s', Elimination::SCREEN_GREEN));
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Tvdt\Tests\Controller;
use PHPUnit\Framework\Attributes\CoversClass;
use Symfony\Component\HttpFoundation\Request;
use Tvdt\Controller\LoginController;
#[CoversClass(LoginController::class)]
final class LoginControllerTest extends AbstractControllerWebTestCase
{
public function testLoginPageLoadsWhenNotAuthenticated(): void
{
$this->client->request(Request::METHOD_GET, '/login');
self::assertResponseIsSuccessful();
self::assertSelectorExists('form');
}
public function testLoginRedirectsToBackofficeWhenAlreadyAuthenticated(): void
{
$this->loginAs('test@example.org');
$this->client->request(Request::METHOD_GET, '/login');
self::assertResponseRedirects('/backoffice/');
}
public function testLoginWithInvalidCredentialsShowsFlash(): void
{
$this->client->request(Request::METHOD_GET, '/login');
$form = $this->client->getCrawler()->filter('form')->form([
'_username' => 'test@example.org',
'_password' => 'wrong-password',
]);
$this->client->submit($form);
self::assertResponseRedirects('/login');
$this->client->followRedirect();
self::assertSelectorTextContains('body', 'Ongeldige inloggegevens.');
}
public function testLogoutIsInterceptedByFirewall(): void
{
$this->loginAs('test@example.org');
$this->client->request(Request::METHOD_GET, '/logout');
self::assertResponseRedirects();
}
}
+209
View File
@@ -0,0 +1,209 @@
<?php
declare(strict_types=1);
namespace Tvdt\Tests\Controller;
use PHPUnit\Framework\Attributes\CoversClass;
use Symfony\Component\HttpFoundation\Request;
use Tvdt\Controller\QuizController;
use Tvdt\Entity\Answer;
use Tvdt\Entity\Candidate;
use Tvdt\Entity\GivenAnswer;
use Tvdt\Entity\Question;
use Tvdt\Entity\QuizCandidate;
use Tvdt\Helpers\Base64;
#[CoversClass(QuizController::class)]
final class QuizControllerTest extends AbstractControllerWebTestCase
{
private function answerQuestion(Question $question): void
{
$tomHash = Base64::base64UrlEncode('Tom');
$url = \sprintf('/krtek/%s', $tomHash);
$crawler = $this->client->request(Request::METHOD_GET, $url);
self::assertResponseIsSuccessful();
$token = (string) $crawler->filter('input[name="token"]')->first()->attr('value');
$answer = $question->answers->first();
$this->assertInstanceOf(Answer::class, $answer);
$this->client->request(Request::METHOD_POST, $url, [
'token' => $token,
'answer' => (string) $answer->id,
]);
self::assertResponseRedirects($url);
}
public function testSelectSeasonPageLoads(): void
{
$this->client->request(Request::METHOD_GET, '/');
self::assertResponseIsSuccessful();
self::assertSelectorExists('form');
}
public function testSelectSeasonWithInvalidCodeRedirectsWithFlash(): void
{
$crawler = $this->client->request(Request::METHOD_GET, '/');
$form = $crawler->filter('form')->form([
'select_season[season_code]' => 'aaaaa',
]);
$this->client->submit($form);
self::assertResponseRedirects('/');
$this->client->followRedirect();
self::assertSelectorTextContains('body', 'Ongeldige seizoencode');
}
public function testSelectSeasonWithValidCodeRedirectsToEnterName(): void
{
$crawler = $this->client->request(Request::METHOD_GET, '/');
$form = $crawler->filter('form')->form([
'select_season[season_code]' => 'krtek',
]);
$this->client->submit($form);
self::assertResponseRedirects('/krtek');
}
public function testEnterNamePageLoads(): void
{
$this->client->request(Request::METHOD_GET, '/krtek');
self::assertResponseIsSuccessful();
self::assertSelectorExists('form');
}
public function testEnterNameRedirectsToQuizPage(): void
{
$crawler = $this->client->request(Request::METHOD_GET, '/krtek');
$form = $crawler->filter('form')->form([
'enter_name[name]' => 'Tom',
]);
$this->client->submit($form);
self::assertResponseRedirects(\sprintf('/krtek/%s', Base64::base64UrlEncode('Tom')));
}
public function testQuizPageUnknownCandidateRedirectsWithFlash(): void
{
$this->client->request(Request::METHOD_GET, \sprintf('/krtek/%s', Base64::base64UrlEncode('Nobody')));
self::assertResponseRedirects('/krtek');
$this->client->followRedirect();
self::assertSelectorTextContains('body', 'Kandidaat niet gevonden');
}
public function testQuizPageWithoutActiveQuizRedirectsWithFlash(): void
{
$season = $this->getSeasonByCode('bbbbb');
$season->addCandidate(new Candidate('Nienke'));
$this->entityManager->flush();
$this->client->request(Request::METHOD_GET, \sprintf('/bbbbb/%s', Base64::base64UrlEncode('Nienke')));
self::assertResponseRedirects('/bbbbb');
$this->client->followRedirect();
self::assertSelectorTextContains('body', 'Er is geen test actief');
}
public function testQuizPageRendersFirstQuestion(): void
{
$this->client->request(Request::METHOD_GET, \sprintf('/krtek/%s', Base64::base64UrlEncode('Tom')));
self::assertResponseIsSuccessful();
self::assertSelectorTextContains('body', 'Is de Krtek een man of een vrouw?');
}
public function testQuizPageAnsweringPersistsGivenAnswerAndRedirects(): void
{
$quiz = $this->getQuizByName('Quiz 1');
$firstQuestion = $quiz->questions->first();
$this->assertInstanceOf(Question::class, $firstQuestion);
$answer = $firstQuestion->answers->first();
$this->assertInstanceOf(Answer::class, $answer);
$this->answerQuestion($firstQuestion);
$this->entityManager->clear();
$candidate = $this->getCandidate('Tom');
$givenAnswer = $this->entityManager->getRepository(GivenAnswer::class)->findOneBy(['candidate' => $candidate]);
$this->assertInstanceOf(GivenAnswer::class, $givenAnswer);
$this->assertTrue($answer->id->equals($givenAnswer->answer->id));
}
public function testQuizPageInvalidAnswerIdShowsFlash(): void
{
$url = \sprintf('/krtek/%s', Base64::base64UrlEncode('Tom'));
$crawler = $this->client->request(Request::METHOD_GET, $url);
$token = (string) $crawler->filter('input[name="token"]')->first()->attr('value');
$this->client->request(Request::METHOD_POST, $url, [
'token' => $token,
'answer' => '00000000-0000-0000-0000-000000000000',
]);
self::assertResponseRedirects($url);
$this->client->followRedirect();
self::assertSelectorTextContains('body', 'Selecteer een antwoorden alsjeblieft');
}
public function testQuizPageOutOfOrderAnswerShowsFlash(): void
{
$quiz = $this->getQuizByName('Quiz 1');
$secondQuestion = $quiz->questions->get(1);
$this->assertInstanceOf(Question::class, $secondQuestion);
$answer = $secondQuestion->answers->first();
$this->assertInstanceOf(Answer::class, $answer);
$url = \sprintf('/krtek/%s', Base64::base64UrlEncode('Tom'));
$crawler = $this->client->request(Request::METHOD_GET, $url);
$token = (string) $crawler->filter('input[name="token"]')->first()->attr('value');
$this->client->request(Request::METHOD_POST, $url, [
'token' => $token,
'answer' => (string) $answer->id,
]);
self::assertResponseRedirects($url);
$this->client->followRedirect();
self::assertSelectorTextContains('body', 'Je kan deze vraag niet beantwoorden');
}
public function testQuizPageCompletedShowsFlashAndRedirects(): void
{
$quiz = $this->getQuizByName('Quiz 1');
foreach ($quiz->questions as $question) {
$this->answerQuestion($question);
}
$this->client->request(Request::METHOD_GET, \sprintf('/krtek/%s', Base64::base64UrlEncode('Tom')));
self::assertResponseRedirects('/krtek');
$this->client->followRedirect();
self::assertSelectorTextContains('body', 'Test voltooid');
}
public function testQuizPageInactiveCandidateIsBlocked(): void
{
$quiz = $this->getQuizByName('Quiz 1');
$candidate = $this->getCandidate('Tom');
$quizCandidate = new QuizCandidate($quiz, $candidate);
$quizCandidate->active = false;
$this->entityManager->persist($quizCandidate);
$this->entityManager->flush();
$this->client->request(Request::METHOD_GET, \sprintf('/krtek/%s', Base64::base64UrlEncode('Tom')));
self::assertResponseRedirects('/krtek');
$this->client->followRedirect();
self::assertSelectorTextContains('body', 'Je mag deze test niet beantwoorden');
}
}
@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace Tvdt\Tests\Controller;
use PHPUnit\Framework\Attributes\CoversClass;
use Symfony\Component\HttpFoundation\Request;
use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface;
use Tvdt\Controller\RegistrationController;
#[CoversClass(RegistrationController::class)]
final class RegistrationControllerTest extends AbstractControllerWebTestCase
{
public function testRegisterPageLoadsWhenNotAuthenticated(): void
{
$this->client->request(Request::METHOD_GET, '/register');
self::assertResponseIsSuccessful();
self::assertSelectorExists('form');
}
public function testRegisterRedirectsToBackofficeWhenAlreadyAuthenticated(): void
{
$this->loginAs('test@example.org');
$this->client->request(Request::METHOD_GET, '/register');
self::assertResponseRedirects('/backoffice/');
}
public function testRegisterCreatesUserSendsConfirmationAndLogsIn(): void
{
$crawler = $this->client->request(Request::METHOD_GET, '/register');
$form = $crawler->filter('form')->form([
'registration_form[email]' => 'newuser@example.org',
'registration_form[plainPassword][first]' => 'NewPass123!',
'registration_form[plainPassword][second]' => 'NewPass123!',
]);
$this->client->submit($form);
self::assertResponseRedirects('/backoffice/');
self::assertEmailCount(1);
$this->entityManager->clear();
$user = $this->getUserByEmail('newuser@example.org');
$this->assertFalse($user->isVerified);
}
public function testVerifyEmailWithoutIdRedirectsToRegister(): void
{
$this->client->request(Request::METHOD_GET, '/verify/email');
self::assertResponseRedirects('/register');
}
public function testVerifyEmailWithUnknownIdRedirectsToRegister(): void
{
$this->client->request(Request::METHOD_GET, '/verify/email', ['id' => '00000000-0000-0000-0000-000000000000']);
self::assertResponseRedirects('/register');
}
public function testVerifyEmailWithValidSignatureMarksUserVerified(): void
{
$user = $this->getUserByEmail('test@example.org');
$this->assertFalse($user->isVerified);
/** @var VerifyEmailHelperInterface $helper */
$helper = self::getContainer()->get(VerifyEmailHelperInterface::class);
$signature = $helper->generateSignature('tvdt_verify_email', $user->id->toRfc4122(), $user->email, ['id' => $user->id]);
$this->client->request(Request::METHOD_GET, $signature->getSignedUrl());
self::assertResponseRedirects('/backoffice/');
$this->entityManager->clear();
$updatedUser = $this->getUserByEmail('test@example.org');
$this->assertTrue($updatedUser->isVerified);
}
public function testVerifyEmailWithInvalidSignatureShowsErrorAndRedirects(): void
{
$user = $this->getUserByEmail('test@example.org');
$this->client->request(Request::METHOD_GET, '/verify/email', ['id' => (string) $user->id, 'expires' => '9999999999', 'signature' => 'invalid']);
self::assertResponseRedirects('/register');
}
}
@@ -4,10 +4,8 @@ declare(strict_types=1);
namespace Tvdt\Tests\Controller; namespace Tvdt\Tests\Controller;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use Symfony\Bundle\FrameworkBundle\KernelBrowser; use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface; use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface;
@@ -15,18 +13,8 @@ use Tvdt\Controller\ResetPasswordController;
use Tvdt\Entity\User; use Tvdt\Entity\User;
#[CoversClass(ResetPasswordController::class)] #[CoversClass(ResetPasswordController::class)]
final class ResetPasswordControllerTest extends WebTestCase final class ResetPasswordControllerTest extends AbstractControllerWebTestCase
{ {
private KernelBrowser $client;
private EntityManagerInterface $entityManager;
protected function setUp(): void
{
$this->client = self::createClient();
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
}
public function testRequestPageLoads(): void public function testRequestPageLoads(): void
{ {
$this->client->request(Request::METHOD_GET, '/reset-password'); $this->client->request(Request::METHOD_GET, '/reset-password');
@@ -35,22 +23,19 @@ final class ResetPasswordControllerTest extends WebTestCase
$this->assertSelectorExists('form'); $this->assertSelectorExists('form');
} }
public function testRequestWithUnknownEmailRedirectsToCheckEmail(): void /** @return iterable<string, array{string}> */
public static function emailProvider(): iterable
{ {
$this->client->request(Request::METHOD_GET, '/reset-password'); yield 'unknown email' => ['unknown@example.org'];
$form = $this->client->getCrawler()->filter('form')->form([ yield 'known email' => ['test@example.org'];
'reset_password_request_form[email]' => 'unknown@example.org',
]);
$this->client->submit($form);
$this->assertResponseRedirects('/reset-password/check-email');
} }
public function testRequestWithKnownEmailRedirectsToCheckEmail(): void #[DataProvider('emailProvider')]
public function testRequestRedirectsToCheckEmail(string $email): void
{ {
$this->client->request(Request::METHOD_GET, '/reset-password'); $this->client->request(Request::METHOD_GET, '/reset-password');
$form = $this->client->getCrawler()->filter('form')->form([ $form = $this->client->getCrawler()->filter('form')->form([
'reset_password_request_form[email]' => 'test@example.org', 'reset_password_request_form[email]' => $email,
]); ]);
$this->client->submit($form); $this->client->submit($form);
+1 -10
View File
@@ -6,21 +6,12 @@ namespace Tvdt\Tests\Controller;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use Safe\DateTimeImmutable; use Safe\DateTimeImmutable;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Tvdt\Controller\WellKnownController; use Tvdt\Controller\WellKnownController;
#[CoversClass(WellKnownController::class)] #[CoversClass(WellKnownController::class)]
final class WellKnownControllerTest extends WebTestCase final class WellKnownControllerTest extends AbstractControllerWebTestCase
{ {
private KernelBrowser $client;
protected function setUp(): void
{
$this->client = self::createClient();
}
public function testChangePasswordRedirectsToSettings(): void public function testChangePasswordRedirectsToSettings(): void
{ {
$this->client->request(Request::METHOD_GET, '/.well-known/change-password'); $this->client->request(Request::METHOD_GET, '/.well-known/change-password');
+100
View File
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Tvdt\Tests\Entity;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Tvdt\Entity\BankAnswer;
use Tvdt\Entity\BankQuestion;
use Tvdt\Entity\BankQuestionUsage;
use Tvdt\Entity\Quiz;
#[CoversClass(BankQuestion::class)]
final class BankQuestionTest extends TestCase
{
public function testIsCompleteForQuizIsFalseWithoutTwoAnswers(): void
{
$bankQuestion = new BankQuestion();
$bankQuestion->addAnswer(new BankAnswer('Only answer', true));
$this->assertFalse($bankQuestion->isCompleteForQuiz);
}
public function testIsCompleteForQuizIsFalseWithoutCorrectAnswer(): void
{
$bankQuestion = new BankQuestion();
$bankQuestion->addAnswer(new BankAnswer('Wrong 1'));
$bankQuestion->addAnswer(new BankAnswer('Wrong 2'));
$this->assertFalse($bankQuestion->isCompleteForQuiz);
}
public function testIsCompleteForQuizIsFalseWithMultipleCorrectAnswers(): void
{
$bankQuestion = new BankQuestion();
$bankQuestion->addAnswer(new BankAnswer('Right 1', true));
$bankQuestion->addAnswer(new BankAnswer('Right 2', true));
$this->assertFalse($bankQuestion->isCompleteForQuiz);
}
public function testIsCompleteForQuizIsTrueWithTwoAnswersAndOneCorrect(): void
{
$bankQuestion = new BankQuestion();
$bankQuestion->addAnswer(new BankAnswer('Right', true));
$bankQuestion->addAnswer(new BankAnswer('Wrong'));
$this->assertTrue($bankQuestion->isCompleteForQuiz);
}
public function testCanBeAssignedIsTrueWhenUnused(): void
{
$bankQuestion = new BankQuestion();
$this->assertTrue($bankQuestion->canBeAssigned);
}
public function testCanBeAssignedIsTrueWhenReusableEvenIfUsed(): void
{
$bankQuestion = new BankQuestion();
$bankQuestion->reusable = true;
$bankQuestion->addUsage(new BankQuestionUsage($bankQuestion, new Quiz()));
$this->assertTrue($bankQuestion->canBeAssigned);
}
public function testCanBeAssignedIsFalseWhenSingleUseAndUsed(): void
{
$bankQuestion = new BankQuestion();
$bankQuestion->addUsage(new BankQuestionUsage($bankQuestion, new Quiz()));
$this->assertFalse($bankQuestion->canBeAssigned);
}
public function testIsUsedInQuizIsTrueForQuizWithUsage(): void
{
$bankQuestion = new BankQuestion();
$quiz = new Quiz();
$bankQuestion->addUsage(new BankQuestionUsage($bankQuestion, $quiz));
$this->assertTrue($bankQuestion->isUsedInQuiz($quiz));
}
public function testIsUsedInQuizIsFalseForDifferentQuiz(): void
{
$bankQuestion = new BankQuestion();
$bankQuestion->addUsage(new BankQuestionUsage($bankQuestion, new Quiz()));
$this->assertFalse($bankQuestion->isUsedInQuiz(new Quiz()));
}
public function testToStringReturnsQuestionText(): void
{
$bankQuestion = new BankQuestion();
$bankQuestion->question = 'Wie is de Krtek?';
$this->assertSame('Wie is de Krtek?', (string) $bankQuestion);
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Tvdt\Tests\Entity;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\InputBag;
use Tvdt\Entity\Elimination;
use Tvdt\Entity\Quiz;
#[CoversClass(Elimination::class)]
final class EliminationTest extends TestCase
{
public function testGetScreenColourReturnsNullForNullName(): void
{
$elimination = new Elimination(new Quiz());
$this->assertNull($elimination->getScreenColour(null));
}
public function testGetScreenColourReturnsNullForUnknownName(): void
{
$elimination = new Elimination(new Quiz());
$elimination->data = $this->colours(['Tom' => Elimination::SCREEN_GREEN]);
$this->assertNull($elimination->getScreenColour('Claudia'));
}
public function testGetScreenColourReturnsColourForKnownName(): void
{
$elimination = new Elimination(new Quiz());
$elimination->data = $this->colours(['Tom' => Elimination::SCREEN_GREEN, 'Claudia' => Elimination::SCREEN_RED]);
$this->assertSame(Elimination::SCREEN_RED, $elimination->getScreenColour('Claudia'));
}
public function testUpdateFromInputBagUpdatesKnownColours(): void
{
$elimination = new Elimination(new Quiz());
$elimination->data = $this->colours(['Tom' => Elimination::SCREEN_GREEN, 'Claudia' => Elimination::SCREEN_RED]);
$elimination->updateFromInputBag($this->inputBag(['colour-tom' => Elimination::SCREEN_RED]));
$this->assertSame(Elimination::SCREEN_RED, $elimination->data['Tom']);
$this->assertSame(Elimination::SCREEN_RED, $elimination->data['Claudia']);
}
public function testUpdateFromInputBagIgnoresMissingInput(): void
{
$elimination = new Elimination(new Quiz());
$elimination->data = $this->colours(['Tom' => Elimination::SCREEN_GREEN]);
$elimination->updateFromInputBag($this->inputBag([]));
$this->assertSame(Elimination::SCREEN_GREEN, $elimination->data['Tom']);
}
public function testUpdateFromInputBagReturnsSelf(): void
{
$elimination = new Elimination(new Quiz());
$this->assertSame($elimination, $elimination->updateFromInputBag($this->inputBag([])));
}
/**
* @param array<string, string> $colours
*
* @return array<string, string>
*/
private function colours(array $colours): array
{
return $colours;
}
/**
* @param array<string, string> $parameters
*
* @return InputBag<bool|float|int|string>
*/
private function inputBag(array $parameters): InputBag
{
/** @var InputBag<bool|float|int|string> $inputBag */
$inputBag = new InputBag($parameters);
return $inputBag;
}
}
+17 -11
View File
@@ -4,28 +4,34 @@ declare(strict_types=1);
namespace Tvdt\Tests\Helpers; namespace Tvdt\Tests\Helpers;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Safe\Exceptions\UrlException; use Safe\Exceptions\UrlException;
use Tvdt\Helpers\Base64; use Tvdt\Helpers\Base64;
#[CoversClass(Base64::class)]
final class Base64Test extends TestCase final class Base64Test extends TestCase
{ {
public function testBase64UrlEncode(): void /** @return iterable<string, array{string, string}> */
public static function pairProvider(): iterable
{ {
$this->assertSame('TWFyaWpu', Base64::base64UrlEncode('Marijn')); yield 'Marijn' => ['Marijn', 'TWFyaWpu'];
$this->assertSame('UGhpbGluZQ', Base64::base64UrlEncode('Philine')); yield 'Philine' => ['Philine', 'UGhpbGluZQ'];
yield 'byte 254' => [\chr(254), '_g'];
$this->assertSame('_g', Base64::base64UrlEncode(\chr(254))); yield 'byte 250' => [\chr(250), '-g'];
$this->assertSame('-g', Base64::base64UrlEncode(\chr(250)));
} }
public function testBase64UrlDecode(): void #[DataProvider('pairProvider')]
public function testBase64UrlEncode(string $decoded, string $encoded): void
{ {
$this->assertSame('Marijn', Base64::base64UrlDecode('TWFyaWpu')); $this->assertSame($encoded, Base64::base64UrlEncode($decoded));
$this->assertSame('Philine', Base64::base64UrlDecode('UGhpbGluZQ')); }
$this->assertSame(\chr(254), Base64::base64UrlDecode('_g')); #[DataProvider('pairProvider')]
$this->assertSame(\chr(250), Base64::base64UrlDecode('-g')); public function testBase64UrlDecode(string $decoded, string $encoded): void
{
$this->assertSame($decoded, Base64::base64UrlDecode($encoded));
} }
public function testBase64UrlDecodeCanHandlePadding(): void public function testBase64UrlDecodeCanHandlePadding(): void
+17 -27
View File
@@ -4,41 +4,31 @@ declare(strict_types=1);
namespace Tvdt\Tests\Helpers; namespace Tvdt\Tests\Helpers;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Tvdt\Helpers\FilenameSanitizer; use Tvdt\Helpers\FilenameSanitizer;
#[CoversClass(FilenameSanitizer::class)]
final class FilenameSanitizerTest extends TestCase final class FilenameSanitizerTest extends TestCase
{ {
public function testReplacesSpacesWithDashes(): void /** @return iterable<string, array{string, string}> */
public static function sanitizeProvider(): iterable
{ {
$this->assertSame('Krtek-Weekend', FilenameSanitizer::sanitize('Krtek Weekend')); yield 'replaces spaces with dashes' => ['Krtek Weekend', 'Krtek-Weekend'];
yield 'strips path traversal' => ['../../etc/passwd', 'etc-passwd'];
yield 'strips forward slash' => ['a/b', 'a-b'];
yield 'strips backslash' => ['a\\b', 'a-b'];
yield 'strips control characters and special symbols' => ["Quiz #1 <script>\0", 'Quiz-1-script'];
yield 'transliterates unicode to ascii' => ['Wéird Ñame', 'Weird-Name'];
yield 'transliterates at sign in email' => ['test@example.org', 'test-example-org'];
yield 'returns unnamed for empty input' => ['', 'unnamed'];
yield 'returns unnamed for fully stripped input' => ['///', 'unnamed'];
} }
public function testStripsPathSeparatorsAndTraversal(): void #[DataProvider('sanitizeProvider')]
public function testSanitize(string $input, string $expected): void
{ {
$this->assertSame('etc-passwd', FilenameSanitizer::sanitize('../../etc/passwd')); $this->assertSame($expected, FilenameSanitizer::sanitize($input));
$this->assertSame('a-b', FilenameSanitizer::sanitize('a/b'));
$this->assertSame('a-b', FilenameSanitizer::sanitize('a\\b'));
}
public function testStripsControlCharactersAndSpecialSymbols(): void
{
$this->assertSame('Quiz-1-script', FilenameSanitizer::sanitize("Quiz #1 <script>\0"));
}
public function testTransliteratesUnicodeToAscii(): void
{
$this->assertSame('Weird-Name', FilenameSanitizer::sanitize('Wéird Ñame'));
}
public function testTransliteratesAtSignInEmail(): void
{
$this->assertSame('test-example-org', FilenameSanitizer::sanitize('test@example.org'));
}
public function testReturnsUnnamedForEmptyOrFullyStrippedInput(): void
{
$this->assertSame('unnamed', FilenameSanitizer::sanitize(''));
$this->assertSame('unnamed', FilenameSanitizer::sanitize('///'));
} }
} }
+12 -9
View File
@@ -5,25 +5,28 @@ declare(strict_types=1);
namespace Tvdt\Tests\Repository; namespace Tvdt\Tests\Repository;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use Tvdt\Entity\Season; use Tvdt\Entity\Season;
use Tvdt\Repository\SeasonRepository; use Tvdt\Repository\SeasonRepository;
#[CoversClass(SeasonRepository::class)] #[CoversClass(SeasonRepository::class)]
final class SeasonRepositoryTest extends DatabaseTestCase final class SeasonRepositoryTest extends DatabaseTestCase
{ {
public function testGetSeasonsForUser(): void /** @return iterable<string, array{string, string}> */
public static function userSeasonsProvider(): iterable
{ {
$user = $this->getUserByEmail('krtek-admin@example.org'); yield 'krtek admin' => ['krtek-admin@example.org', 'krtek'];
yield 'user1' => ['user1@example.org', 'bbbbb'];
}
#[DataProvider('userSeasonsProvider')]
public function testGetSeasonsForUser(string $email, string $expectedSeasonCode): void
{
$user = $this->getUserByEmail($email);
$seasons = $this->seasonRepository->getSeasonsForUser($user); $seasons = $this->seasonRepository->getSeasonsForUser($user);
$this->assertCount(1, $seasons); $this->assertCount(1, $seasons);
$this->assertSame('krtek', $seasons[0]->seasonCode); $this->assertSame($expectedSeasonCode, $seasons[0]->seasonCode);
$user = $this->getUserByEmail('user1@example.org');
$seasons = $this->seasonRepository->getSeasonsForUser($user);
$this->assertCount(1, $seasons);
$this->assertSame('bbbbb', $seasons[0]->seasonCode);
} }
public function testUserWithMultipleSeasons(): void public function testUserWithMultipleSeasons(): void