Files
TijdVoorDeTest/tests/Controller/Backoffice/QuizQuestionControllerTest.php
T
Marijn b1f68425cd 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
2026-07-10 09:09:52 +02:00

158 lines
6.0 KiB
PHP

<?php
declare(strict_types=1);
namespace Tvdt\Tests\Controller\Backoffice;
use PHPUnit\Framework\Attributes\CoversClass;
use Symfony\Component\HttpFoundation\Request;
use Tvdt\Controller\Backoffice\QuizQuestionController;
use Tvdt\Entity\Question;
use Tvdt\Tests\Controller\AbstractControllerWebTestCase;
#[CoversClass(QuizQuestionController::class)]
final class QuizQuestionControllerTest extends AbstractControllerWebTestCase
{
public function testEditPreservesAnswerOrdering(): void
{
$this->loginAs('krtek-admin@example.org');
$quiz = $this->getQuizByName('Quiz 2');
$question = null;
foreach ($quiz->questions as $q) {
if ('Is de Krtek een man of een vrouw?' === $q->question) {
$question = $q;
break;
}
}
$this->assertInstanceOf(Question::class, $question);
$answers = $question->answers->toArray();
$this->assertCount(2, $answers);
$firstText = $answers[0]->text;
$secondText = $answers[1]->text;
$url = \sprintf(
'/backoffice/season/krtek/quiz/%s/question/%s/edit',
$quiz->id,
$question->id,
);
$crawler = $this->client->request(Request::METHOD_GET, $url);
$this->assertResponseIsSuccessful();
$token = (string) $crawler->filter('input[name="question_form[_token]"]')->attr('value');
// Submit with ordering values that invert which answer appears first on reload.
// The answer currently at index 0 ($firstText) gets ordering=7,
// the one at index 1 ($secondText) gets ordering=3.
// @OrderBy(['ordering' => 'ASC']) on Question::$answers will return
// $secondText (3) before $firstText (7) after flush+clear.
$this->client->request(Request::METHOD_POST, $url, [
'question_form' => [
'question' => $question->question,
'answers' => [
0 => ['text' => $firstText, 'ordering' => '7'],
1 => ['text' => $secondText, 'ordering' => '3'],
],
'_token' => $token,
],
]);
$this->assertResponseRedirects();
$this->entityManager->clear();
$quiz = $this->getQuizByName('Quiz 2');
$reloadedQuestion = null;
foreach ($quiz->questions as $q) {
if ('Is de Krtek een man of een vrouw?' === $q->question) {
$reloadedQuestion = $q;
break;
}
}
$this->assertInstanceOf(Question::class, $reloadedQuestion);
$reloadedAnswers = $reloadedQuestion->answers->toArray();
$this->assertSame(3, $reloadedAnswers[0]->ordering);
$this->assertSame($secondText, $reloadedAnswers[0]->text);
$this->assertSame(7, $reloadedAnswers[1]->ordering);
$this->assertSame($firstText, $reloadedAnswers[1]->text);
}
public function testReorderQuestionsWithinQuiz(): void
{
$this->loginAs('krtek-admin@example.org');
$quiz = $this->getQuizByName('Quiz 2');
$originalQuestions = $quiz->questions->toArray();
$this->assertGreaterThanOrEqual(3, \count($originalQuestions));
$originalFirstId = (string) $originalQuestions[0]->id;
$originalLastId = (string) $originalQuestions[\count($originalQuestions) - 1]->id;
$overviewUrl = \sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz->id);
$crawler = $this->client->request(Request::METHOD_GET, $overviewUrl);
$this->assertResponseIsSuccessful();
$csrfToken = $crawler->filter('[data-bo--question-list-csrf-value]')->attr('data-bo--question-list-csrf-value');
$this->assertNotEmpty($csrfToken);
$reversedIds = array_reverse(array_map(static fn (Question $q): string => (string) $q->id, $originalQuestions));
$reorderUrl = \sprintf('/backoffice/season/krtek/quiz/%s/questions/reorder', $quiz->id);
$this->client->request(Request::METHOD_POST, $reorderUrl, [
'_token' => $csrfToken,
'ordering' => $reversedIds,
]);
$this->assertResponseStatusCodeSame(204);
$this->entityManager->clear();
$quiz = $this->getQuizByName('Quiz 2');
$reorderedQuestions = $quiz->questions->toArray();
$this->assertSame($originalLastId, (string) $reorderedQuestions[0]->id);
$this->assertSame($originalFirstId, (string) $reorderedQuestions[\count($reorderedQuestions) - 1]->id);
}
public function testEditIsDeniedForNonOwner(): void
{
$this->loginAs('test@example.org');
$quiz = $this->getQuizByName('Quiz 2');
$question = $quiz->questions->first();
$this->assertInstanceOf(Question::class, $question);
$this->client->request(Request::METHOD_GET, \sprintf(
'/backoffice/season/krtek/quiz/%s/question/%s/edit',
$quiz->id,
$question->id,
));
self::assertResponseStatusCodeSame(403);
}
public function testReorderIsDeniedForNonOwner(): void
{
$quiz = $this->getQuizByName('Quiz 2');
// Scrape a valid CSRF token as the owner before switching to the non-owner account,
// since the token is bound to the session, not the logged-in user.
$this->loginAs('krtek-admin@example.org');
$crawler = $this->client->request(Request::METHOD_GET, \sprintf('/backoffice/season/krtek/quiz/%s/overview', $quiz->id));
self::assertResponseIsSuccessful();
$csrfToken = $crawler->filter('[data-bo--question-list-csrf-value]')->attr('data-bo--question-list-csrf-value');
$this->assertNotEmpty($csrfToken);
$this->loginAs('test@example.org');
$this->client->request(Request::METHOD_POST, \sprintf('/backoffice/season/krtek/quiz/%s/questions/reorder', $quiz->id), [
'_token' => $csrfToken,
'ordering' => array_map(static fn (Question $q): string => (string) $q->id, $quiz->questions->toArray()),
]);
self::assertResponseStatusCodeSame(403);
}
}