Files
xamxam/tests/phpunit/ThesisEditValidationTest.php
T
Pontoporeia 3cecee10c9 fix: prevent file deletion on relink + restore button now visible + OOB-style in-place update
Two critical fixes:

1. Relink flow no longer destroys/recreates FilePond instances:
   The relink (XamxamRelinkFile) and PeerTube relink (XamxamRelinkPeerTube)
   previously refreshed the entire fichiers fragment via HTMX after
   pond.addFile(). This triggered destroyFilePondsIn on ALL pools, which
   could fire server.remove callbacks and move existing files to corbeille.
   Now just closes the modal — the file is already added to the pool in-place,
   and syncOrderInput creates the hidden form input.

2. Cleanup page « Corbeille (restaurable) » now actually shows files:
   _cleanup-stats-data.php previously classified trash files by checking if
   the thesis_files DB row still existed. But both deleteThesisFileToTrash
   and FilepondHandler::handleRemove DELETE the DB row. So ALL trash files
   appeared as `stale` (not restorable). Now uses the JSON sidecar file
   presence as the classification criterion — if the sidecar exists and is
   recent, the file is restorable regardless of DB row state.

Also removed unused DB query from _cleanup-stats-data.php.
2026-07-10 16:29:04 +02:00

313 lines
13 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']);
}
// ── handleWebsiteUrl regression: existing rows preserved (not deleted-then-recreated) ─
public function testHandleWebsiteUrlPreservesExistingRowWhenUrlUnchanged(): void
{
[$authorId, $thesisId] = TestDatabase::seedBasicThesis('Keep Site', 'Author', 2024);
$pdo = TestDatabase::getPDO();
// Seed an existing website row
$pdo->prepare(
"INSERT INTO thesis_files (thesis_id, file_type, file_path, file_name, file_size, mime_type, display_label)
VALUES (?, 'website', 'https://old.example.com', 'old.example.com', 0, 'text/html', 'Old Label')"
)->execute([$thesisId]);
$oldId = (int)$pdo->lastInsertId();
// Submit the SAME URL (no change intended)
$post = ['website_url' => 'https://old.example.com'];
$this->invokeHandleWebsiteUrl($thesisId, $post);
// The row should still exist with the same ID and (crucially) preserved label
$row = $pdo->query("SELECT * FROM thesis_files WHERE thesis_id = $thesisId AND file_type = 'website'")->fetch();
$this->assertNotFalse($row, 'Website row should still exist');
$this->assertSame($oldId, (int)$row['id'], 'Row ID should be preserved (not delete+reinsert)');
$this->assertSame('Old Label', $row['display_label'], 'Label should be preserved when no new label is given');
$this->assertSame('https://old.example.com', $row['file_path']);
}
public function testHandleWebsiteUrlPreservesLabelWhenNotProvided(): void
{
[$authorId, $thesisId] = TestDatabase::seedBasicThesis('Label Preserve', 'Author', 2024);
$pdo = TestDatabase::getPDO();
$pdo->prepare(
"INSERT INTO thesis_files (thesis_id, file_type, file_path, file_name, file_size, mime_type, display_label)
VALUES (?, 'website', 'https://example.com', 'example.com', 0, 'text/html', 'My Custom Label')"
)->execute([$thesisId]);
// Submit URL without a label
$post = ['website_url' => 'https://example.com', 'website_label' => ''];
$this->invokeHandleWebsiteUrl($thesisId, $post);
$row = $pdo->query("SELECT * FROM thesis_files WHERE thesis_id = $thesisId AND file_type = 'website'")->fetch();
$this->assertSame('My Custom Label', $row['display_label'], 'Existing label should survive when no new label is provided');
}
public function testHandleWebsiteUrlUpdatesLabelWhenProvided(): void
{
[$authorId, $thesisId] = TestDatabase::seedBasicThesis('Label Update', 'Author', 2024);
$pdo = TestDatabase::getPDO();
$pdo->prepare(
"INSERT INTO thesis_files (thesis_id, file_type, file_path, file_name, file_size, mime_type, display_label)
VALUES (?, 'website', 'https://example.com', 'example.com', 0, 'text/html', 'Old Label')"
)->execute([$thesisId]);
$post = ['website_url' => 'https://example.com', 'website_label' => 'New Label'];
$this->invokeHandleWebsiteUrl($thesisId, $post);
$row = $pdo->query("SELECT * FROM thesis_files WHERE thesis_id = $thesisId AND file_type = 'website'")->fetch();
$this->assertSame('New Label', $row['display_label']);
}
public function testHandleWebsiteUrlDeletesRowWhenUrlExplicitlyCleared(): void
{
[$authorId, $thesisId] = TestDatabase::seedBasicThesis('Clear Site', 'Author', 2024);
$pdo = TestDatabase::getPDO();
$pdo->prepare(
"INSERT INTO thesis_files (thesis_id, file_type, file_path, file_name, file_size, mime_type, display_label)
VALUES (?, 'website', 'https://example.com', 'example.com', 0, 'text/html', 'Label')"
)->execute([$thesisId]);
// Explicitly clear the URL
$post = ['website_url' => ''];
$this->invokeHandleWebsiteUrl($thesisId, $post);
$count = $pdo->query("SELECT COUNT(*) FROM thesis_files WHERE thesis_id = $thesisId AND file_type = 'website'")->fetchColumn();
$this->assertSame(0, (int)$count, 'Website row should be deleted when URL is explicitly cleared');
}
public function testHandleWebsiteUrlUpdatesUrlWhenChanged(): void
{
[$authorId, $thesisId] = TestDatabase::seedBasicThesis('Change URL', 'Author', 2024);
$pdo = TestDatabase::getPDO();
$pdo->prepare(
"INSERT INTO thesis_files (thesis_id, file_type, file_path, file_name, file_size, mime_type, display_label)
VALUES (?, 'website', 'https://old.example.com', 'old.example.com', 0, 'text/html', 'Label')"
)->execute([$thesisId]);
$oldId = (int)$pdo->lastInsertId();
$post = ['website_url' => 'https://new.example.com'];
$this->invokeHandleWebsiteUrl($thesisId, $post);
$row = $pdo->query("SELECT * FROM thesis_files WHERE thesis_id = $thesisId AND file_type = 'website'")->fetch();
$this->assertSame($oldId, (int)$row['id'], 'Row ID should be preserved on URL update');
$this->assertSame('https://new.example.com', $row['file_path']);
$this->assertSame('Label', $row['display_label'], 'Label should be preserved on URL-only change');
}
public function testHandleWebsiteUrlNoExistingRowEmptyUrlDoesNothing(): void
{
[$authorId, $thesisId] = TestDatabase::seedBasicThesis('Noop', 'Author', 2024);
$pdo = TestDatabase::getPDO();
// Should not error even with no existing row
$post = ['website_url' => ''];
$this->invokeHandleWebsiteUrl($thesisId, $post);
$count = $pdo->query("SELECT COUNT(*) FROM thesis_files WHERE thesis_id = $thesisId AND file_type = 'website'")->fetchColumn();
$this->assertSame(0, (int)$count);
// All other files should still be intact
$totalFiles = $pdo->query("SELECT COUNT(*) FROM thesis_files WHERE thesis_id = $thesisId")->fetchColumn();
$this->assertGreaterThan(0, (int)$totalFiles, 'Cover file from seeding should still exist');
}
}