Files
xamxam/tests/phpunit/ThesisEditValidationTest.php
Pontoporeia 99125cc8e3 Add autosave draft system for partage form with HTMX-based session persistence
- New fragment endpoint POST/GET /partage/fragments/draft.php:
  saves all form fields to PHP session, excludes file/csrf/slug fields
  GET returns JSON for JS hydration on page load
  rotates both global CSRF and share CSRF tokens in sync

- form.php accepts optional $formExtraAttrs and $showAutosaveStatus:
  allows injecting HTMX attributes and 'Brouillon enregistré' indicator

- renderShareLinkForm adds hx-post with change/input debounce trigger,
  loads autosave-handler.js, hydrate fields from draft on page load

- Draft cleared on successful form submission in handleShareLinkSubmission

- autosave-handler.js now also updates share_link_token hidden input
  when rotating CSRF token (partage form uses both csrf_token and share_link_token)

- Added .autosave-status CSS to form.css (was admin.css-only)

- Updated fragment routing to accept GET requests (needed for draft hydration)
2026-06-11 11:04:49 +02:00

197 lines
7.0 KiB
PHP

<?php
use PHPUnit\Framework\TestCase;
/**
* ThesisEditValidationTest — Tests for ThesisEditController validation helpers
* (collectJuryMembers, handleWebsiteUrl, load).
*/
class ThesisEditValidationTest extends TestCase
{
private PDO $pdo;
private ThesisEditController $ctrl;
protected function setUp(): void
{
TestDatabase::resetData();
$this->pdo = TestDatabase::getPDO();
$db = TestDatabase::getInstance();
$this->ctrl = new ThesisEditController($db);
}
// ── load() ───────────────────────────────────────────────────────────────
public function testLoadReturnsDataForKnownId(): void
{
[$authorId, $thesisId] = TestDatabase::seedBasicThesis('Load Test', 'Author Name', 2024);
$data = $this->ctrl->load($thesisId);
$this->assertIsArray($data);
$this->assertArrayHasKey('thesis', $data);
$this->assertSame('Load Test', $data['thesis']['title']);
$this->assertArrayHasKey('orientations', $data);
$this->assertArrayHasKey('formatTypes', $data);
}
public function testLoadThrowsOnUnknownId(): void
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('TFE non trouvé');
$this->ctrl->load(9999);
}
public function testLoadThrowsOnInvalidId(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('ID invalide');
$this->ctrl->load(0);
}
public function testLoadThrowsOnNegativeId(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('ID invalide');
$this->ctrl->load(-5);
}
// ── collectJuryMembers (private, test via reflection) ─────────────────────
private function collectJuryMembers(array $post): array
{
$ref = new ReflectionMethod(ThesisEditController::class, 'collectJuryMembers');
return $ref->invoke($this->ctrl, $post);
}
public function testCollectJuryMembersEmptyInput(): void
{
$members = $this->collectJuryMembers([]);
$this->assertIsArray($members);
$this->assertEmpty($members);
}
public function testCollectJuryMembersSinglePromoteur(): void
{
$post = ['jury_promoteur' => ['John Smith']];
$members = $this->collectJuryMembers($post);
$this->assertCount(1, $members);
$this->assertSame('promoteur', $members[0]['role']);
$this->assertSame('John Smith', $members[0]['name']);
$this->assertSame(0, $members[0]['is_external']);
$this->assertSame(0, $members[0]['is_ulb']);
}
public function testCollectJuryMembersPromoteurUlb(): void
{
$post = ['jury_promoteur_ulb_name' => ['ULB Prof']];
$members = $this->collectJuryMembers($post);
$this->assertCount(1, $members);
$this->assertSame('promoteur', $members[0]['role']);
$this->assertSame(1, $members[0]['is_external']);
$this->assertSame(1, $members[0]['is_ulb']);
}
public function testCollectJuryMembersLecteurs(): void
{
$post = [
'jury_lecteur_interne' => ['Int One', 'Int Two'],
'jury_lecteur_externe' => ['Ext One'],
];
$members = $this->collectJuryMembers($post);
$this->assertCount(3, $members);
$internes = array_filter($members, fn ($m) => $m['is_external'] === 0 && $m['role'] === 'lecteur');
$externes = array_filter($members, fn ($m) => $m['is_external'] === 1 && $m['role'] === 'lecteur');
$this->assertCount(2, $internes);
$this->assertCount(1, $externes);
}
public function testCollectJuryMembersDeduplicatesEmptyStrings(): void
{
$post = [
'jury_promoteur' => ['John', '', ' ', 'Jane'],
];
$members = $this->collectJuryMembers($post);
$names = array_column($members, 'name');
$this->assertCount(2, $names);
$this->assertContains('John', $names);
$this->assertContains('Jane', $names);
}
public function testCollectJuryMembersScalarPromoteurAccepted(): void
{
// Accepts scalar instead of array for promoteur fields
$post = ['jury_promoteur' => 'Single Promoter'];
$members = $this->collectJuryMembers($post);
$this->assertCount(1, $members);
$this->assertSame('Single Promoter', $members[0]['name']);
}
// ── handleWebsiteUrl (private, test via reflection) ───────────────────────
private function invokeHandleWebsiteUrl(int $thesisId, array $post): void
{
$ref = new ReflectionMethod(ThesisEditController::class, 'handleWebsiteUrl');
$ref->invoke($this->ctrl, $thesisId, $post);
}
public function testHandleWebsiteUrlStoresValidUrl(): void
{
[$authorId, $thesisId] = TestDatabase::seedBasicThesis('Website Test', 'Author', 2024);
$post = ['website_url' => 'https://example.com', 'website_label' => 'My Site'];
$this->invokeHandleWebsiteUrl($thesisId, $post);
$pdo = TestDatabase::getPDO();
$files = $pdo->query("SELECT * FROM thesis_files WHERE thesis_id = $thesisId AND file_type = 'website'")->fetchAll();
$this->assertCount(1, $files);
$this->assertSame('https://example.com', $files[0]['file_path']);
$this->assertSame('My Site', $files[0]['display_label']);
}
public function testHandleWebsiteUrlSkipsInvalidUrl(): void
{
[$authorId, $thesisId] = TestDatabase::seedBasicThesis('Bad URL Test', 'Author', 2024);
$post = ['website_url' => 'not-a-url'];
$this->invokeHandleWebsiteUrl($thesisId, $post);
$pdo = TestDatabase::getPDO();
$count = $pdo->query("SELECT COUNT(*) FROM thesis_files WHERE thesis_id = $thesisId AND file_type = 'website'")->fetchColumn();
$this->assertSame(0, (int)$count);
}
public function testHandleWebsiteUrlSkipsEmptyUrl(): void
{
[$authorId, $thesisId] = TestDatabase::seedBasicThesis('Empty URL', 'Author', 2024);
$post = ['website_url' => ''];
$this->invokeHandleWebsiteUrl($thesisId, $post);
$pdo = TestDatabase::getPDO();
$count = $pdo->query("SELECT COUNT(*) FROM thesis_files WHERE thesis_id = $thesisId AND file_type = 'website'")->fetchColumn();
$this->assertSame(0, (int)$count);
}
public function testHandleWebsiteUrlNormalisesHttp(): void
{
[$authorId, $thesisId] = TestDatabase::seedBasicThesis('HTTP URL Test', 'Author', 2024);
$post = ['website_url' => 'https://example.com/path'];
$this->invokeHandleWebsiteUrl($thesisId, $post);
$pdo = TestDatabase::getPDO();
$file = $pdo->query("SELECT * FROM thesis_files WHERE thesis_id = $thesisId AND file_type = 'website'")->fetch();
$this->assertStringContainsString('example.com/path', $file['file_name']);
}
}