splitJuryByRole($jury); } public function exposedCollectCaptionPaths(array $files): array { return $this->collectCaptionPaths($files); } }; } private function getThesisCreateController(): ThesisCreateController { $db = TestDatabase::getInstance(); return new class ($db) extends ThesisCreateController { public function exposedDetectFileType(string $mimeType, string $ext): string { return $this->detectFileType($mimeType, $ext); } }; } /** * Invoke the private hasFilePondQueueData() method via reflection. * The method lives in the ThesisFileHandler trait, used by ThesisCreateController. */ private function invokeHasFilePondQueueData(array $post): bool { $ctrl = $this->getThesisCreateController(); $ref = new ReflectionMethod(ThesisCreateController::class, 'hasFilePondQueueData'); return $ref->invoke($ctrl, $post); } // ── splitJuryByRole ────────────────────────────────────────────────────── public function testSplitJuryByRoleAllRoles(): void { $ctrl = $this->getTfeController(); $jury = [ ['name' => 'Alice', 'role' => 'president', 'is_external' => 0, 'is_ulb' => 0], ['name' => 'Bob', 'role' => 'promoteur', 'is_external' => 0, 'is_ulb' => 0], ['name' => 'Carol', 'role' => 'promoteur', 'is_external' => 1, 'is_ulb' => 1], ['name' => 'Dave', 'role' => 'promoteur', 'is_external' => 1, 'is_ulb' => 0], ['name' => 'Eve', 'role' => 'lecteur', 'is_external' => 0, 'is_ulb' => 0], ['name' => 'Frank', 'role' => 'lecteur', 'is_external' => 1, 'is_ulb' => 0], ]; $split = $ctrl->exposedSplitJuryByRole($jury); $this->assertSame(['Alice'], $split['presidents']); $this->assertSame(['Bob'], $split['internes']); $this->assertSame(['Carol'], $split['ulb']); $this->assertSame(['Dave'], $split['externes']); $this->assertSame(['Eve'], $split['lecteurs_internes']); $this->assertSame(['Frank'], $split['lecteurs_externes']); } public function testSplitJuryByRoleEmptyNameSkipped(): void { $ctrl = $this->getTfeController(); $jury = [['name' => '', 'role' => 'president', 'is_external' => 0, 'is_ulb' => 0]]; $split = $ctrl->exposedSplitJuryByRole($jury); $this->assertEmpty($split['presidents']); } public function testSplitJuryByRoleEmptyJury(): void { $ctrl = $this->getTfeController(); $split = $ctrl->exposedSplitJuryByRole([]); $this->assertEmpty($split['presidents']); $this->assertEmpty($split['internes']); $this->assertEmpty($split['ulb']); $this->assertEmpty($split['externes']); $this->assertEmpty($split['lecteurs_internes']); $this->assertEmpty($split['lecteurs_externes']); } // ── collectCaptionPaths ────────────────────────────────────────────────── public function testCollectCaptionPathsVttByMime(): void { $ctrl = $this->getTfeController(); $files = [ ['mime_type' => 'application/pdf', 'file_path' => 'main.pdf'], ['mime_type' => 'text/vtt', 'file_path' => 'captions1.vtt'], ['mime_type' => 'video/mp4', 'file_path' => 'video.mp4'], ['mime_type' => 'text/plain', 'file_path' => 'captions2.vtt'], ]; $captions = $ctrl->exposedCollectCaptionPaths($files); // Only the VTT files, in order $this->assertCount(2, $captions); $this->assertSame('captions1.vtt', $captions[0]); $this->assertSame('captions2.vtt', $captions[1]); } public function testCollectCaptionPathsVttByExtension(): void { $ctrl = $this->getTfeController(); $files = [ ['mime_type' => 'application/octet-stream', 'file_path' => 'sub.vtt'], ['mime_type' => 'video/mp4', 'file_path' => 'video.mp4'], ]; $captions = $ctrl->exposedCollectCaptionPaths($files); $this->assertCount(1, $captions); $this->assertSame('sub.vtt', $captions[0]); } public function testCollectCaptionPathsNoVttReturnsEmpty(): void { $ctrl = $this->getTfeController(); $files = [['mime_type' => 'video/mp4', 'file_path' => 'video.mp4']]; $this->assertEmpty($ctrl->exposedCollectCaptionPaths($files)); } // ── detectFileType ─────────────────────────────────────────────────────── public function testDetectFileTypeByMime(): void { $ctrl = $this->getThesisCreateController(); $this->assertSame('caption', $ctrl->exposedDetectFileType('text/vtt', 'vtt')); $this->assertSame('audio', $ctrl->exposedDetectFileType('audio/mpeg', 'mp3')); $this->assertSame('audio', $ctrl->exposedDetectFileType('audio/ogg', 'ogg')); $this->assertSame('video', $ctrl->exposedDetectFileType('video/mp4', 'mp4')); $this->assertSame('video', $ctrl->exposedDetectFileType('video/webm', 'webm')); $this->assertSame('main', $ctrl->exposedDetectFileType('application/pdf', 'pdf')); $this->assertSame('image', $ctrl->exposedDetectFileType('image/jpeg', 'jpg')); $this->assertSame('image', $ctrl->exposedDetectFileType('image/png', 'png')); $this->assertSame('other', $ctrl->exposedDetectFileType('application/zip', 'zip')); } public function testDetectFileTypeByExtensionFallback(): void { $ctrl = $this->getThesisCreateController(); $this->assertSame('audio', $ctrl->exposedDetectFileType('application/octet-stream', 'mp3')); $this->assertSame('video', $ctrl->exposedDetectFileType('application/octet-stream', 'mp4')); $this->assertSame('main', $ctrl->exposedDetectFileType('application/octet-stream', 'pdf')); $this->assertSame('image', $ctrl->exposedDetectFileType('application/octet-stream', 'webp')); $this->assertSame('caption', $ctrl->exposedDetectFileType('application/octet-stream', 'vtt')); } // ── hasFilePondQueueData ──────────────────────────────────────────────── public function testHasFilePondQueueDataReturnsTrueForHexId(): void { $post = ['queue_file' => ['tfe' => ['abc123def456abc123def456abc123de']]]; $this->assertTrue($this->invokeHasFilePondQueueData($post)); } public function testHasFilePondQueueDataReturnsTrueForPeertubeVideo(): void { $post = ['queue_file' => ['tfe' => ['peertube:video:bmpQZTUPv4ou8ufiwajV63']]]; $this->assertTrue( $this->invokeHasFilePondQueueData($post), 'peertube:video:UUID should be detected as FilePond data' ); } public function testHasFilePondQueueDataReturnsTrueForPeertubeAudio(): void { $post = ['queue_file' => ['tfe' => ['peertube:audio:xyz123']]]; $this->assertTrue( $this->invokeHasFilePondQueueData($post), 'peertube:audio:UUID should be detected as FilePond data' ); } public function testHasFilePondQueueDataReturnsTrueForMixedIds(): void { $post = ['queue_file' => ['tfe' => ['123', 'peertube:video:abc123', '456']]]; $this->assertTrue( $this->invokeHasFilePondQueueData($post), 'Mixed array containing a peertube: ID should be detected' ); } public function testHasFilePondQueueDataReturnsTrueForHexInCoverQueue(): void { $post = ['queue_file' => ['cover' => ['abcdef1234567890abcdef1234567890']]]; $this->assertTrue($this->invokeHasFilePondQueueData($post)); } public function testHasFilePondQueueDataReturnsTrueForPeertubeInCoverQueue(): void { $post = ['queue_file' => ['cover' => ['peertube:video:uuid1']]]; $this->assertTrue($this->invokeHasFilePondQueueData($post)); } public function testHasFilePondQueueDataReturnsFalseForNumericIdsOnly(): void { $post = ['queue_file' => ['tfe' => ['123', '456']]]; $this->assertFalse($this->invokeHasFilePondQueueData($post)); } public function testHasFilePondQueueDataReturnsFalseForEmptyInput(): void { $this->assertFalse($this->invokeHasFilePondQueueData([])); $this->assertFalse($this->invokeHasFilePondQueueData(['queue_file' => []])); $this->assertFalse($this->invokeHasFilePondQueueData(['queue_file' => ['tfe' => []]])); } public function testHasFilePondQueueDataReturnsFalseForEmptyStrings(): void { $post = ['queue_file' => ['tfe' => ['', ' ']]]; $this->assertFalse($this->invokeHasFilePondQueueData($post)); } public function testHasFilePondQueueDataHandlesScalarNotArray(): void { $post = ['queue_file' => ['tfe' => 'peertube:video:singleUuid']]; $this->assertTrue($this->invokeHasFilePondQueueData($post)); $post2 = ['queue_file' => ['tfe' => 'abc123def456abc123def456abc123de']]; $this->assertTrue($this->invokeHasFilePondQueueData($post2)); $post3 = ['queue_file' => ['tfe' => '123']]; $this->assertFalse($this->invokeHasFilePondQueueData($post3)); } }