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'); } }