mirror of
https://github.com/MarijnDoeve/TijdVoorDeTest.git
synced 2026-07-11 12:28:23 +02:00
test: expand coverage, dedupe data-driven tests, extract shared WebTestCase base (#201)
* 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 * test: address PR review feedback - Scope AbstractControllerWebTestCase::getCandidate/getQuizByName by season code (both Candidate and Quiz are only unique per season, not system-wide) and add a CandidateRepository regression test guarding against same-named candidates in different seasons - Add missing entityManager->clear() before verifying DB state after a POST in PrepareEliminationControllerTest and QuestionBankControllerTest - Add non-owner denial tests for BackofficeController::exportQuiz and QuizQuestionController::edit/reorder, which had IsGranted checks with no test coverage * ci: publish PHPUnit coverage to GitHub code coverage - Generate a Cobertura report alongside the existing JUnit report and upload it with actions/upload-code-coverage so coverage shows up on PRs and the default branch via GitHub's code coverage feature - Add a step to copy both reports out of the php container before publishing them, since var/ is a Docker volume (see the Dockerfile's VOLUME /app/var/) and isn't bind-mounted to the runner — this also fixes the existing JUnit report publishing step, which was silently looking at a path that never had contets on the runner * ci: fix coverage report paths and tolerate Code Quality not yet enabled - Write PHPUnit's JUnit and Cobertura reports to reports/ instead of var/, since var/ is a Docker volume (Dockerfile's VOLUME /app/var/) that isn't bind-mounted to the runner - reports written there never reached the host, which is also why the prior junit.xml publish step had nothing to read. reports/ is a plain path under the project's bind mount, so no extra copy-out step is needed - Set fail-on-error: false on the coverage upload step: it 404s until "Code Quality" is turned on for the repo under Settings > Code security > Code quality, a one-time manual step
This commit is contained in:
@@ -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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user