getAllOrientations(); $apPrograms = $db->getAllAPPrograms(); $finalityTypes = $db->getAllFinalityTypes(); $languages = $db->getAllLanguages(); $formatTypes = $db->getAllFormatTypes(); $licenseTypes = $db->getAllLicenseTypes(); if (empty($orientations) || empty($apPrograms) || empty($finalityTypes) || empty($languages) || empty($formatTypes) || empty($licenseTypes)) { throw new RuntimeException('Lookup tables empty — cannot build POST fixture'); } $base = [ 'titre' => 'Test TFE Title', 'subtitle' => 'Test Subtitle', 'auteurice' => 'Doe, Jane', 'mail' => 'jane@example.com', 'synopsis' => 'A short synopsis for testing purposes.', 'année' => '2025', 'orientation' => (string)$orientations[0]['id'], 'ap' => (string)$apPrograms[0]['id'], 'finality' => (string)$finalityTypes[0]['id'], 'has_annexes' => '', 'languages' => [(string)$languages[0]['id']], 'language_autre' => '', 'formats' => [(string)$formatTypes[0]['id']], 'tag' => 'art, test', 'jury_promoteur' => 'Prof. Smith', 'jury_lecteur_interne' => ['Dr. Internal'], 'jury_lecteur_externe' => ['Dr. External'], 'license_id' => (string)$licenseTypes[0]['id'], 'license_custom' => '', 'access_type_id' => '2', 'objet' => 'tfe', 'lien' => '', 'context_note' => '', 'remarks' => '', 'jury_points' => '', 'exemplaire_baiu' => '', 'exemplaire_erg' => '', 'cc2r' => '', 'is_published' => '', ]; return array_merge($base, $overrides); } /** * Assert helper — throws on failure, echoes on pass. */ function assertEq(mixed $expected, mixed $actual, string $label): void { if ($expected == $actual) { echo " ✓ $label\n"; } else { $e = var_export($expected, true); $a = var_export($actual, true); throw new RuntimeException("FAIL $label\n expected: $e\n actual: $a"); } } function assertContains(mixed $needle, array $haystack, string $label): void { if (in_array($needle, $haystack, false)) { echo " ✓ $label\n"; } else { $a = implode(', ', $haystack); throw new RuntimeException("FAIL $label: $needle not in [$a]"); } } function assertNotEmpty(mixed $value, string $label): void { if (!empty($value)) { echo " ✓ $label\n"; } else { throw new RuntimeException("FAIL $label: value is empty"); } } // ── Test setup ──────────────────────────────────────────────────────────────── echo "Form Save Round-Trip Test\n"; echo "=========================\n\n"; $db = Database::getInstance(); $createCtrl = new ThesisCreateController($db); $editCtrl = new ThesisEditController($db); $createdIds = []; try { // ========================================================================= // TEST 1: Create — basic fields persisted // ========================================================================= echo "Test 1: Create — basic fields persisted\n"; $post = buildPost($db, [ 'titre' => 'Round-trip test titre', 'subtitle' => 'Round-trip subtitle', 'synopsis' => 'Round-trip synopsis', 'année' => '2025', ]); $thesisId = $createCtrl->submit($post, []); $createdIds[] = $thesisId; $row = $db->getThesis($thesisId); assertEq('Round-trip test titre', $row['title'], 'title saved'); assertEq('Round-trip subtitle', $row['subtitle'], 'subtitle saved'); assertEq('Round-trip synopsis', $row['synopsis'], 'synopsis saved'); assertEq(2025, (int)$row['year'], 'year saved'); echo "\n"; // ========================================================================= // TEST 2: Create — language_autre creates and links new language // ========================================================================= echo "Test 2: Create — language_autre creates and links new language\n"; $uniqueLang = 'TestLang_' . bin2hex(random_bytes(4)); $post = buildPost($db, [ 'titre' => 'Language autre test', 'languages' => [], // no checkbox 'language_autre' => $uniqueLang, ]); $thesisId = $createCtrl->submit($post, []); $createdIds[] = $thesisId; $langIds = $db->getThesisLanguageIds($thesisId); $allLangs = $db->getAllLanguages(); $found = array_filter($allLangs, fn($l) => $l['name'] === $uniqueLang); assertNotEmpty($found, "language '$uniqueLang' created in languages table"); $createdLangId = (int)array_values($found)[0]['id']; assertContains((string)$createdLangId, array_map('strval', $langIds), 'language_autre ID linked to thesis'); echo "\n"; // ========================================================================= // TEST 3: Create — language_autre + checkbox together // ========================================================================= echo "Test 5: Create — language_autre appended alongside checked languages\n"; $db2 = Database::getInstance(); $allLangs = $db2->getAllLanguages(); $uniqueLang2 = 'TestLang2_' . bin2hex(random_bytes(4)); $post = buildPost($db, [ 'titre' => 'Language combo test', 'languages' => [(string)$allLangs[0]['id']], 'language_autre' => $uniqueLang2, ]); $thesisId = $createCtrl->submit($post, []); $createdIds[] = $thesisId; $langIds = $db->getThesisLanguageIds($thesisId); assertContains((string)$allLangs[0]['id'], array_map('strval', $langIds), 'checkbox language linked'); $found2 = array_filter($db->getAllLanguages(), fn($l) => $l['name'] === $uniqueLang2); $createdLang2 = (int)array_values($found2)[0]['id']; assertContains((string)$createdLang2, array_map('strval', $langIds), 'language_autre also linked'); echo "\n"; // ========================================================================= // TEST 4: Edit — language checkboxes round-trip // ========================================================================= echo "Test 3: Edit — language checkboxes round-trip\n"; $allLangs = $db->getAllLanguages(); $lang1 = (string)$allLangs[0]['id']; $lang2 = (string)$allLangs[1]['id']; $post = buildPost($db, [ 'titre' => 'Lang checkbox test', 'languages' => [$lang1], ]); $thesisId = $createCtrl->submit($post, []); $createdIds[] = $thesisId; $editPost = buildPost($db, [ 'titre' => 'Lang checkbox test', 'languages' => [$lang1, $lang2], ]); $editCtrl->save($thesisId, $editPost, []); $langIds = $db->getThesisLanguageIds($thesisId); assertContains($lang1, array_map('strval', $langIds), 'first language retained on edit'); assertContains($lang2, array_map('strval', $langIds), 'second language added on edit'); echo "\n"; // ========================================================================= // TEST 5: Edit — language_autre adds new language // ========================================================================= echo "Test 4: Edit — language_autre creates and links on edit\n"; $uniqueLang3 = 'EditLang_' . bin2hex(random_bytes(4)); $editPost = buildPost($db, [ 'titre' => 'Lang checkbox test', 'languages' => [$lang1], 'language_autre' => $uniqueLang3, ]); $editCtrl->save($thesisId, $editPost, []); $langIds = $db->getThesisLanguageIds($thesisId); $found3 = array_filter($db->getAllLanguages(), fn($l) => $l['name'] === $uniqueLang3); assertNotEmpty($found3, "language '$uniqueLang3' created on edit"); $createdLang3 = (int)array_values($found3)[0]['id']; assertContains((string)$createdLang3, array_map('strval', $langIds), 'language_autre linked on edit'); echo "\n"; // ========================================================================= // TEST 6: Create — backoffice fields persisted // ========================================================================= echo "Test 5: Create — backoffice fields (remarks, jury_points, exemplaires, cc2r)\n"; $post = buildPost($db, [ 'titre' => 'Backoffice fields test', 'remarks' => 'Internal note here', 'jury_points' => '15.5', 'exemplaire_baiu' => '1', 'exemplaire_erg' => '1', 'cc2r' => '1', ]); $thesisId = $createCtrl->submit($post, []); $createdIds[] = $thesisId; $raw = $db->getThesisRawFields($thesisId); assertEq('Internal note here', $raw['remarks'], 'remarks saved'); assertEq(15.5, (float)$raw['jury_points'], 'jury_points saved'); assertEq(1, (int)$raw['exemplaire_baiu'], 'exemplaire_baiu saved'); assertEq(1, (int)$raw['exemplaire_erg'], 'exemplaire_erg saved'); assertEq(1, (int)$raw['cc2r'], 'cc2r saved'); echo "\n"; // ========================================================================= // TEST 7: Edit — backoffice fields updated // ========================================================================= echo "Test 6: Edit — backoffice fields updated\n"; $editPost = buildPost($db, [ 'titre' => 'Backoffice fields test', 'remarks' => 'Updated note', 'jury_points' => '18', 'exemplaire_baiu' => '', 'exemplaire_erg' => '1', 'cc2r' => '', ]); $editCtrl->save($thesisId, $editPost, []); $raw = $db->getThesisRawFields($thesisId); assertEq('Updated note', $raw['remarks'], 'remarks updated'); assertEq(18.0, (float)$raw['jury_points'], 'jury_points updated'); assertEq(0, (int)$raw['exemplaire_baiu'], 'exemplaire_baiu cleared'); assertEq(1, (int)$raw['exemplaire_erg'], 'exemplaire_erg retained'); assertEq(0, (int)$raw['cc2r'], 'cc2r cleared'); echo "\n"; // ========================================================================= // TEST 8: getOrCreateLanguage — idempotent // ========================================================================= echo "Test 7: getOrCreateLanguage — idempotent (same name returns same ID)\n"; $uniqueName = 'Idempotent_' . bin2hex(random_bytes(4)); $id1 = $db->getOrCreateLanguage($uniqueName); $id2 = $db->getOrCreateLanguage($uniqueName); $id3 = $db->getOrCreateLanguage(strtolower($uniqueName)); // case-insensitive assertEq($id1, $id2, 'same ID on second call'); assertEq($id1, $id3, 'same ID with different case'); echo "\n"; // ========================================================================= // TEST 9: Edit — context_note saved // ========================================================================= echo "Test 8: Edit — context_note saved\n"; $post = buildPost($db, ['titre' => 'Context note test']); $thesisId = $createCtrl->submit($post, []); $createdIds[] = $thesisId; $editPost = buildPost($db, [ 'titre' => 'Context note test', 'context_note' => 'A contextual note visible publicly.', ]); $editCtrl->save($thesisId, $editPost, []); $raw = $db->getThesisRawFields($thesisId); assertEq('A contextual note visible publicly.', $raw['context_note'], 'context_note saved'); echo "\n"; echo "✅ All form save tests passed!\n"; $result = true; } catch (Exception $e) { echo '❌ FAIL: ' . $e->getMessage() . "\n"; $result = false; } finally { // Clean up test theses foreach ($createdIds as $id) { try { $db->deleteThesis($id); } catch (Exception $e) { /* ignore */ } } // Clean up test languages $allLangs = $db->getAllLanguages(); foreach ($allLangs as $lang) { if (str_starts_with($lang['name'], 'TestLang_') || str_starts_with($lang['name'], 'TestLang2_') || str_starts_with($lang['name'], 'EditLang_') || str_starts_with($lang['name'], 'Idempotent_')) { try { $db->getConnection()->prepare('DELETE FROM languages WHERE id = ?')->execute([$lang['id']]); } catch (Exception $e) { /* ignore */ } } } } return $result ?? false;