db = TestDatabase::getInstance(); require_once APP_ROOT . '/src/Controllers/MediaController.php'; } /** * An 'Interdit' thesis file (access_type_id=3) must be flagged as * forbidden so the public route returns 403 (and only the admin bypass * may open it). */ public function testInterditThesisFileIsFlaggedForbidden(): void { [$authorId, $thesisId] = TestDatabase::seedBasicThesis('Interdit TFE', 'Author', 2024); $pdo = TestDatabase::getPDO(); $pdo->prepare('UPDATE theses SET access_type_id = 3 WHERE id = ?') ->execute([$thesisId]); $pdo->prepare( "INSERT INTO thesis_files (thesis_id, file_type, file_path, file_name, file_size, mime_type) VALUES (?, 'main', ?, 'rapport.pdf', 0, 'application/pdf')" )->execute([$thesisId, 'documents/2024-001/rapport.pdf']); $accessTypeId = $this->db->getFileVisibility('documents/2024-001/rapport.pdf'); $this->assertSame(3, $accessTypeId); } /** * A 'Libre' thesis file (access_type_id=1) is served publicly by /media — * never blocked by the gate. */ public function testLibreThesisFileIsNotFlaggedForbidden(): void { [$authorId, $thesisId] = TestDatabase::seedBasicThesis('Libre TFE', 'Author', 2024); $pdo = TestDatabase::getPDO(); $pdo->prepare('UPDATE theses SET access_type_id = 1 WHERE id = ?') ->execute([$thesisId]); $accessTypeId = $this->db->getFileVisibility('documents/2024-001/cover.jpg'); $this->assertSame(1, $accessTypeId); } /** * A path that belongs to no thesis file resolves to null — the gate is * a no-op (file still subject to MIME + jail checks in MediaController). */ public function testUnrelatedPathReturnsNull(): void { // No files seeded at all — seed an Interdit thesis without entries. $pdo = TestDatabase::getPDO(); $pdo->prepare( "INSERT INTO theses (title, year, identifier, is_published, objet, access_type_id) VALUES ('Orphan', 2024, '2024-999', 1, 'tfe', 3)" )->execute(); $accessTypeId = $this->db->getFileVisibility('theses/2024-999/unknown.pdf'); $this->assertNull($accessTypeId); } // ── getFileDisplayName (tab title / download name) ──────────────────────── /** * getFileDisplayName returns the original uploaded file_name for a known * thesis file — used to set a useful browser tab title instead of 'media.php'. */ public function testFileDisplayNameFromStoredFileName(): void { [$authorId, $thesisId] = TestDatabase::seedBasicThesis('Display Name', 'Author', 2024); $pdo = TestDatabase::getPDO(); $pdo->prepare( "INSERT INTO thesis_files (thesis_id, file_type, file_path, file_name, file_size, mime_type) VALUES (?, 'main', 'tfe/2024-001/rapport.pdf', 'Note de synthese — Étude erg.pdf', 0, 'application/pdf')" )->execute([$thesisId]); $this->assertSame( 'Note de synthese — Étude erg.pdf', $this->db->getFileDisplayName('tfe/2024-001/rapport.pdf') ); } /** * getFileDisplayName returns null for a path that belongs to no thesis file, * so MediaController falls back to the basename of the requested path. */ public function testFileDisplayNameUnknownPathReturnsNull(): void { $this->assertNull($this->db->getFileDisplayName('theses/2024-999/unknown.pdf')); } // ── isThesisFilePath (admin-bypass defense-in-depth) ────────────────────── /** * The admin bypass route is restricted to thesis-file prefixes only. * All five storage layouts (tfe/ these/ frart/ documents/ theses/) count; * any other storage path (schema, db, tmp, backups…) is rejected. */ public function testIsThesisFilePathRecognisesAllThesisPrefixes(): void { foreach (['tfe', 'these', 'frart', 'documents', 'theses'] as $prefix) { $this->assertTrue( MediaController::isThesisFilePath($prefix . '/2024/2024-001/rapport.pdf'), "Expected '{$prefix}/…' to be a thesis file path" ); } } public function testIsThesisFilePathRejectsNonThesisStoragePaths(): void { foreach ([ 'schema.sql', 'xamxam.db', 'tmp/evil.php', 'backups/xamxam-2024.db', 'cache/foo.png', ] as $nonThesisPath) { $this->assertFalse( MediaController::isThesisFilePath($nonThesisPath), "Expected '{$nonThesisPath}' to be rejected as a non-thesis path" ); } } }