Error tests, FK violations fix

- ErrorHandler tests: 77 assertions covering FK extraction, normalization, dedup, edge cases. Fix FK table map for child tables.
- Fix FK violation: (int)null → 0 in createThesis for orientation/ap/finality/license FK columns. Add FK value logging to updateThesis.
- Add CURRENT_ISSUES.md with summary of FK violation, dev debugging, and tag dedup status for next conversation
This commit is contained in:
Pontoporeia
2026-05-19 00:08:05 +02:00
parent a80b2c08bf
commit 6cc0e407f3
38 changed files with 1515 additions and 82 deletions
+2 -1
View File
@@ -2,6 +2,7 @@
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/Parsedown.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
class AboutController
{
@@ -24,7 +25,7 @@ class AboutController
$contacts = $db->getAproposContent('contacts');
$contacts = is_array($contacts) && !empty($contacts) ? $contacts : null;
} catch (Exception $e) {
error_log('Error loading about page: ' . $e->getMessage());
ErrorHandler::log('about_page', $e);
$rawContent = $this->defaultContent;
$contacts = null;
}
+2 -1
View File
@@ -37,6 +37,7 @@ class HomeController
public static function create(): self
{
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
return new self(Database::getInstance());
}
@@ -98,7 +99,7 @@ class HomeController
);
}
} catch (Exception $e) {
error_log('HomeController: ' . $e->getMessage());
ErrorHandler::log('home', $e);
// Return safe empty state; view will show "Aucun mémoire trouvé"
$isDefaultView = false;
}
+2 -1
View File
@@ -2,6 +2,7 @@
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/Parsedown.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
class LicenceController
{
@@ -18,7 +19,7 @@ class LicenceController
$content = $dbPage ? $dbPage['content'] : '';
$pageTitle = $dbPage ? $dbPage['title'] : 'Licences';
} catch (Exception $e) {
error_log('Error loading licence page: ' . $e->getMessage());
ErrorHandler::log('licence_page', $e);
$content = '';
$pageTitle = 'Licences';
}
+2 -1
View File
@@ -53,6 +53,7 @@ class MediaController
// 3. Visibility gate for thesis files
if (preg_match('#^theses/#', $requestedPath)) {
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
try {
$mediaDb = Database::getInstance();
$accessTypeId = $mediaDb->getFileVisibility($requestedPath);
@@ -61,7 +62,7 @@ class MediaController
exit;
}
} catch (\Throwable $e) {
error_log('MediaController visibility check error: ' . $e->getMessage());
ErrorHandler::log('media_visibility', $e, ['path' => $path]);
}
}
+3 -2
View File
@@ -45,6 +45,7 @@ class SearchController
{
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/RateLimit.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
$rateLimit = new RateLimit(
self::RATE_LIMIT_MAX,
@@ -105,7 +106,7 @@ class SearchController
} catch (InvalidArgumentException $e) {
$validationError = $e->getMessage();
} catch (Exception $e) {
error_log('SearchController: ' . $e->getMessage());
ErrorHandler::log('search', $e);
$validationError = 'Une erreur est survenue.';
}
@@ -169,7 +170,7 @@ class SearchController
} catch (InvalidArgumentException $e) {
$validationError = $e->getMessage();
} catch (Exception $e) {
error_log('SearchController: ' . $e->getMessage());
ErrorHandler::log('repertoire', $e);
$validationError = 'Une erreur est survenue.';
}
+2 -1
View File
@@ -39,6 +39,7 @@ class TfeController
public static function create(): self
{
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
return new self(Database::getInstance());
}
@@ -64,7 +65,7 @@ class TfeController
try {
$data = $this->db->getThesisById($thesisId);
} catch (Exception $e) {
error_log('TfeController: ' . $e->getMessage());
ErrorHandler::log('tfe_view', $e, ['thesis_id' => $thesisId]);
$this->redirectHome();
}
+35 -4
View File
@@ -77,6 +77,7 @@ class ThesisCreateController
public static function make(): self
{
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
return new self(new Database());
}
@@ -198,17 +199,28 @@ class ThesisCreateController
]);
$identifier = $this->db->getThesisIdentifier($thesisId);
error_log("ThesisCreateController: created thesis #$thesisId ($identifier) with " . count($authorEntries) . ' author(s)');
error_log("[ThesisCreate] Step 1 OK — thesis_id=$thesisId ($identifier) | authors=" . count($authorEntries));
$this->db->setThesisAuthors($thesisId, $authorEntries);
error_log("[ThesisCreate] Step 2 OK — authors=" . json_encode($data['authorNames']));
$this->db->setThesisJury($thesisId, $data['juryMembers']);
error_log("[ThesisCreate] Step 3 OK — jury=" . count($data['juryMembers']));
$this->db->setThesisLanguages($thesisId, $data['languageIds']);
error_log("[ThesisCreate] Step 4 OK — languages=" . json_encode($data['languageIds']));
$this->db->setThesisFormats($thesisId, $data['formatIds']);
error_log("[ThesisCreate] Step 5 OK — formats=" . json_encode($data['formatIds']));
$this->db->setThesisTags($thesisId, $data['keywords']);
error_log("[ThesisCreate] Step 6 OK — tags=" . json_encode($data['keywords']));
$this->db->commit();
error_log("[ThesisCreate] COMMIT OK — thesis_id=$thesisId");
} catch (Exception $e) {
ErrorHandler::log('thesis_create_tx', $e, ['thesis_id' => $thesisId ?? null]);
$this->db->rollback();
throw $e;
}
@@ -420,12 +432,31 @@ class ThesisCreateController
throw new Exception('Veuillez indiquer au moins un·e lecteur·ice externe.');
}
// Keywords (max 10)
$tagRaw = $this->sanitiseString($post['tag'] ?? '');
$keywords = $tagRaw !== '' ? array_map('trim', explode(',', $tagRaw)) : [];
// Keywords (max 10, min 3) — lowercased, spaces collapsed, deduplicated
$keywords = [];
$normalizeTag = fn(string $t): string => strtolower(trim(preg_replace('/\s+/', ' ', $t)));
if (isset($post['tag']) && is_array($post['tag'])) {
$keywords = array_values(array_unique(array_map(
$normalizeTag,
array_map(fn($t) => (string)$t, $post['tag'])
)));
$keywords = array_filter($keywords, fn($t) => $t !== '');
$keywords = array_slice($keywords, 0, 10);
} else {
$tagRaw = $this->sanitiseString($post['tag'] ?? '');
if ($tagRaw !== '') {
$keywords = array_map($normalizeTag, explode(',', $tagRaw));
}
}
$keywords = array_values(array_unique($keywords));
$keywords = array_filter($keywords, fn($t) => $t !== '');
$keywords = array_slice($keywords, 0, 10);
if (count($keywords) > 10) {
throw new Exception('Maximum 10 mots-clés autorisés.');
}
if (count($keywords) < 3) {
throw new Exception('Veuillez indiquer au moins 3 mots-clés.');
}
// Languages (at least one required)
$languageIds = isset($post['languages']) && is_array($post['languages'])
+35 -12
View File
@@ -36,6 +36,7 @@ class ThesisEditController
public static function create(?Database $db = null): self
{
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
return new self($db ?? Database::getInstance());
}
@@ -192,7 +193,7 @@ class ThesisEditController
try {
// ── 1. Thesis metadata ────────────────────────────────────────────
$this->db->updateThesis($thesisId, [
$meta = [
'title' => trim($post['titre'] ?? ''),
'subtitle' => trim($post['subtitle'] ?? ''),
'year' => intval($post['année'] ?? 0),
@@ -211,7 +212,9 @@ class ThesisEditController
'exemplaire_erg' => !empty($post['exemplaire_erg']),
'cc2r' => !empty($post['cc2r']),
'license_custom' => trim($post['license_custom'] ?? ''),
]);
];
$this->db->updateThesis($thesisId, $meta);
error_log('[ThesisEdit] Step 1 OK — thesis_id=' . $thesisId);
// ── 2. Authors (alphabetically sorted) ─────────────────────────────
$authorsRaw = trim($post['auteurice'] ?? '');
@@ -230,10 +233,12 @@ class ThesisEditController
];
}
$this->db->setThesisAuthors($thesisId, $authorEntries);
error_log('[ThesisEdit] Step 2 OK — authors=' . json_encode($authorNames));
// ── 3. Jury ───────────────────────────────────────────────────────
$juryMembers = $this->collectJuryMembers($post);
$this->db->setThesisJury($thesisId, $juryMembers);
error_log('[ThesisEdit] Step 3 OK — jury=' . count($juryMembers));
// ── 4. Languages ──────────────────────────────────────────────────
$langIds = isset($post['languages']) && is_array($post['languages'])
@@ -248,25 +253,43 @@ class ThesisEditController
}
}
$this->db->setThesisLanguages($thesisId, $langIds);
error_log('[ThesisEdit] Step 4 OK — languages=' . json_encode($langIds));
// ── 5. Formats ────────────────────────────────────────────────────
$this->db->setThesisFormats(
$thesisId,
isset($post['formats']) && is_array($post['formats'])
? $post['formats']
: []
);
$formatIds = isset($post['formats']) && is_array($post['formats'])
? $post['formats']
: [];
$this->db->setThesisFormats($thesisId, $formatIds);
error_log('[ThesisEdit] Step 5 OK — formats=' . json_encode($formatIds));
// ── 6. Tags ───────────────────────────────────────────────────────
$keywordsRaw = trim($post['tag'] ?? '');
$keywords = $keywordsRaw !== ''
? array_map('trim', explode(',', $keywordsRaw))
: [];
$normalizeTag = fn(string $t): string => strtolower(trim(preg_replace('/\s+/', ' ', $t)));
$keywords = [];
if (isset($post['tag']) && is_array($post['tag'])) {
$keywords = array_values(array_unique(array_map(
$normalizeTag,
array_map(fn($t) => (string)$t, $post['tag'])
)));
} else {
$keywordsRaw = trim($post['tag'] ?? '');
if ($keywordsRaw !== '') {
$keywords = array_map($normalizeTag, explode(',', $keywordsRaw));
}
}
$keywords = array_values(array_unique($keywords));
$keywords = array_filter($keywords, fn($t) => $t !== '');
$keywords = array_slice($keywords, 0, 10);
if (count($keywords) < 3) {
throw new Exception('Veuillez indiquer au moins 3 mots-clés.');
}
$this->db->setThesisTags($thesisId, $keywords);
error_log('[ThesisEdit] Step 6 OK — tags=' . json_encode($keywords));
$this->db->commit();
error_log('[ThesisEdit] COMMIT OK — thesis_id=' . $thesisId);
} catch (Exception $e) {
ErrorHandler::log('thesis_edit_tx', $e, ['thesis_id' => $thesisId]);
$this->db->rollback();
throw $e;
}