diff --git a/tests/Command/ClaimSeasonCommandTest.php b/tests/Command/ClaimSeasonCommandTest.php index 4cd6634..52597c9 100644 --- a/tests/Command/ClaimSeasonCommandTest.php +++ b/tests/Command/ClaimSeasonCommandTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Tvdt\Tests\Command; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Console\Command\Command; @@ -48,21 +49,19 @@ final class ClaimSeasonCommandTest extends KernelTestCase $this->assertCount(3, $season->owners); } - public function testInvalidEmailFails(): void + /** @return iterable */ + public static function invalidArgumentsProvider(): iterable { - $this->commandTester->execute([ - 'season-code' => 'krtek', - 'email' => 'nonexisting@example.org', - ]); - - $this->assertSame(Command::FAILURE, $this->commandTester->getStatusCode()); + yield 'unknown email' => ['krtek', 'nonexisting@example.org']; + yield 'unknown season' => ['dhadk', 'test@example.org']; } - public function testInvalidSeasonCodeFails(): void + #[DataProvider('invalidArgumentsProvider')] + public function testInvalidArgumentFails(string $seasonCode, string $email): void { $this->commandTester->execute([ - 'season-code' => 'dhadk', - 'email' => 'test@example.org', + 'season-code' => $seasonCode, + 'email' => $email, ]); $this->assertSame(Command::FAILURE, $this->commandTester->getStatusCode()); diff --git a/tests/Controller/AbstractControllerWebTestCase.php b/tests/Controller/AbstractControllerWebTestCase.php new file mode 100644 index 0000000..3ceacbe --- /dev/null +++ b/tests/Controller/AbstractControllerWebTestCase.php @@ -0,0 +1,100 @@ +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'); + } +} diff --git a/tests/Controller/Backoffice/BackofficeControllerTest.php b/tests/Controller/Backoffice/BackofficeControllerTest.php index 0072558..4ffecb1 100644 --- a/tests/Controller/Backoffice/BackofficeControllerTest.php +++ b/tests/Controller/Backoffice/BackofficeControllerTest.php @@ -4,53 +4,41 @@ declare(strict_types=1); namespace Tvdt\Tests\Controller\Backoffice; -use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\Attributes\CoversClass; -use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Request; use Tvdt\Controller\Backoffice\BackofficeController; -use Tvdt\Entity\Quiz; -use Tvdt\Entity\User; +use Tvdt\Tests\Controller\AbstractControllerWebTestCase; #[CoversClass(BackofficeController::class)] -final class BackofficeControllerTest extends WebTestCase +final class BackofficeControllerTest extends AbstractControllerWebTestCase { public function testExportQuizFilenameIsSanitized(): void { - $client = self::createClient(); - $entityManager = self::getContainer()->get(EntityManagerInterface::class); - - $user = $entityManager->getRepository(User::class)->findOneBy(['email' => 'user2@example.org']); - $this->assertInstanceOf(User::class, $user); + $user = $this->getUserByEmail('user2@example.org'); $user->isVerified = true; - $entityManager->flush(); - $client->loginUser($user); - $quiz = $entityManager->getRepository(Quiz::class)->findOneBy(['name' => 'Quiz 1']); - $this->assertInstanceOf(Quiz::class, $quiz); + $this->entityManager->flush(); + $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(); - $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->assertStringNotContainsString('Quiz 1.xlsx', $disposition); } public function testExportQuizRequiresVerifiedEmail(): void { - $client = self::createClient(); - $entityManager = self::getContainer()->get(EntityManagerInterface::class); - - $user = $entityManager->getRepository(User::class)->findOneBy(['email' => 'user2@example.org']); - $this->assertInstanceOf(User::class, $user); + $user = $this->getUserByEmail('user2@example.org'); $this->assertFalse($user->isVerified); - $client->loginUser($user); + $this->client->loginUser($user); - $quiz = $entityManager->getRepository(Quiz::class)->findOneBy(['name' => 'Quiz 1']); - $this->assertInstanceOf(Quiz::class, $quiz); + $quiz = $this->getQuizByName('Quiz 1'); - $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)); } diff --git a/tests/Controller/Backoffice/PrepareEliminationControllerTest.php b/tests/Controller/Backoffice/PrepareEliminationControllerTest.php new file mode 100644 index 0000000..3147fa9 --- /dev/null +++ b/tests/Controller/Backoffice/PrepareEliminationControllerTest.php @@ -0,0 +1,118 @@ +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)); + } +} diff --git a/tests/Controller/Backoffice/QuestionBankControllerTest.php b/tests/Controller/Backoffice/QuestionBankControllerTest.php index 56dce89..d50ada7 100644 --- a/tests/Controller/Backoffice/QuestionBankControllerTest.php +++ b/tests/Controller/Backoffice/QuestionBankControllerTest.php @@ -4,39 +4,18 @@ declare(strict_types=1); namespace Tvdt\Tests\Controller\Backoffice; -use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\Attributes\CoversClass; -use Symfony\Bundle\FrameworkBundle\KernelBrowser; -use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Request; use Tvdt\Controller\Backoffice\QuestionBankController; use Tvdt\Entity\BankAnswer; use Tvdt\Entity\BankQuestion; use Tvdt\Entity\Question; use Tvdt\Entity\QuestionLabel; -use Tvdt\Entity\Quiz; -use Tvdt\Entity\User; +use Tvdt\Tests\Controller\AbstractControllerWebTestCase; #[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 { $bankQuestion = $this->entityManager->getRepository(BankQuestion::class)->findOneBy(['question' => $question]); @@ -45,26 +24,9 @@ final class QuestionBankControllerTest extends WebTestCase 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 { - $this->loginAsOwner(); + $this->loginAs('krtek-admin@example.org'); $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank'); $this->assertResponseIsSuccessful(); @@ -75,7 +37,7 @@ final class QuestionBankControllerTest extends WebTestCase public function testIndexFiltersByLabel(): void { - $this->loginAsOwner(); + $this->loginAs('krtek-admin@example.org'); $label = $this->entityManager->getRepository(QuestionLabel::class)->findOneBy(['name' => 'Locatie']); $this->assertInstanceOf(QuestionLabel::class, $label); @@ -89,9 +51,7 @@ final class QuestionBankControllerTest extends WebTestCase public function testNonOwnerIsDenied(): void { - $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'test@example.org']); - $this->assertInstanceOf(User::class, $user); - $this->client->loginUser($user); + $this->loginAs('test@example.org'); $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/question-bank'); @@ -100,7 +60,7 @@ final class QuestionBankControllerTest extends WebTestCase 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'); $this->assertResponseIsSuccessful(); @@ -129,7 +89,7 @@ final class QuestionBankControllerTest extends WebTestCase 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'); $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 { - $this->loginAsOwner(); + $this->loginAs('krtek-admin@example.org'); $bankQuestion = $this->getBankQuestion('Wat at de Krtek als ontbijt?'); $url = \sprintf('/backoffice/season/krtek/question-bank/%s/edit', $bankQuestion->id); @@ -181,12 +141,12 @@ final class QuestionBankControllerTest extends WebTestCase public function testDeleteUsedBankQuestionLeavesQuizIntact(): void { - $this->loginAsOwner(); + $this->loginAs('krtek-admin@example.org'); $bankQuestion = $this->getBankQuestion('Waar sliep de Krtek?'); $quiz2QuestionCount = $this->getQuizByName('Quiz 2')->questions->count(); $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), [ '_token' => $token, @@ -201,13 +161,13 @@ final class QuestionBankControllerTest extends WebTestCase public function testAssignCopiesQuestionIntoQuiz(): void { - $this->loginAsOwner(); + $this->loginAs('krtek-admin@example.org'); $bankQuestion = $this->getBankQuestion('Wat at de Krtek als ontbijt?'); $quiz = $this->getQuizByName('Quiz 2'); $questionCount = $quiz->questions->count(); $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), [ '_token' => $token, @@ -240,7 +200,7 @@ final class QuestionBankControllerTest extends WebTestCase public function testAssignUsedNonReusableQuestionIsRefused(): void { - $this->loginAsOwner(); + $this->loginAs('krtek-admin@example.org'); $bankQuestion = $this->getBankQuestion('Waar sliep de Krtek?'); $quiz = $this->getQuizByName('Quiz 2'); $questionCount = $quiz->questions->count(); @@ -248,7 +208,7 @@ final class QuestionBankControllerTest extends WebTestCase $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 - $token = $this->getCsrfToken('/assign'); + $token = $this->getCsrfTokenFromCurrentPage('/assign'); $this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/question-bank/%s/assign', $bankQuestion->id), [ '_token' => $token, 'quiz' => (string) $quiz->id, @@ -262,13 +222,13 @@ final class QuestionBankControllerTest extends WebTestCase public function testAssignSameReusableQuestionTwiceToSameQuizIsRefused(): void { - $this->loginAsOwner(); + $this->loginAs('krtek-admin@example.org'); $bankQuestion = $this->getBankQuestion('Wie is de Krtek?'); $quiz = $this->getQuizByName('Quiz 2'); $questionCount = $quiz->questions->count(); $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); $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 { - $this->loginAsOwner(); + $this->loginAs('krtek-admin@example.org'); $bankQuestion = $this->getBankQuestion('Wie is de Krtek?'); $finalizedQuiz = $this->getQuizByName('Quiz 1'); $this->assertTrue($finalizedQuiz->isFinalized); $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), [ '_token' => $token, @@ -301,7 +261,7 @@ final class QuestionBankControllerTest extends WebTestCase 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'); $this->assertResponseIsSuccessful(); $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 { - $this->loginAsOwner(); + $this->loginAs('krtek-admin@example.org'); $bankQuestion = $this->getBankQuestion('Wat at de Krtek als ontbijt?'); // 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 { - $this->loginAsOwner(); + $this->loginAs('krtek-admin@example.org'); $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'); @@ -389,7 +349,7 @@ final class QuestionBankControllerTest extends WebTestCase $this->assertInstanceOf(QuestionLabel::class, $label); $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), [ '_token' => $deleteToken, diff --git a/tests/Controller/Backoffice/QuizControllerTest.php b/tests/Controller/Backoffice/QuizControllerTest.php index 0e65754..04db72a 100644 --- a/tests/Controller/Backoffice/QuizControllerTest.php +++ b/tests/Controller/Backoffice/QuizControllerTest.php @@ -4,11 +4,8 @@ declare(strict_types=1); namespace Tvdt\Tests\Controller\Backoffice; -use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\Attributes\CoversClass; use Safe\DateTimeImmutable; -use Symfony\Bundle\FrameworkBundle\KernelBrowser; -use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Request; use Tvdt\Controller\Backoffice\QuizController; use Tvdt\Entity\Answer; @@ -17,51 +14,21 @@ use Tvdt\Entity\GivenAnswer; use Tvdt\Entity\Question; use Tvdt\Entity\Quiz; use Tvdt\Entity\QuizCandidate; -use Tvdt\Entity\Season; -use Tvdt\Entity\User; +use Tvdt\Tests\Controller\AbstractControllerWebTestCase; #[CoversClass(QuizController::class)] -final class QuizControllerTest extends WebTestCase +final class QuizControllerTest extends AbstractControllerWebTestCase { - private KernelBrowser $client; - - private EntityManagerInterface $entityManager; - protected function setUp(): void { - $this->client = self::createClient(); - $this->entityManager = self::getContainer()->get(EntityManagerInterface::class); + parent::setUp(); - $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; - } - - private function getCandidate(string $name): Candidate - { - $candidate = $this->entityManager->getRepository(Candidate::class)->findOneBy(['name' => $name]); - $this->assertInstanceOf(Candidate::class, $candidate); - - return $candidate; + $this->loginAs('krtek-admin@example.org'); } private function getCsrfTokenFromOverview(Quiz $quiz, string $formActionContains): string { - $crawler = $this->client->request(Request::METHOD_GET, \sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz->id)); - 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'); + return $this->getCsrfTokenFromPage(\sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz->id), $formActionContains); } public function testIndexRedirectsToOverview(): void @@ -296,9 +263,7 @@ final class QuizControllerTest extends WebTestCase public function testNonOwnerIsDenied(): void { - $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'test@example.org']); - $this->assertInstanceOf(User::class, $user); - $this->client->loginUser($user); + $this->loginAs('test@example.org'); $quiz = $this->getQuizByName('Quiz 1'); $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 { - $season = $this->entityManager->getRepository(Season::class)->findOneBy(['seasonCode' => 'krtek']); - $this->assertInstanceOf(Season::class, $season); + $season = $this->getSeasonByCode('krtek'); $emptyQuiz = new Quiz(); $emptyQuiz->name = 'Empty Quiz'; @@ -326,8 +290,7 @@ final class QuizControllerTest extends WebTestCase public function testAnswerMappingRedirectsWithFlashWhenNoQuestions(): void { - $season = $this->entityManager->getRepository(Season::class)->findOneBy(['seasonCode' => 'krtek']); - $this->assertInstanceOf(Season::class, $season); + $season = $this->getSeasonByCode('krtek'); $emptyQuiz = new Quiz(); $emptyQuiz->name = 'Empty Quiz'; diff --git a/tests/Controller/Backoffice/QuizFinalizeTest.php b/tests/Controller/Backoffice/QuizFinalizeTest.php index 2e3e5e8..f16b6db 100644 --- a/tests/Controller/Backoffice/QuizFinalizeTest.php +++ b/tests/Controller/Backoffice/QuizFinalizeTest.php @@ -4,63 +4,29 @@ declare(strict_types=1); namespace Tvdt\Tests\Controller\Backoffice; -use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\Attributes\CoversClass; use Safe\DateTimeImmutable; -use Symfony\Bundle\FrameworkBundle\KernelBrowser; -use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Request; use Tvdt\Controller\Backoffice\QuizController; use Tvdt\Entity\Answer; -use Tvdt\Entity\Candidate; use Tvdt\Entity\Question; use Tvdt\Entity\Quiz; use Tvdt\Entity\QuizCandidate; -use Tvdt\Entity\Season; -use Tvdt\Entity\User; +use Tvdt\Tests\Controller\AbstractControllerWebTestCase; #[CoversClass(QuizController::class)] -final class QuizFinalizeTest extends WebTestCase +final class QuizFinalizeTest extends AbstractControllerWebTestCase { - private KernelBrowser $client; - - private EntityManagerInterface $entityManager; - protected function setUp(): void { - $this->client = self::createClient(); - $this->entityManager = self::getContainer()->get(EntityManagerInterface::class); + parent::setUp(); - $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; - } - - private function getKrtekSeason(): Season - { - $season = $this->entityManager->getRepository(Season::class)->findOneBy(['seasonCode' => 'krtek']); - $this->assertInstanceOf(Season::class, $season); - - return $season; + $this->loginAs('krtek-admin@example.org'); } private function getCsrfTokenFromOverview(Quiz $quiz, string $formActionContains): string { - $crawler = $this->client->request(Request::METHOD_GET, \sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz->id)); - $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'); + return $this->getCsrfTokenFromPage(\sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz->id), $formActionContains); } public function testFinalizeSetsFinalizedAt(): void @@ -79,7 +45,7 @@ final class QuizFinalizeTest extends WebTestCase public function testFinalizeRefusedWhenQuizHasErrors(): void { - $season = $this->getKrtekSeason(); + $season = $this->getSeasonByCode('krtek'); $invalidQuiz = new Quiz(); $invalidQuiz->name = 'Invalid Quiz'; @@ -116,7 +82,7 @@ final class QuizFinalizeTest extends WebTestCase $this->assertResponseRedirects(); $this->entityManager->clear(); - $season = $this->getKrtekSeason(); + $season = $this->getSeasonByCode('krtek'); $this->assertInstanceOf(Quiz::class, $season->activeQuiz); $this->assertSame('Quiz 1', $season->activeQuiz->name); } @@ -134,7 +100,7 @@ final class QuizFinalizeTest extends WebTestCase $this->assertResponseRedirects(); $this->entityManager->clear(); - $season = $this->getKrtekSeason(); + $season = $this->getSeasonByCode('krtek'); $this->assertInstanceOf(Quiz::class, $season->activeQuiz); $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 $token = $this->getCsrfTokenFromOverview($quiz, '/unfinalize'); - $candidate = $this->entityManager->getRepository(Candidate::class)->findOneBy(['name' => 'Tom']); - $this->assertInstanceOf(Candidate::class, $candidate); + $candidate = $this->getCandidate('Tom'); $quizCandidate = new QuizCandidate($quiz, $candidate); $quizCandidate->started = new DateTimeImmutable(); @@ -229,6 +194,6 @@ final class QuizFinalizeTest extends WebTestCase self::assertResponseRedirects(\sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz2->id)); $this->entityManager->clear(); - $this->assertNotInstanceOf(Quiz::class, $this->getKrtekSeason()->activeQuiz); + $this->assertNotInstanceOf(Quiz::class, $this->getSeasonByCode('krtek')->activeQuiz); } } diff --git a/tests/Controller/Backoffice/QuizQuestionControllerTest.php b/tests/Controller/Backoffice/QuizQuestionControllerTest.php index 4cb7f03..38ec5bc 100644 --- a/tests/Controller/Backoffice/QuizQuestionControllerTest.php +++ b/tests/Controller/Backoffice/QuizQuestionControllerTest.php @@ -4,47 +4,18 @@ declare(strict_types=1); namespace Tvdt\Tests\Controller\Backoffice; -use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\Attributes\CoversClass; -use Symfony\Bundle\FrameworkBundle\KernelBrowser; -use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Request; use Tvdt\Controller\Backoffice\QuizQuestionController; use Tvdt\Entity\Question; -use Tvdt\Entity\Quiz; -use Tvdt\Entity\User; +use Tvdt\Tests\Controller\AbstractControllerWebTestCase; #[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 { - $this->loginAsOwner(); + $this->loginAs('krtek-admin@example.org'); $quiz = $this->getQuizByName('Quiz 2'); $question = null; @@ -111,7 +82,7 @@ final class QuizQuestionControllerTest extends WebTestCase public function testReorderQuestionsWithinQuiz(): void { - $this->loginAsOwner(); + $this->loginAs('krtek-admin@example.org'); $quiz = $this->getQuizByName('Quiz 2'); $originalQuestions = $quiz->questions->toArray(); diff --git a/tests/Controller/Backoffice/SeasonControllerTest.php b/tests/Controller/Backoffice/SeasonControllerTest.php index 6982157..6c2fabc 100644 --- a/tests/Controller/Backoffice/SeasonControllerTest.php +++ b/tests/Controller/Backoffice/SeasonControllerTest.php @@ -4,40 +4,27 @@ declare(strict_types=1); namespace Tvdt\Tests\Controller\Backoffice; -use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\Attributes\CoversClass; -use Symfony\Bundle\FrameworkBundle\KernelBrowser; -use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Request; use Tvdt\Controller\Backoffice\SeasonController; use Tvdt\Entity\Candidate; use Tvdt\Entity\Season; -use Tvdt\Entity\User; +use Tvdt\Tests\Controller\AbstractControllerWebTestCase; #[CoversClass(SeasonController::class)] -final class SeasonControllerTest extends WebTestCase +final class SeasonControllerTest extends AbstractControllerWebTestCase { - private KernelBrowser $client; - - private EntityManagerInterface $entityManager; - protected function setUp(): void { - $this->client = self::createClient(); - $this->entityManager = self::getContainer()->get(EntityManagerInterface::class); + parent::setUp(); - $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'krtek-admin@example.org']); - $this->assertInstanceOf(User::class, $user); - $this->client->loginUser($user); + $this->loginAs('krtek-admin@example.org'); } public function testRegenerateSeasonCodeChangesTheCode(): void { $oldCode = 'krtek'; - $crawler = $this->client->request(Request::METHOD_GET, \sprintf('/backoffice/season/%s/settings', $oldCode)); - self::assertResponseIsSuccessful(); - - $token = (string) $crawler->filter('form[action*="/regenerate-code"] input[name="_token"]')->first()->attr('value'); + $token = $this->getCsrfTokenFromPage(\sprintf('/backoffice/season/%s/settings', $oldCode), '/regenerate-code'); $this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/%s/settings/regenerate-code', $oldCode), [ '_token' => $token, @@ -54,13 +41,9 @@ final class SeasonControllerTest extends WebTestCase public function testRegenerateSeasonCodeIsDeniedForNonOwner(): void { - $crawler = $this->client->request(Request::METHOD_GET, '/backoffice/season/krtek/settings'); - self::assertResponseIsSuccessful(); - $token = (string) $crawler->filter('form[action*="/regenerate-code"] input[name="_token"]')->first()->attr('value'); + $token = $this->getCsrfTokenFromPage('/backoffice/season/krtek/settings', '/regenerate-code'); - $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => 'test@example.org']); - $this->assertInstanceOf(User::class, $user); - $this->client->loginUser($user); + $this->loginAs('test@example.org'); $this->client->request(Request::METHOD_POST, '/backoffice/season/krtek/settings/regenerate-code', [ '_token' => $token, @@ -69,29 +52,10 @@ final class SeasonControllerTest extends WebTestCase 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 { $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), [ '_token' => $token, @@ -109,7 +73,7 @@ final class SeasonControllerTest extends WebTestCase public function testRenameCandidateToExistingNameShowsError(): void { $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), [ '_token' => $token, @@ -128,7 +92,7 @@ final class SeasonControllerTest extends WebTestCase { $candidate = $this->getCandidate('Tom'); $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), [ '_token' => $token, @@ -143,11 +107,9 @@ final class SeasonControllerTest extends WebTestCase public function testRenameCandidateIsDeniedForNonOwner(): void { $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->assertInstanceOf(User::class, $user); - $this->client->loginUser($user); + $this->loginAs('test@example.org'); $this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/candidate/%s/rename', $candidate->id), [ '_token' => $token, diff --git a/tests/Controller/Backoffice/SettingsControllerTest.php b/tests/Controller/Backoffice/SettingsControllerTest.php index 8261692..d1be975 100644 --- a/tests/Controller/Backoffice/SettingsControllerTest.php +++ b/tests/Controller/Backoffice/SettingsControllerTest.php @@ -4,11 +4,9 @@ declare(strict_types=1); namespace Tvdt\Tests\Controller\Backoffice; -use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use Safe\DateTimeImmutable; -use Symfony\Bundle\FrameworkBundle\KernelBrowser; -use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Tvdt\Controller\Backoffice\SettingsController; @@ -17,43 +15,21 @@ use Tvdt\Entity\Quiz; use Tvdt\Entity\ResetPasswordRequest; use Tvdt\Entity\Season; use Tvdt\Entity\User; +use Tvdt\Tests\Controller\AbstractControllerWebTestCase; #[CoversClass(SettingsController::class)] -final class SettingsControllerTest extends WebTestCase +final class SettingsControllerTest extends AbstractControllerWebTestCase { - private KernelBrowser $client; - - private EntityManagerInterface $entityManager; - protected function setUp(): void { - $this->client = self::createClient(); - $this->entityManager = self::getContainer()->get(EntityManagerInterface::class); + parent::setUp(); $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 { - $crawler = $this->client->request(Request::METHOD_GET, '/backoffice/settings'); - 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'); + return $this->getCsrfTokenFromPage('/backoffice/settings', $formActionContains); } public function testSettingsPageLoadsAndNavContainsSettingsLink(): void @@ -98,7 +74,6 @@ final class SettingsControllerTest extends WebTestCase $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, 'NewPass123!')); @@ -107,32 +82,21 @@ final class SettingsControllerTest extends WebTestCase self::assertResponseIsSuccessful(); } - public function testChangePasswordWithWrongCurrentPasswordIsRejected(): void + /** @return iterable */ + public static function invalidPasswordChangeProvider(): iterable { - $this->client->request(Request::METHOD_GET, '/backoffice/settings'); - $form = $this->client->getCrawler()->filter('form[action*="/backoffice/settings/password"]')->form([ - '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)); + yield 'wrong current password' => ['wrong-password', 'NewPass123!', 'NewPass123!']; + yield 'mismatched repeat' => [TestFixtures::PASSWORD, 'NewPass123!', 'SomethingElse!']; } - public function testChangePasswordWithMismatchedRepeatIsRejected(): void + #[DataProvider('invalidPasswordChangeProvider')] + public function testChangePasswordIsRejected(string $currentPassword, string $first, string $second): void { $this->client->request(Request::METHOD_GET, '/backoffice/settings'); $form = $this->client->getCrawler()->filter('form[action*="/backoffice/settings/password"]')->form([ - 'change_user_password_form[currentPassword]' => TestFixtures::PASSWORD, - 'change_user_password_form[plainPassword][first]' => 'NewPass123!', - 'change_user_password_form[plainPassword][second]' => 'SomethingElse!', + 'change_user_password_form[currentPassword]' => $currentPassword, + 'change_user_password_form[plainPassword][first]' => $first, + 'change_user_password_form[plainPassword][second]' => $second, ]); $this->client->submit($form); @@ -140,7 +104,6 @@ final class SettingsControllerTest extends WebTestCase $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)); } @@ -157,9 +120,8 @@ final class SettingsControllerTest extends WebTestCase self::assertEmailCount(1); $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'); - $this->assertInstanceOf(User::class, $user); $this->assertFalse($user->isVerified); // User stays logged in @@ -179,7 +141,7 @@ final class SettingsControllerTest extends WebTestCase self::assertEmailCount(0); $this->entityManager->clear(); - $this->assertInstanceOf(User::class, $this->getUserByEmail('test@example.org')); + $this->getUserByEmail('test@example.org'); } public function testResendConfirmationEmailSendsEmail(): void @@ -200,8 +162,8 @@ final class SettingsControllerTest extends WebTestCase $token = $this->getCsrfTokenFromSettings('/backoffice/settings/resend-confirmation'); $user = $this->getUserByEmail('test@example.org'); - $this->assertInstanceOf(User::class, $user); $user->isVerified = true; + $this->entityManager->flush(); $crawler = $this->client->request(Request::METHOD_GET, '/backoffice/settings'); @@ -242,7 +204,6 @@ final class SettingsControllerTest extends WebTestCase public function testChangePasswordInvalidatesResetPasswordRequests(): void { $user = $this->getUserByEmail('test@example.org'); - $this->assertInstanceOf(User::class, $user); $this->createResetPasswordRequest($user); $this->client->request(Request::METHOD_GET, '/backoffice/settings'); @@ -257,14 +218,12 @@ final class SettingsControllerTest extends WebTestCase $this->entityManager->clear(); $user = $this->getUserByEmail('test@example.org'); - $this->assertInstanceOf(User::class, $user); $this->assertSame(0, $this->entityManager->getRepository(ResetPasswordRequest::class)->count(['user' => $user])); } public function testChangeEmailInvalidatesResetPasswordRequests(): void { $user = $this->getUserByEmail('test@example.org'); - $this->assertInstanceOf(User::class, $user); $this->createResetPasswordRequest($user); $this->client->request(Request::METHOD_GET, '/backoffice/settings'); @@ -277,7 +236,6 @@ final class SettingsControllerTest extends WebTestCase $this->entityManager->clear(); $user = $this->getUserByEmail('new-address@example.org'); - $this->assertInstanceOf(User::class, $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'); $this->entityManager->clear(); - $this->assertInstanceOf(User::class, $this->getUserByEmail('test@example.org')); + $this->getUserByEmail('test@example.org'); } public function testDeleteAccountRemovesSoleOwnerSeasonsAndKeepsSharedSeasons(): void @@ -309,7 +267,7 @@ final class SettingsControllerTest extends WebTestCase self::assertResponseRedirects(); $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 $this->assertNotInstanceOf(Season::class, $this->entityManager->getRepository(Season::class)->findOneBy(['seasonCode' => 'doomd'])); @@ -340,7 +298,7 @@ final class SettingsControllerTest extends WebTestCase self::assertResponseRedirects(); $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) { $season = $this->entityManager->getRepository(Season::class)->findOneBy(['seasonCode' => $seasonCode]); @@ -378,7 +336,6 @@ final class SettingsControllerTest extends WebTestCase public function testDownloadDataRequiresVerifiedEmail(): void { $user = $this->getUserByEmail('test@example.org'); - $this->assertInstanceOf(User::class, $user); $this->assertFalse($user->isVerified); $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 { $user = $this->getUserByEmail($email); - $this->assertInstanceOf(User::class, $user); $user->isVerified = true; + $this->entityManager->flush(); } } diff --git a/tests/Controller/EliminationControllerTest.php b/tests/Controller/EliminationControllerTest.php new file mode 100644 index 0000000..6cbac32 --- /dev/null +++ b/tests/Controller/EliminationControllerTest.php @@ -0,0 +1,86 @@ +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)); + } +} diff --git a/tests/Controller/LoginControllerTest.php b/tests/Controller/LoginControllerTest.php new file mode 100644 index 0000000..43ef7c7 --- /dev/null +++ b/tests/Controller/LoginControllerTest.php @@ -0,0 +1,53 @@ +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(); + } +} diff --git a/tests/Controller/QuizControllerTest.php b/tests/Controller/QuizControllerTest.php new file mode 100644 index 0000000..a5f88ef --- /dev/null +++ b/tests/Controller/QuizControllerTest.php @@ -0,0 +1,209 @@ +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'); + } +} diff --git a/tests/Controller/RegistrationControllerTest.php b/tests/Controller/RegistrationControllerTest.php new file mode 100644 index 0000000..ce14cd6 --- /dev/null +++ b/tests/Controller/RegistrationControllerTest.php @@ -0,0 +1,90 @@ +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'); + } +} diff --git a/tests/Controller/ResetPasswordControllerTest.php b/tests/Controller/ResetPasswordControllerTest.php index dd49031..363544a 100644 --- a/tests/Controller/ResetPasswordControllerTest.php +++ b/tests/Controller/ResetPasswordControllerTest.php @@ -4,10 +4,8 @@ declare(strict_types=1); namespace Tvdt\Tests\Controller; -use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\Attributes\CoversClass; -use Symfony\Bundle\FrameworkBundle\KernelBrowser; -use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface; @@ -15,18 +13,8 @@ use Tvdt\Controller\ResetPasswordController; use Tvdt\Entity\User; #[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 { $this->client->request(Request::METHOD_GET, '/reset-password'); @@ -35,22 +23,19 @@ final class ResetPasswordControllerTest extends WebTestCase $this->assertSelectorExists('form'); } - public function testRequestWithUnknownEmailRedirectsToCheckEmail(): void + /** @return iterable */ + public static function emailProvider(): iterable { - $this->client->request(Request::METHOD_GET, '/reset-password'); - $form = $this->client->getCrawler()->filter('form')->form([ - 'reset_password_request_form[email]' => 'unknown@example.org', - ]); - $this->client->submit($form); - - $this->assertResponseRedirects('/reset-password/check-email'); + yield 'unknown email' => ['unknown@example.org']; + yield 'known email' => ['test@example.org']; } - public function testRequestWithKnownEmailRedirectsToCheckEmail(): void + #[DataProvider('emailProvider')] + public function testRequestRedirectsToCheckEmail(string $email): void { $this->client->request(Request::METHOD_GET, '/reset-password'); $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); diff --git a/tests/Controller/WellKnownControllerTest.php b/tests/Controller/WellKnownControllerTest.php index f114281..3d27b51 100644 --- a/tests/Controller/WellKnownControllerTest.php +++ b/tests/Controller/WellKnownControllerTest.php @@ -6,21 +6,12 @@ namespace Tvdt\Tests\Controller; use PHPUnit\Framework\Attributes\CoversClass; use Safe\DateTimeImmutable; -use Symfony\Bundle\FrameworkBundle\KernelBrowser; -use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Request; use Tvdt\Controller\WellKnownController; #[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 { $this->client->request(Request::METHOD_GET, '/.well-known/change-password'); diff --git a/tests/Entity/BankQuestionTest.php b/tests/Entity/BankQuestionTest.php new file mode 100644 index 0000000..b26a989 --- /dev/null +++ b/tests/Entity/BankQuestionTest.php @@ -0,0 +1,100 @@ +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); + } +} diff --git a/tests/Entity/EliminationTest.php b/tests/Entity/EliminationTest.php new file mode 100644 index 0000000..848bb79 --- /dev/null +++ b/tests/Entity/EliminationTest.php @@ -0,0 +1,89 @@ +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 $colours + * + * @return array + */ + private function colours(array $colours): array + { + return $colours; + } + + /** + * @param array $parameters + * + * @return InputBag + */ + private function inputBag(array $parameters): InputBag + { + /** @var InputBag $inputBag */ + $inputBag = new InputBag($parameters); + + return $inputBag; + } +} diff --git a/tests/Helpers/Base64Test.php b/tests/Helpers/Base64Test.php index 114c8f4..6fefbee 100644 --- a/tests/Helpers/Base64Test.php +++ b/tests/Helpers/Base64Test.php @@ -4,28 +4,34 @@ declare(strict_types=1); namespace Tvdt\Tests\Helpers; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Safe\Exceptions\UrlException; use Tvdt\Helpers\Base64; +#[CoversClass(Base64::class)] final class Base64Test extends TestCase { - public function testBase64UrlEncode(): void + /** @return iterable */ + public static function pairProvider(): iterable { - $this->assertSame('TWFyaWpu', Base64::base64UrlEncode('Marijn')); - $this->assertSame('UGhpbGluZQ', Base64::base64UrlEncode('Philine')); - - $this->assertSame('_g', Base64::base64UrlEncode(\chr(254))); - $this->assertSame('-g', Base64::base64UrlEncode(\chr(250))); + yield 'Marijn' => ['Marijn', 'TWFyaWpu']; + yield 'Philine' => ['Philine', 'UGhpbGluZQ']; + yield 'byte 254' => [\chr(254), '_g']; + yield 'byte 250' => [\chr(250), '-g']; } - public function testBase64UrlDecode(): void + #[DataProvider('pairProvider')] + public function testBase64UrlEncode(string $decoded, string $encoded): void { - $this->assertSame('Marijn', Base64::base64UrlDecode('TWFyaWpu')); - $this->assertSame('Philine', Base64::base64UrlDecode('UGhpbGluZQ')); + $this->assertSame($encoded, Base64::base64UrlEncode($decoded)); + } - $this->assertSame(\chr(254), Base64::base64UrlDecode('_g')); - $this->assertSame(\chr(250), Base64::base64UrlDecode('-g')); + #[DataProvider('pairProvider')] + public function testBase64UrlDecode(string $decoded, string $encoded): void + { + $this->assertSame($decoded, Base64::base64UrlDecode($encoded)); } public function testBase64UrlDecodeCanHandlePadding(): void diff --git a/tests/Helpers/FilenameSanitizerTest.php b/tests/Helpers/FilenameSanitizerTest.php index c1553c9..f4a581a 100644 --- a/tests/Helpers/FilenameSanitizerTest.php +++ b/tests/Helpers/FilenameSanitizerTest.php @@ -4,41 +4,31 @@ declare(strict_types=1); namespace Tvdt\Tests\Helpers; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Tvdt\Helpers\FilenameSanitizer; +#[CoversClass(FilenameSanitizer::class)] final class FilenameSanitizerTest extends TestCase { - public function testReplacesSpacesWithDashes(): void + /** @return iterable */ + 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