Files
xamxam/tests/phpunit/ThesisCreateValidationTest.php

341 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
use PHPUnit\Framework\TestCase;
/**
* ThesisCreateValidationTest — Tests for ThesisCreateController::validateAndSanitise()
* and related validation logic.
*
* validateAndSanitise() is private; we test via a thin subclass or by driving
* the submit() method with controlled inputs. For pure validation tests,
* we use reflection.
*/
class ThesisCreateValidationTest extends TestCase
{
private PDO $pdo;
protected function setUp(): void
{
TestDatabase::resetData();
$this->pdo = TestDatabase::getPDO();
}
/**
* Invoke the private validateAndSanitise() method via reflection.
*/
private function validate(array $post, bool $adminMode = false): array
{
$ref = new ReflectionMethod(ThesisCreateController::class, 'validateAndSanitise');
$db = TestDatabase::getInstance();
$ctrl = new ThesisCreateController($db);
return $ref->invoke($ctrl, $post, $adminMode);
}
/**
* Build minimal valid POST data for a submission.
*/
private function validPost(): array
{
return [
'auteurice' => 'John Doe',
'année' => '2024',
'orientation' => '1',
'ap' => '1',
'finality' => '1',
'titre' => 'My Thesis Title',
'synopsis' => 'A compelling synopsis.',
'jury_promoteur' => ['Promoteur One'],
'jury_lecteur_interne' => ['Lecteur Interne'],
'jury_lecteur_externe' => ['Lecteur Externe'],
'tag' => ['art', 'design', 'research'],
'languages' => ['1'],
'formats' => ['2'],
'access_type_id' => '1',
'license_id' => '1',
'objet' => 'tfe',
];
}
// ── Valid submission ─────────────────────────────────────────────────────
public function testValidSubmissionReturnsCleanedData(): void
{
$data = $this->validate($this->validPost());
$this->assertSame('My Thesis Title', $data['titre']);
$this->assertSame(2024, $data['annee']);
$this->assertSame('John Doe', $data['authorNames'][0]);
$this->assertNotEmpty($data['juryMembers']);
$this->assertCount(3, $data['keywords']);
}
// ── Missing required fields ──────────────────────────────────────────────
public function testMissingTitleThrowsException(): void
{
$post = $this->validPost();
$post['titre'] = '';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Titre du TFE');
$this->validate($post);
}
public function testMissingAuthorsThrowsException(): void
{
$post = $this->validPost();
$post['auteurice'] = '';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Auteur');
$this->validate($post);
}
public function testMissingSynopsisThrowsException(): void
{
$post = $this->validPost();
$post['synopsis'] = '';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Synopsis');
$this->validate($post);
}
public function testMissingOrientationInNonAdminModeThrowsException(): void
{
$post = $this->validPost();
$post['orientation'] = '';
$this->expectException(Exception::class);
$this->expectExceptionMessage('orientation');
$this->validate($post);
}
public function testMissingAPProgramInNonAdminModeThrowsException(): void
{
$post = $this->validPost();
$post['ap'] = '';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Atelier Pratique');
$this->validate($post);
}
public function testMissingFinalityInNonAdminModeThrowsException(): void
{
$post = $this->validPost();
$post['finality'] = '';
$this->expectException(Exception::class);
$this->expectExceptionMessage('finalité');
$this->validate($post);
}
// ── Invalid year ─────────────────────────────────────────────────────────
public function testInvalidYearFormatRejected(): void
{
$post = $this->validPost();
$post['année'] = 'not-a-year';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Année invalide');
$this->validate($post);
}
public function testYearZeroRejected(): void
{
$post = $this->validPost();
$post['année'] = '0';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Année invalide');
$this->validate($post);
}
public function testYearBefore2000Rejected(): void
{
$post = $this->validPost();
$post['année'] = '1999';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Année invalide');
$this->validate($post);
}
public function testFarFutureYearRejected(): void
{
$post = $this->validPost();
$post['année'] = (string)((int)date('Y') + 5);
$this->expectException(Exception::class);
$this->expectExceptionMessage('Année invalide');
$this->validate($post);
}
public function testCurrentYearAccepted(): void
{
$post = $this->validPost();
$post['année'] = (string)(int)date('Y');
$data = $this->validate($post);
$this->assertSame((int)date('Y'), $data['annee']);
}
// ── Malformed URL ────────────────────────────────────────────────────────
public function testMalformedUrlRejected(): void
{
$post = $this->validPost();
$post['lien'] = 'not-a-valid-url';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Lien URL invalide');
$this->validate($post);
}
public function testValidUrlAccepted(): void
{
$post = $this->validPost();
$post['lien'] = 'https://example.com';
$data = $this->validate($post);
$this->assertSame('https://example.com', $data['lien']);
}
// ── Tag deduplication ────────────────────────────────────────────────────
public function testDuplicateTagsAreDeduplicated(): void
{
$post = $this->validPost();
$post['tag'] = ['art', 'Art', 'ART', 'design', 'research', 'philosophy'];
$data = $this->validate($post);
// Tags are lowercased and deduplicated: art×3 + design + research + philosophy = 4
$this->assertCount(4, $data['keywords']);
$expected = ['art', 'design', 'philosophy', 'research'];
sort($data['keywords']);
$this->assertSame($expected, $data['keywords']);
}
public function testMaxTenKeywordsEnforced(): void
{
$post = $this->validPost();
$post['tag'] = range(1, 12);
$data = $this->validate($post);
$this->assertCount(10, $data['keywords']);
}
// ── XSS escaping ─────────────────────────────────────────────────────────
public function testXssPayloadStrippedFromTitle(): void
{
$post = $this->validPost();
$post['titre'] = '<script>alert("xss")</script>Clean Title';
$data = $this->validate($post);
$this->assertStringNotContainsString('<script>', $data['titre']);
$this->assertStringContainsString('Clean Title', $data['titre']);
}
public function testHtmlInSynopsisStripped(): void
{
$post = $this->validPost();
$post['synopsis'] = '<b>Bold</b> synopsis <img src=x onerror=alert(1)>';
$data = $this->validate($post);
$this->assertStringNotContainsString('<b>', $data['synopsis']);
$this->assertStringNotContainsString('<img', $data['synopsis']);
$this->assertStringContainsString('Bold synopsis', $data['synopsis']);
}
// ── Author processing ────────────────────────────────────────────────────
public function testMultipleAuthorsAreSorted(): void
{
$post = $this->validPost();
$post['auteurice'] = 'Zoe, Alice, Bob';
$data = $this->validate($post);
$this->assertSame(['Alice', 'Bob', 'Zoe'], $data['authorNames']);
}
// ── Jury validation ──────────────────────────────────────────────────────
public function testMissingPromoteurInNonAdminModeThrowsException(): void
{
$post = $this->validPost();
$post['jury_promoteur'] = [];
$this->expectException(Exception::class);
$this->expectExceptionMessage('promoteur');
$this->validate($post);
}
public function testMissingLecteurInterneInNonAdminModeThrowsException(): void
{
$post = $this->validPost();
$post['jury_lecteur_interne'] = [];
$this->expectException(Exception::class);
$this->expectExceptionMessage('lecteur·ice interne');
$this->validate($post);
}
// ── Language validation ──────────────────────────────────────────────────
public function testMissingLanguagesInNonAdminModeThrowsException(): void
{
$post = $this->validPost();
unset($post['languages']);
$this->expectException(Exception::class);
$this->expectExceptionMessage('langue');
$this->validate($post);
}
// ── Format validation ────────────────────────────────────────────────────
public function testMissingFormatsInNonAdminModeThrowsException(): void
{
$post = $this->validPost();
unset($post['formats']);
$this->expectException(Exception::class);
$this->expectExceptionMessage('format');
$this->validate($post);
}
// ── License validation ───────────────────────────────────────────────────
public function testMissingLicenseWithLibreAccessThrowsException(): void
{
$post = $this->validPost();
$post['access_type_id'] = '1';
$post['license_id'] = '';
$this->expectException(Exception::class);
$this->expectExceptionMessage('licence');
$this->validate($post);
}
}