test: add ShareLinkTest + PureLogicTest (TDD), fix coverMap undefined in SearchController

This commit is contained in:
Pontoporeia
2026-05-08 10:56:27 +02:00
parent 15d54fa19e
commit 6ba13e00ea
16 changed files with 1274 additions and 68 deletions

View File

@@ -86,6 +86,7 @@ class SearchController
$years = [];
$orientations = [];
$apPrograms = [];
$coverMap = [];
try {
$results = $this->db->searchTheses(
@@ -98,6 +99,9 @@ class SearchController
$years = $this->db->getAvailableYears();
$orientations = $this->db->getAllOrientations();
$apPrograms = $this->db->getAllAPPrograms();
if (!empty($results)) {
$coverMap = $this->db->getCoverPathsForTheses(array_column($results, 'id'));
}
} catch (InvalidArgumentException $e) {
$validationError = $e->getMessage();
} catch (Exception $e) {

View File

@@ -141,7 +141,7 @@ class TfeController
/**
* Build a ~160-character meta description from the thesis synopsis.
*/
private function buildMetaDescription(string $synopsis): string
protected function buildMetaDescription(string $synopsis): string
{
$plain = strip_tags($synopsis);
@@ -159,7 +159,7 @@ class TfeController
*
* @param array<int, array<string, mixed>> $files
*/
private function resolveOgImage(array $files): string
protected function resolveOgImage(array $files): string
{
// Prefer the dedicated cover
foreach ($files as $file) {
@@ -185,7 +185,7 @@ class TfeController
* @param array<string, mixed> $data
* @return array<string, string>
*/
private function buildOgTags(array $data, int $thesisId, string $metaDescription): array
protected function buildOgTags(array $data, int $thesisId, string $metaDescription): array
{
$ogImage = $this->resolveOgImage($data['files'] ?? []);
$title = $data['title'] . (!empty($data['authors']) ? ' ' . $data['authors'] : '');
@@ -210,7 +210,7 @@ class TfeController
* @param array<int, array<string, mixed>> $jury
* @return array{presidents: list<string>, internes: list<string>, externes: list<string>, ulb: list<string>, lecteurs_internes: list<string>, lecteurs_externes: list<string>}
*/
private function splitJuryByRole(array $jury): array
protected function splitJuryByRole(array $jury): array
{
$result = ['presidents' => [], 'internes' => [], 'externes' => [], 'ulb' => [], 'lecteurs_internes' => [], 'lecteurs_externes' => []];
@@ -253,7 +253,7 @@ class TfeController
* @param array<int, array<string, mixed>> $files
* @return list<string>
*/
private function collectCaptionPaths(array $files): array
protected function collectCaptionPaths(array $files): array
{
$captions = [];

View File

@@ -183,6 +183,8 @@ class ThesisCreateController
'synopsis' => $data['synopsis'],
'context_note' => $data['contextNote'],
'file_size_info' => $data['durationInfo'],
'duration_pages' => $data['durationPages'],
'duration_minutes'=> $data['durationMinutes'],
'baiu_link' => $data['lien'],
'license_id' => $data['licenseId'],
'license_custom' => $data['licenseCustom'],
@@ -338,14 +340,15 @@ class ThesisCreateController
$subtitle = $this->sanitiseString($post['subtitle'] ?? '');
$synopsis = $this->validateRequired($this->sanitiseString($post['synopsis'] ?? ''), 'Synopsis');
$durationInfo = $this->sanitiseString($post['duration_pages'] ?? '');
$durationPages = $this->sanitiseString($post['duration_pages'] ?? '');
$durationMinutes = $this->sanitiseString($post['duration_minutes'] ?? '');
if ($durationInfo !== '' && $durationMinutes !== '') {
$durationInfo = $durationInfo . ' pages + ' . $durationMinutes . ' minutes';
$durationInfo = '';
if ($durationPages !== '' && $durationMinutes !== '') {
$durationInfo = $durationPages . ' pages + ' . $durationMinutes . ' minutes';
} elseif ($durationMinutes !== '') {
$durationInfo = $durationMinutes . ' minutes';
} elseif ($durationInfo !== '') {
$durationInfo = $durationInfo . ' pages';
} elseif ($durationPages !== '') {
$durationInfo = $durationPages . ' pages';
}
if (!empty($post['has_annexes'])) {
$durationInfo = $durationInfo ? $durationInfo . ' + annexe(s)' : 'Annexe(s)';
@@ -430,6 +433,14 @@ class ThesisCreateController
$languageIds = isset($post['languages']) && is_array($post['languages'])
? array_map('intval', $post['languages'])
: [];
$autreRaw = trim($post['language_autre'] ?? '');
if ($autreRaw !== '') {
foreach (array_map('trim', explode(',', $autreRaw)) as $langName) {
if ($langName !== '') {
$languageIds[] = $this->db->getOrCreateLanguage($langName);
}
}
}
if (empty($languageIds)) {
throw new Exception('Veuillez sélectionner au moins une langue.');
}
@@ -508,6 +519,8 @@ class ThesisCreateController
'subtitle',
'synopsis',
'durationInfo',
'durationPages',
'durationMinutes',
'juryMembers',
'keywords',
'languageIds',
@@ -676,7 +689,7 @@ class ThesisCreateController
/**
* Determine the logical file_type from MIME type, extension, and original filename.
*/
private function detectFileType(string $mimeType, string $ext, string $originalName): string
protected function detectFileType(string $mimeType, string $ext, string $originalName): string
{
if ($ext === 'vtt' || $mimeType === 'text/vtt') {
return 'caption';
@@ -725,7 +738,7 @@ class ThesisCreateController
* Generate a filesystem-safe author slug from the author name.
* Converts to uppercase, replaces spaces with underscores, removes accents.
*/
private function generateAuthorSlug(string $authorName): string
protected function generateAuthorSlug(string $authorName): string
{
// Remove accents using iconv if available, otherwise simple mapping
$normalized = $authorName;
@@ -764,7 +777,7 @@ class ThesisCreateController
* Sanitize a filename: remove accents, replace spaces with underscore, remove special chars.
* Keeps extension.
*/
private function sanitizeFilename(string $filename): string
protected function sanitizeFilename(string $filename): string
{
$ext = pathinfo($filename, PATHINFO_EXTENSION);
$name = pathinfo($filename, PATHINFO_FILENAME);
@@ -800,7 +813,7 @@ class ThesisCreateController
* Find a unique folder name inside theses/{year}/.
* Pattern: {year}_{authorSlug} or {year}_{authorSlug}_{suffix} if exists.
*/
private function ensureUniqueFolder(int $year, string $authorSlug): string
protected function ensureUniqueFolder(int $year, string $authorSlug): string
{
$baseDir = STORAGE_ROOT . '/theses/' . $year . '/';
if (!is_dir($baseDir)) {
@@ -822,7 +835,7 @@ class ThesisCreateController
* The URL is stored in file_path; no filesystem operation is performed.
* label and sort_order from the POST are preserved.
*/
private function handleWebsiteUrl(int $thesisId, array $post): void
protected function handleWebsiteUrl(int $thesisId, array $post): void
{
$websiteUrl = trim($post['website_url'] ?? '');
if ($websiteUrl === '') {

View File

@@ -256,6 +256,8 @@ class ThesisEditController
'synopsis' => trim($post['synopsis'] ?? ''),
'context_note' => trim($post['context_note'] ?? ''),
'file_size_info' => $this->buildFileSizeInfo($post),
'duration_pages' => trim($post['duration_pages'] ?? ''),
'duration_minutes'=> trim($post['duration_minutes'] ?? ''),
'baiu_link' => trim($post['lien'] ?? ''),
'license_id' => filter_var($post['license_id'] ?? '', FILTER_VALIDATE_INT) ?: null,
'access_type_id' => filter_var($post['access_type_id'] ?? '', FILTER_VALIDATE_INT) ?: null,
@@ -291,12 +293,18 @@ class ThesisEditController
$this->db->setThesisJury($thesisId, $juryMembers);
// ── 4. Languages ──────────────────────────────────────────────────
$this->db->setThesisLanguages(
$thesisId,
isset($post['languages']) && is_array($post['languages'])
? $post['languages']
: []
);
$langIds = isset($post['languages']) && is_array($post['languages'])
? $post['languages']
: [];
$autreRaw = trim($post['language_autre'] ?? '');
if ($autreRaw !== '') {
foreach (array_map('trim', explode(',', $autreRaw)) as $langName) {
if ($langName !== '') {
$langIds[] = (string)$this->db->getOrCreateLanguage($langName);
}
}
}
$this->db->setThesisLanguages($thesisId, $langIds);
// ── 5. Formats ────────────────────────────────────────────────────
$this->db->setThesisFormats(
@@ -710,7 +718,7 @@ class ThesisEditController
/**
* Build file_size_info from separate duration fields.
*/
private function buildFileSizeInfo(array $post): string
protected function buildFileSizeInfo(array $post): string
{
$pages = trim($post['duration_pages'] ?? '');
$minutes = trim($post['duration_minutes'] ?? '');

View File

@@ -1477,6 +1477,23 @@ class Database
}
}
/**
* Return the ID of an existing language by name, inserting it if absent.
* Name is trimmed and stored as-is (case-preserved).
*/
public function getOrCreateLanguage(string $name): int
{
$name = trim($name);
$stmt = $this->pdo->prepare('SELECT id FROM languages WHERE LOWER(name) = LOWER(?) LIMIT 1');
$stmt->execute([$name]);
$id = $stmt->fetchColumn();
if ($id !== false) {
return (int)$id;
}
$this->pdo->prepare('INSERT INTO languages (name) VALUES (?)')->execute([$name]);
return (int)$this->pdo->lastInsertId();
}
/**
* Replace all format associations for a thesis.
* @param int $thesisId
@@ -1581,7 +1598,7 @@ class Database
public function getThesisRawFields(int $thesisId): ?array
{
$stmt = $this->pdo->prepare(
'SELECT license_id, license_custom, access_type_id, context_note, remarks, jury_points, exemplaire_baiu, exemplaire_erg, cc4r FROM theses WHERE id = ? LIMIT 1'
'SELECT license_id, license_custom, access_type_id, context_note, remarks, jury_points, exemplaire_baiu, exemplaire_erg, cc4r, duration_pages, duration_minutes FROM theses WHERE id = ? LIMIT 1'
);
$stmt->execute([$thesisId]);
$row = $stmt->fetch();
@@ -1697,6 +1714,8 @@ class Database
synopsis = ?,
context_note = ?,
file_size_info = ?,
duration_pages = ?,
duration_minutes = ?,
baiu_link = ?,
license_id = ?,
license_custom = ?,
@@ -1720,6 +1739,8 @@ class Database
$data['synopsis'],
!empty($data['context_note']) ? $data['context_note'] : null,
!empty($data['file_size_info']) ? $data['file_size_info'] : null,
isset($data['duration_pages']) && $data['duration_pages'] !== '' ? (int)$data['duration_pages'] : null,
isset($data['duration_minutes']) && $data['duration_minutes'] !== '' ? (int)$data['duration_minutes'] : null,
!empty($data['baiu_link']) ? $data['baiu_link'] : null,
$data['license_id'] ?? null,
!empty($data['license_custom']) ? $data['license_custom'] : null,
@@ -1765,6 +1786,7 @@ class Database
identifier, title, subtitle, year,
orientation_id, ap_program_id, finality_id,
synopsis, context_note, file_size_info,
duration_pages, duration_minutes,
baiu_link, license_id, license_custom,
access_type_id,
objet,
@@ -1773,7 +1795,7 @@ class Database
exemplaire_baiu, exemplaire_erg,
cc4r,
submitted_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
');
$validObjet = ['tfe', 'thèse', 'frart'];
@@ -1790,6 +1812,8 @@ class Database
$data['synopsis'],
!empty($data['context_note']) ? $data['context_note'] : null,
!empty($data['file_size_info']) ? $data['file_size_info'] : null,
isset($data['duration_pages']) && $data['duration_pages'] !== '' ? (int)$data['duration_pages'] : null,
isset($data['duration_minutes']) && $data['duration_minutes'] !== '' ? (int)$data['duration_minutes'] : null,
!empty($data['baiu_link']) ? $data['baiu_link'] : null,
$data['license_id'] ?? null,
!empty($data['license_custom']) ? $data['license_custom'] : null,