466 lines
22 KiB
PHP
466 lines
22 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
ob_start();
|
||
|
||
$root = dirname(__DIR__);
|
||
$tmp = sys_get_temp_dir() . '/globinours-tests-' . bin2hex(random_bytes(5)) . '.sqlite';
|
||
putenv('GLOBINOURS_DB_PATH=' . $tmp);
|
||
putenv('GLOBINOURS_BACKUP_DATA_ONLY=1');
|
||
foreach (
|
||
[
|
||
'DB',
|
||
'Migrations',
|
||
'I18n',
|
||
'AppSettings',
|
||
'SecurityService',
|
||
'Auth',
|
||
'TrustedDeviceService',
|
||
'PermissionService',
|
||
'AuditService',
|
||
'BackupService',
|
||
'InstallationService',
|
||
'InventoryService',
|
||
'AgendaService',
|
||
'CalendarFeedService',
|
||
'NotificationService',
|
||
'PrivacyService',
|
||
'StatisticsService',
|
||
'ShelterRoomService',
|
||
'GlobalSearchService',
|
||
'PrivateMediaService',
|
||
'DemoDataService',
|
||
]
|
||
as $class
|
||
) {
|
||
require_once $root . '/app/Services/' . $class . '.php';
|
||
}
|
||
$passed = 0;
|
||
$failed = 0;
|
||
$createdBackup = null;
|
||
$test = static function (string $name, callable $callback) use (&$passed, &$failed): void {
|
||
try {
|
||
$callback();
|
||
echo "✅ $name\n";
|
||
$passed++;
|
||
} catch (Throwable $e) {
|
||
echo "❌ $name — {$e->getMessage()}\n";
|
||
$failed++;
|
||
}
|
||
};
|
||
$assert = static function (bool $condition, string $message = 'Assertion échouée'): void {
|
||
if (!$condition) {
|
||
throw new RuntimeException($message);
|
||
}
|
||
};
|
||
try {
|
||
Migrations::run($root . '/migrations');
|
||
I18n::setLocale('fr');
|
||
$db = DB::pdo();
|
||
Auth::start();
|
||
$test('Toutes les migrations sont appliquées', function () use ($db, $root, $assert): void {
|
||
$expected = count(glob($root . '/migrations/*.sql') ?: []);
|
||
$actual = (int) $db->query('SELECT COUNT(*) FROM _migrations')->fetchColumn();
|
||
$assert($actual === $expected, "$actual/$expected migrations");
|
||
});
|
||
$test('Une installation vierge initialise les médias privés', function () use ($db, $assert): void {
|
||
$columns = array_column($db->query('PRAGMA table_info(animal_photos)')->fetchAll(PDO::FETCH_ASSOC), 'name');
|
||
$assert(in_array('is_public', $columns, true), 'Colonne is_public absente');
|
||
$assert(
|
||
(int) $db
|
||
->query("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='medical_photos'")
|
||
->fetchColumn() === 1,
|
||
'Table medical_photos absente',
|
||
);
|
||
PrivateMediaService::migrateLegacy();
|
||
});
|
||
$test('La base SQLite temporaire est intègre', function () use ($db, $assert): void {
|
||
$assert($db->query('PRAGMA integrity_check')->fetchColumn() === 'ok');
|
||
});
|
||
$test('La configuration initiale des salles est complète', function () use ($assert): void {
|
||
$rooms = ShelterRoomService::byCode(false);
|
||
foreach (['silver', 'twix', 'care', 'quarantine'] as $code) {
|
||
$assert(isset($rooms[$code]), 'Salle absente : ' . $code);
|
||
}
|
||
$assert($rooms['silver']['name'] === 'Salle collective 1');
|
||
$assert($rooms['twix']['name'] === 'Salle collective 2');
|
||
$assert(
|
||
count($rooms['care']['boxes']) === 5 && count($rooms['quarantine']['boxes']) === 5,
|
||
'Les cinq box initiaux ne sont pas disponibles',
|
||
);
|
||
});
|
||
$test('Les paramètres sont enregistrés et relus', function () use ($assert): void {
|
||
AppSettings::save(['association_name' => 'Refuge Test', 'app_language' => 'en'], null);
|
||
$assert(AppSettings::get('association_name') === 'Refuge Test');
|
||
$assert(AppSettings::get('app_language') === 'en');
|
||
I18n::setLocale('fr');
|
||
});
|
||
$test('Les quatre bilans biologiques existent pour chats et chiens', function () use ($db, $assert): void {
|
||
$rows = $db
|
||
->query(
|
||
'SELECT analyzer_code,species_code,COUNT(*) total FROM lab_reference_ranges GROUP BY analyzer_code,species_code',
|
||
)
|
||
->fetchAll(PDO::FETCH_ASSOC);
|
||
$sets = [];
|
||
foreach ($rows as $row) {
|
||
$sets[$row['analyzer_code']][$row['species_code']] = (int) $row['total'];
|
||
}
|
||
foreach (['vetscan-hm5', 'renal', 'hepatic', 'thyroid'] as $code) {
|
||
foreach (['chat', 'chien'] as $species) {
|
||
$assert(($sets[$code][$species] ?? 0) > 0, "Référentiel $code/$species absent");
|
||
}
|
||
}
|
||
$assert(
|
||
(string) $db
|
||
->query("SELECT analyzer_name FROM lab_reference_ranges WHERE analyzer_code='vetscan-hm5' LIMIT 1")
|
||
->fetchColumn() === 'Bilan complet',
|
||
);
|
||
});
|
||
$test('Authentification et mot de passe haché', function () use ($db, $assert): void {
|
||
$assert(ini_get('session.use_strict_mode') === '1', 'Le mode strict des sessions est désactivé');
|
||
$hash = password_hash('MotDePasseSolide!', PASSWORD_DEFAULT);
|
||
$db->prepare(
|
||
"INSERT INTO users(username,password_hash,display_name,role,active) VALUES('admin-test',?,'Administrateur test','admin',1)",
|
||
)->execute([$hash]);
|
||
$assert(!Auth::login('admin-test', 'incorrect'));
|
||
$assert(
|
||
(int) $db->query('SELECT COUNT(*) FROM login_attempts WHERE succeeded=0')->fetchColumn() === 1,
|
||
'Échec de connexion non enregistré',
|
||
);
|
||
$assert(Auth::login('admin-test', 'MotDePasseSolide!'));
|
||
$assert(Auth::is('admin'));
|
||
});
|
||
$test('Les permissions administrateur restent complètes', function () use ($assert): void {
|
||
$assert(PermissionService::can('animals', 'edit'));
|
||
$assert(PermissionService::can('medical', 'view'));
|
||
});
|
||
$test('Les permissions et répétitions de l’agenda sont opérationnelles', function () use ($db, $assert): void {
|
||
$assert(PermissionService::can('agenda', 'edit'));
|
||
$id = AgendaService::save([
|
||
'item_type' => 'task',
|
||
'title' => 'Tâche répétée de test',
|
||
'date' => date('Y-m-01'),
|
||
'time' => '09:00',
|
||
'priority' => 'high',
|
||
'repeat_rule' => 'weekly',
|
||
'repeat_until' => date('Y-m-t'),
|
||
]);
|
||
$month = AgendaService::month(new DateTimeImmutable('first day of this month'));
|
||
$occurrences = array_values(array_filter($month, static fn($row) => (int) $row['id'] === $id));
|
||
$assert(count($occurrences) >= 4, 'La répétition hebdomadaire n’est pas développée');
|
||
AgendaService::setStatus($id, 'completed');
|
||
$assert((string) $db->query('SELECT status FROM agenda_items WHERE id=' . $id)->fetchColumn() === 'completed');
|
||
});
|
||
$test('La vue hebdomadaire respecte le lundi, le dimanche et la préférence utilisateur', function () use (
|
||
$db,
|
||
$assert,
|
||
): void {
|
||
$inside = AgendaService::save([
|
||
'item_type' => 'task',
|
||
'title' => 'Dans la semaine',
|
||
'date' => date('Y-m-d'),
|
||
'time' => '10:00',
|
||
]);
|
||
$outside = AgendaService::save([
|
||
'item_type' => 'task',
|
||
'title' => 'Semaine suivante',
|
||
'date' => new DateTimeImmutable('monday next week')->format('Y-m-d'),
|
||
'time' => '10:00',
|
||
]);
|
||
$ids = array_map('intval', array_column(AgendaService::week(new DateTimeImmutable('today')), 'id'));
|
||
$assert(in_array($inside, $ids, true), 'Le jour courant est absent de sa semaine');
|
||
$assert(!in_array($outside, $ids, true), 'La semaine suivante déborde dans la vue');
|
||
$columns = array_column($db->query('PRAGMA table_info(users)')->fetchAll(PDO::FETCH_ASSOC), 'name');
|
||
$assert(in_array('agenda_view', $columns, true), 'La préférence de vue agenda est absente');
|
||
});
|
||
$test('Les bénévoles de l’annuaire peuvent être mobilisés sur une mission', function () use ($db, $assert): void {
|
||
$db->exec("INSERT INTO directory_contacts(kind,name) VALUES('person','Bénévole Agenda')");
|
||
$volunteer = (int) $db->lastInsertId();
|
||
$db->prepare("INSERT INTO directory_contact_roles(contact_id,role) VALUES(?,'benevole')")->execute([
|
||
$volunteer,
|
||
]);
|
||
$event = AgendaService::save([
|
||
'item_type' => 'transport',
|
||
'title' => 'Mission bénévole',
|
||
'date' => date('Y-m-d'),
|
||
'time' => '14:00',
|
||
'location' => 'Clinique',
|
||
'volunteer_contact_ids' => [$volunteer],
|
||
]);
|
||
$rows = array_values(
|
||
array_filter(
|
||
AgendaService::week(new DateTimeImmutable('today')),
|
||
static fn($row) => (int) $row['id'] === $event,
|
||
),
|
||
);
|
||
$assert(
|
||
count($rows) === 1 && str_contains((string) $rows[0]['volunteer_names'], 'Bénévole Agenda'),
|
||
'Bénévole absent de la mission',
|
||
);
|
||
$assert(str_contains((string) $rows[0]['title'], 'Bénévole Agenda'), 'Affectation non visible dans l’agenda');
|
||
$overlap = AgendaService::save([
|
||
'item_type' => 'task',
|
||
'title' => 'Mission simultanée',
|
||
'date' => date('Y-m-d'),
|
||
'time' => '14:30',
|
||
'volunteer_contact_ids' => [$volunteer],
|
||
]);
|
||
$assert(count(AgendaService::conflicts($overlap)) === 1, 'Le chevauchement du bénévole n’est pas détecté');
|
||
});
|
||
$test('L’export iCalendar et son abonnement privé sont révocables', function () use ($db, $assert): void {
|
||
$token = CalendarFeedService::generateToken((int) Auth::id());
|
||
$assert(
|
||
strlen($token) === 64 && CalendarFeedService::userForToken($token) !== null,
|
||
'Jeton iCalendar invalide',
|
||
);
|
||
$stored = (string) $db
|
||
->query('SELECT agenda_feed_token_hash FROM users WHERE id=' . (int) Auth::id())
|
||
->fetchColumn();
|
||
$assert($stored === hash('sha256', $token) && $stored !== $token, 'Le jeton privé est stocké en clair');
|
||
$ics = CalendarFeedService::render(AgendaService::week(new DateTimeImmutable('today')), 'Agenda Test');
|
||
$assert(
|
||
str_starts_with($ics, "BEGIN:VCALENDAR\r\n") &&
|
||
str_contains($ics, 'BEGIN:VEVENT') &&
|
||
str_contains($ics, 'END:VCALENDAR'),
|
||
'Export iCalendar incomplet',
|
||
);
|
||
CalendarFeedService::revoke((int) Auth::id());
|
||
$assert(CalendarFeedService::userForToken($token) === null, 'Le lien révoqué reste utilisable');
|
||
});
|
||
$test('Une échéance vaccinale produit une notification', function () use ($db, $assert): void {
|
||
$db->exec("INSERT INTO ref_vaccines(name) VALUES('Test vaccin')");
|
||
$v = (int) $db->lastInsertId();
|
||
$db->exec(
|
||
"INSERT INTO animals(internal_code,name,species,status) VALUES('TEST-001','Animal Test','chat','refuge')",
|
||
);
|
||
$a = (int) $db->lastInsertId();
|
||
$db->prepare(
|
||
"INSERT INTO vaccinations(animal_id,vaccine_id,done_date,due_date) VALUES(?,?,date('now','-1 year'),date('now','-1 day'))",
|
||
)->execute([$a, $v]);
|
||
$alerts = NotificationService::alerts();
|
||
$assert((bool) array_filter($alerts, static fn($x) => $x['key'] === 'vaccines'));
|
||
});
|
||
$test('Les rappels vaccinaux apparaissent automatiquement dans l’agenda', function () use ($assert): void {
|
||
$month = AgendaService::month(new DateTimeImmutable('first day of this month'));
|
||
$reminders = array_values(
|
||
array_filter(
|
||
$month,
|
||
static fn($row) => ($row['source_type'] ?? '') === 'vaccine' && ($row['item_type'] ?? '') === 'vaccine',
|
||
),
|
||
);
|
||
$assert(count($reminders) >= 1, 'Aucun rappel vaccinal virtuel dans le calendrier');
|
||
$assert((int) $reminders[0]['id'] < 0, 'Le rappel vaccinal ne doit pas devenir un événement éditable');
|
||
});
|
||
$test('Vermifuges et traitements alimentent l’agenda sans double saisie', function () use ($db, $assert): void {
|
||
$animal = (int) $db->query("SELECT id FROM animals WHERE internal_code='TEST-001'")->fetchColumn();
|
||
$dewormer = (int) $db->query('SELECT id FROM ref_dewormers ORDER BY id LIMIT 1')->fetchColumn();
|
||
$db->prepare(
|
||
"INSERT INTO dewormings(animal_id,dewormer_id,administered_on,next_due_date) VALUES(?,?,date('now','-3 months'),date('now'))",
|
||
)->execute([$animal, $dewormer]);
|
||
$db->exec("INSERT INTO ref_medications(name) VALUES('Traitement agenda')");
|
||
$medication = (int) $db->lastInsertId();
|
||
$db->prepare(
|
||
"INSERT INTO treatments(animal_id,medication_id,dose_text,start_date,end_date,ongoing) VALUES(?,?,'Dose test',date('now'),date('now','+1 day'),1)",
|
||
)->execute([$animal, $medication]);
|
||
$sources = array_column(AgendaService::upcoming(2), 'source_type');
|
||
foreach (['deworming', 'treatment_start', 'treatment_end'] as $source) {
|
||
$assert(in_array($source, $sources, true), 'Échéance absente : ' . $source);
|
||
}
|
||
});
|
||
$test('Le parcours médical et adoption conserve ses relations', function () use ($db, $assert): void {
|
||
$animal = (int) $db->query("SELECT id FROM animals WHERE internal_code='TEST-001'")->fetchColumn();
|
||
$db->prepare(
|
||
"INSERT INTO medical_notes(animal_id,kind,reason,created_by) VALUES(?,'controle','Contrôle de test',?)",
|
||
)->execute([$animal, Auth::id()]);
|
||
$db->prepare(
|
||
"INSERT INTO adoptions(animal_id,adopter_name,adoption_date) VALUES(?,'Adoptant test',date('now'))",
|
||
)->execute([$animal]);
|
||
$assert((int) $db->query('SELECT COUNT(*) FROM medical_notes')->fetchColumn() === 1);
|
||
$assert((int) $db->query('SELECT COUNT(*) FROM adoptions')->fetchColumn() === 1);
|
||
$assert(!$db->query('PRAGMA foreign_key_check')->fetchAll());
|
||
});
|
||
$test('La fiche annuaire agrège adoptions et dépenses', function () use ($db, $assert): void {
|
||
$db->exec("INSERT INTO directory_contacts(kind,name) VALUES('organization','Structure test')");
|
||
$contact = (int) $db->lastInsertId();
|
||
$animal = (int) $db->query("SELECT id FROM animals WHERE internal_code='TEST-001'")->fetchColumn();
|
||
$db->prepare('UPDATE adoptions SET adopter_contact_id=? WHERE animal_id=?')->execute([$contact, $animal]);
|
||
$db->prepare(
|
||
"INSERT INTO animal_expenses(animal_id,clinic_contact_id,label,occurred_on,quantity,catalog_unit_cents,total_cents) VALUES(?,?,'Acte test',date('now'),1,1234,1234)",
|
||
)->execute([$animal, $contact]);
|
||
$q = $db->prepare(
|
||
"SELECT (SELECT COUNT(*) FROM (SELECT animal_id FROM adoptions WHERE adopter_contact_id=dc.id UNION SELECT animal_id FROM animal_placements WHERE contact_id=dc.id AND event_type='adoption')) adopted,(SELECT COALESCE(SUM(total_cents),0) FROM animal_expenses WHERE clinic_contact_id=dc.id) total FROM directory_contacts dc WHERE dc.id=?",
|
||
);
|
||
$q->execute([$contact]);
|
||
$row = $q->fetch();
|
||
$assert((int) $row['adopted'] === 1);
|
||
$assert((int) $row['total'] === 1234);
|
||
$costs = StatisticsService::costs((int) date('Y'));
|
||
$structure =
|
||
array_values(
|
||
array_filter($costs['by_clinic'], static fn($item) => (int) $item['contact_id'] === $contact),
|
||
)[0] ?? null;
|
||
$assert($structure !== null && $structure['label'] === 'Structure test');
|
||
$assert((int) $structure['total_cents'] === 1234);
|
||
});
|
||
$test('La recherche globale retrouve les dossiers accessibles', function () use ($assert): void {
|
||
$animals = GlobalSearchService::search('Animal Test');
|
||
$assert(($animals['animals'][0]['title'] ?? '') === 'Animal Test', 'Animal absent de la recherche');
|
||
$contacts = GlobalSearchService::search('Structure test');
|
||
$assert(($contacts['directory'][0]['title'] ?? '') === 'Structure test', 'Contact absent de la recherche');
|
||
$agenda = GlobalSearchService::search('Mission bénévole');
|
||
$assert(!empty($agenda['agenda']), 'Événement absent de la recherche');
|
||
});
|
||
$test('Une sauvegarde ZIP contient une base et un manifeste', function () use (&$createdBackup, $assert): void {
|
||
if (!class_exists('ZipArchive')) {
|
||
return;
|
||
}
|
||
$createdBackup = BackupService::create('test-suite');
|
||
$path = BackupService::path($createdBackup['name']);
|
||
$assert($path !== null);
|
||
$zip = new ZipArchive();
|
||
$assert($zip->open($path) === true);
|
||
$assert($zip->locateName('manifest.json') !== false);
|
||
$assert($zip->locateName('database/refuge.sqlite') !== false);
|
||
$zip->close();
|
||
});
|
||
$test('La sauvegarde créée est relue et son SQLite vérifié', function () use (&$createdBackup, $assert): void {
|
||
if (!$createdBackup) {
|
||
return;
|
||
}
|
||
$result = BackupService::verify($createdBackup['name']);
|
||
$assert($result['valid'] === true);
|
||
$assert($result['files'] >= 1);
|
||
});
|
||
$test('La pharmacie refuse un stock négatif et valorise l’animal', function () use ($db, $assert): void {
|
||
$product = InventoryService::saveProduct([
|
||
'category' => 'medication',
|
||
'name' => 'Produit stock test',
|
||
'unit' => 'comprimé',
|
||
'minimum_quantity' => '2',
|
||
]);
|
||
$batch = InventoryService::receive([
|
||
'product_id' => $product,
|
||
'quantity' => '10',
|
||
'unit_cost' => '1,20',
|
||
'batch_number' => 'TEST-LOT',
|
||
'expires_on' => date('Y-m-d', strtotime('+1 year')),
|
||
]);
|
||
$animal = (int) $db->query("SELECT id FROM animals WHERE internal_code='TEST-001'")->fetchColumn();
|
||
InventoryService::consumeBatch($batch, 2, $animal, 'Test automatisé');
|
||
$assert((float) $db->query('SELECT quantity FROM inventory_batches WHERE id=' . $batch)->fetchColumn() === 8.0);
|
||
$assert(
|
||
(int) $db
|
||
->query(
|
||
"SELECT total_cents FROM animal_expenses WHERE source_type='inventory' AND source_id=(SELECT MAX(id) FROM inventory_movements WHERE batch_id=$batch)",
|
||
)
|
||
->fetchColumn() === 240,
|
||
);
|
||
$failed = false;
|
||
try {
|
||
InventoryService::consumeBatch($batch, 20, null, 'Impossible');
|
||
} catch (RuntimeException) {
|
||
$failed = true;
|
||
}
|
||
$assert($failed, 'Le stock négatif a été accepté');
|
||
$assert(
|
||
(int) $db->query('SELECT COUNT(*) FROM inventory_movements WHERE batch_id=' . $batch)->fetchColumn() === 2,
|
||
);
|
||
});
|
||
$test('La portabilité et le registre privé sont opérationnels', function () use ($db, $assert): void {
|
||
if (!class_exists('ZipArchive')) {
|
||
return;
|
||
}
|
||
$db->exec(
|
||
"INSERT INTO directory_contacts(kind,name,email) VALUES('person','Contact RGPD','contact@example.test')",
|
||
);
|
||
$id = (int) $db->lastInsertId();
|
||
$path = PrivacyService::exportContact($id);
|
||
$assert(is_file($path));
|
||
$zip = new ZipArchive();
|
||
$assert($zip->open($path) === true);
|
||
$assert($zip->locateName('contact.json') !== false);
|
||
$zip->close();
|
||
unlink($path);
|
||
PrivacyService::logAccess('test_document', 1, null, 'test.pdf');
|
||
$assert(
|
||
(int) $db
|
||
->query("SELECT COUNT(*) FROM private_file_access_log WHERE resource_type='test_document'")
|
||
->fetchColumn() === 1,
|
||
);
|
||
});
|
||
$test('La maintenance RGPD purge uniquement le journal arrivé à échéance', function () use ($db, $assert): void {
|
||
AppSettings::save(['privacy_access_log_months' => '12'], Auth::id());
|
||
$db->exec(
|
||
"INSERT INTO private_file_access_log(resource_type,action,accessed_at) VALUES('ancien_test','view',datetime('now','-13 months'))",
|
||
);
|
||
$result = PrivacyService::maintenance();
|
||
$assert($result['access_logs_removed'] >= 1);
|
||
$assert(
|
||
(int) $db
|
||
->query("SELECT COUNT(*) FROM private_file_access_log WHERE resource_type='ancien_test'")
|
||
->fetchColumn() === 0,
|
||
);
|
||
$assert(
|
||
(int) $db->query("SELECT COUNT(*) FROM directory_contacts WHERE name='Contact RGPD'")->fetchColumn() === 1,
|
||
'Un contact a été supprimé automatiquement',
|
||
);
|
||
});
|
||
$test('Les appareils persistants sont révocables', function () use ($db, $assert): void {
|
||
TrustedDeviceService::create((int) Auth::id(), 'Téléphone de test');
|
||
$device = $db->query('SELECT * FROM trusted_devices ORDER BY id DESC LIMIT 1')->fetch();
|
||
$assert($device && $device['device_name'] === 'Téléphone de test');
|
||
TrustedDeviceService::revoke((int) $device['id'], (int) Auth::id());
|
||
$assert(
|
||
(bool) $db->query('SELECT revoked_at FROM trusted_devices WHERE id=' . (int) $device['id'])->fetchColumn(),
|
||
);
|
||
});
|
||
$test('La démonstration installe une fiche Twix complète', function () use ($db, $root, $assert): void {
|
||
DemoDataService::reset($db);
|
||
$summary = DemoDataService::seed($db, Auth::id());
|
||
DemoDataService::clearFiles();
|
||
DemoDataService::installFiles($db);
|
||
$twix = (int) $db
|
||
->query("SELECT id FROM animals WHERE internal_code='DEMO-003' AND name='Twix' AND status='quarantaine'")
|
||
->fetchColumn();
|
||
$assert($twix > 0, 'Twix absent de la quarantaine');
|
||
$assert(
|
||
$summary['lab_reports'] === 1 && $summary['agenda_items'] === 1 && $summary['photos'] === 1,
|
||
'Résumé de démonstration incomplet',
|
||
);
|
||
foreach (
|
||
[
|
||
'medical_notes',
|
||
'treatments',
|
||
'vaccinations',
|
||
'dewormings',
|
||
'animal_health_conditions',
|
||
'lab_reports',
|
||
'animal_photos',
|
||
'agenda_items',
|
||
]
|
||
as $table
|
||
) {
|
||
$assert(
|
||
(int) $db->query("SELECT COUNT(*) FROM $table WHERE animal_id=$twix")->fetchColumn() > 0,
|
||
"Donnée Twix absente : $table",
|
||
);
|
||
}
|
||
$assert(is_file($root . '/public/media/animals/' . $twix . '/twix.webp'), 'Photo de Twix absente');
|
||
DemoDataService::clearFiles();
|
||
});
|
||
} finally {
|
||
if ($createdBackup && isset($createdBackup['name'])) {
|
||
try {
|
||
BackupService::delete($createdBackup['name']);
|
||
} catch (Throwable) {
|
||
}
|
||
}
|
||
DB::close();
|
||
foreach ([$tmp, $tmp . '-wal', $tmp . '-shm'] as $file) {
|
||
if (is_file($file)) {
|
||
unlink($file);
|
||
}
|
||
}
|
||
}
|
||
echo "\n$passed réussi(s), $failed échec(s).\n";
|
||
exit($failed ? 1 : 0);
|