From e5f3479f7181556fe4f1b3ae7090de21e1f43284 Mon Sep 17 00:00:00 2001 From: grayTerminal-sh Date: Tue, 14 Jul 2026 19:44:53 +0200 Subject: [PATCH] feat(core): create investigation project structure --- Makefile | 16 +- docs/tickets/closed/TICKET-020.md | 257 ++++++++++++++ include/core/investigation_project.h | 39 +++ labfy-investigation | Bin 104472 -> 108784 bytes src/core/investigation_project.c | 424 +++++++++++++++++++++++ tests/test_investigation_node | Bin 0 -> 25824 bytes tests/test_investigation_project | Bin 0 -> 30688 bytes tests/test_investigation_project.c | 463 ++++++++++++++++++++++++++ tests/test_investigation_tree_builder | Bin 0 -> 22848 bytes tests/test_investigation_tree_model | Bin 0 -> 17368 bytes 10 files changed, 1197 insertions(+), 2 deletions(-) create mode 100644 docs/tickets/closed/TICKET-020.md create mode 100644 include/core/investigation_project.h create mode 100644 src/core/investigation_project.c create mode 100755 tests/test_investigation_node create mode 100755 tests/test_investigation_project create mode 100644 tests/test_investigation_project.c create mode 100755 tests/test_investigation_tree_builder create mode 100755 tests/test_investigation_tree_model diff --git a/Makefile b/Makefile index 259c841..f426622 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,7 @@ TARGET = labfy-investigation TEST_NODE = tests/test_investigation_node TEST_TREE_MODEL = tests/test_investigation_tree_model TEST_TREE_BUILDER = tests/test_investigation_tree_builder +TEST_PROJECT = tests/test_investigation_project all: $(TARGET) @@ -57,11 +58,21 @@ $(TEST_TREE_BUILDER): \ $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) \ $(shell $(PKG_CONFIG) --libs gio-2.0) -test: $(TEST_NODE) $(TEST_TREE_MODEL) $(TEST_TREE_BUILDER) +$(TEST_PROJECT): \ + tests/test_investigation_project.c \ + src/core/investigation_project.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + +test: \ + $(TEST_NODE) \ + $(TEST_TREE_MODEL) \ + $(TEST_TREE_BUILDER) \ + $(TEST_PROJECT) @echo "Exécution des tests..." @./$(TEST_NODE) @./$(TEST_TREE_MODEL) @./$(TEST_TREE_BUILDER) + @./$(TEST_PROJECT) @echo "Tous les tests sont valides." %.o: %.c @@ -74,6 +85,7 @@ clean: rm -f $(OBJ) $(TARGET) \ $(TEST_NODE) \ $(TEST_TREE_MODEL) \ - $(TEST_TREE_BUILDER) + $(TEST_TREE_BUILDER) \ + @./$(TEST_PROJECT) .PHONY: clean run test diff --git a/docs/tickets/closed/TICKET-020.md b/docs/tickets/closed/TICKET-020.md new file mode 100644 index 0000000..27f66b5 --- /dev/null +++ b/docs/tickets/closed/TICKET-020.md @@ -0,0 +1,257 @@ +# Ticket #020 + +## Titre + +Créer une nouvelle enquête. + +--- + +## Objectif + +Ajouter la capacité de créer automatiquement une nouvelle enquête dans un dossier choisi par l'utilisateur. + +La création doit produire une arborescence standard et préparer l'emplacement de la future base SQLite. + +Ce ticket ne crée pas encore le schéma SQL complet. + +--- + +## Architecture + +```text +Application + │ + ▼ +InvestigationProject + │ + ▼ +FileSystem +``` + +Le module `InvestigationProject` devient le point d'entrée métier pour la création d'une enquête. + +La GUI ne doit jamais créer directement les dossiers. + +--- + +## Responsabilités + +### InvestigationProject + +Le module doit : + +- recevoir le chemin du dossier parent ; +- recevoir le nom de la nouvelle enquête ; +- créer le dossier racine de l'enquête ; +- créer l'arborescence standard ; +- créer le dossier `00_BaseDeDonnees` ; +- créer un fichier SQLite vide nommé `Enquete.sqlite` ; +- nettoyer ce qui a été créé en cas d'échec partiel ; +- retourner le chemin complet de l'enquête créée. + +--- + +## Arborescence à créer + +```text +NomEnquete/ +├── 00_BaseDeDonnees/ +│ └── Enquete.sqlite +├── 01_Preuves_Originales/ +│ ├── Captures_Ecran/ +│ ├── Conversations/ +│ ├── Documents/ +│ ├── Emails/ +│ ├── Photos/ +│ └── Videos/ +├── 02_Preuves_Traitees/ +│ ├── Annotations/ +│ ├── Extractions/ +│ ├── OCR/ +│ └── Redactions/ +├── 03_Chronologie/ +├── 04_Entites/ +│ ├── Adresses_Email/ +│ ├── Comptes_Bancaires/ +│ ├── Comptes_Facebook/ +│ ├── Comptes_Instagram/ +│ ├── Documents_Identite/ +│ ├── IBAN/ +│ ├── Personnes/ +│ ├── Pseudonymes/ +│ ├── Telephones/ +│ └── Autres/ +├── 05_Rapports/ +├── 06_Exports/ +├── 07_Notes/ +├── 08_Sources/ +└── 09_Hash/ +``` + +Les noms de dossiers ne doivent pas contenir d'accents. + +--- + +## Interface publique attendue + +Créer : + +```text +include/core/investigation_project.h +src/core/investigation_project.c +``` + +Avec : + +```c +char *investigation_project_create( + const char *parent_directory, + const char *investigation_name +); +``` + +--- + +## Contrat de propriété + +En cas de succès, la fonction retourne une nouvelle chaîne allouée contenant le chemin complet du dossier d'enquête. + +Le code appelant devient propriétaire de cette chaîne et doit la libérer avec : + +```c +g_free(investigation_path); +``` + +En cas d'échec, la fonction retourne : + +```c +NULL +``` + +--- + +## Règles de validation + +La fonction doit refuser : + +- `parent_directory == NULL` ; +- un chemin parent vide ; +- `investigation_name == NULL` ; +- un nom vide ; +- un dossier parent inexistant ; +- un chemin parent qui n'est pas un dossier ; +- un dossier d'enquête qui existe déjà ; +- un nom contenant `/`. + +--- + +## Gestion des erreurs + +Si une étape échoue après la création partielle : + +- tous les fichiers créés par la fonction doivent être supprimés ; +- tous les dossiers créés par la fonction doivent être supprimés ; +- aucun dossier partiel ne doit rester sur le disque ; +- la fonction retourne `NULL`. + +La fonction ne doit jamais supprimer un dossier qui existait avant son appel. + +--- + +## Création de `Enquete.sqlite` + +Pour ce ticket, le fichier doit seulement être créé. + +Le schéma SQLite sera initialisé dans le ticket #022. + +Le fichier attendu est : + +```text +00_BaseDeDonnees/Enquete.sqlite +``` + +--- + +## Hors périmètre + +Ce ticket ne doit pas : + +- créer les tables SQLite ; +- générer un UUID ; +- créer les métadonnées ; +- importer une enquête existante ; +- modifier l'interface GTK ; +- ouvrir automatiquement l'enquête créée ; +- créer un rapport ; +- copier des preuves. + +--- + +## Dépendances + +- C17 ; +- GLib ; +- GIO autorisé. + +Aucune dépendance GTK ou SQLite requise dans ce ticket. + +--- + +## Contraintes techniques + +- structure modulaire ; +- aucun état global ; +- aucune logique de création dans `Application` ; +- aucune logique de création dans les widgets ; +- documentation Doxygen ; +- compilation sans warning ; +- nettoyage complet en cas d'erreur ; +- noms de fonctions préfixés par `investigation_project_`. + +--- + +## Tests + +Créer : + +```text +tests/test_investigation_project.c +``` + +Le test doit : + +- créer un dossier temporaire ; +- créer une enquête valide ; +- vérifier chaque dossier attendu ; +- vérifier la présence de `Enquete.sqlite` ; +- vérifier que la fonction retourne le bon chemin ; +- vérifier le refus des paramètres `NULL` ; +- vérifier le refus des chaînes vides ; +- vérifier le refus d'un nom contenant `/` ; +- vérifier le refus si le dossier existe déjà ; +- supprimer complètement l'enquête de test ; +- vérifier qu'aucun résidu ne reste. + +--- + +## Critères d'acceptation + +- [ ] Le projet compile sans warning. +- [ ] `make test` reste valide. +- [ ] Une enquête complète peut être créée. +- [ ] Tous les dossiers attendus existent. +- [ ] `Enquete.sqlite` existe. +- [ ] Aucun dossier partiel ne reste en cas d'échec. +- [ ] Un dossier existant n'est jamais écrasé. +- [ ] Le chemin retourné est correct. +- [ ] Aucune dépendance GTK. +- [ ] Aucun code SQLite métier. +- [ ] Le nouveau test est intégré à `make test`. + +--- + +## Commit attendu + +```text +feat(core): create investigation project structure +``` diff --git a/include/core/investigation_project.h b/include/core/investigation_project.h new file mode 100644 index 0000000..b45727e --- /dev/null +++ b/include/core/investigation_project.h @@ -0,0 +1,39 @@ +/****************************************************************************** + * @file investigation_project.h + * @brief Interface publique de gestion d'un projet d'enquête. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_INVESTIGATION_PROJECT_H +#define LABFY_INVESTIGATION_INVESTIGATION_PROJECT_H + +/** + * @brief Crée une nouvelle enquête dans un dossier parent. + * + * La fonction crée : + * + * - le dossier racine de l'enquête ; + * - l'arborescence standard ; + * - le fichier vide 00_BaseDeDonnees/Enquete.sqlite. + * + * Le dossier parent doit déjà exister. + * + * Le nom de l'enquête ne doit pas être vide et ne doit pas contenir + * de séparateur de chemin. + * + * En cas d'échec, les éléments créés pendant l'appel sont supprimés. + * Aucun dossier existant avant l'appel ne doit être supprimé. + * + * La chaîne retournée appartient au code appelant et doit être libérée + * avec g_free(). + * + * @param parent_directory Chemin du dossier parent. + * @param investigation_name Nom de la nouvelle enquête. + * + * @return Le chemin complet de l'enquête créée, ou NULL en cas d'échec. + */ +char *investigation_project_create( + const char *parent_directory, + const char *investigation_name +); + +#endif diff --git a/labfy-investigation b/labfy-investigation index 681950a73edc8e0256367b072c39d08913ee1e8b..ec6ccc360eef30d6c0886c08cad1a00c22d5cf6d 100755 GIT binary patch delta 32733 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 delta 24987 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 mFi2Ml{ +#include + +#include +#include + +/** + * @brief Liste des dossiers relatifs composant une enquête. + * + * L'ordre est important : les dossiers parents doivent apparaître avant + * leurs sous-dossiers. + */ +static const char *const investigation_project_directories[] = +{ + "00_BaseDeDonnees", + + "01_Preuves_Originales", + "01_Preuves_Originales/Captures_Ecran", + "01_Preuves_Originales/Conversations", + "01_Preuves_Originales/Documents", + "01_Preuves_Originales/Emails", + "01_Preuves_Originales/Photos", + "01_Preuves_Originales/Videos", + + "02_Preuves_Traitees", + "02_Preuves_Traitees/Annotations", + "02_Preuves_Traitees/Extractions", + "02_Preuves_Traitees/OCR", + "02_Preuves_Traitees/Redactions", + + "03_Chronologie", + + "04_Entites", + "04_Entites/Adresses_Email", + "04_Entites/Comptes_Bancaires", + "04_Entites/Comptes_Facebook", + "04_Entites/Comptes_Instagram", + "04_Entites/Documents_Identite", + "04_Entites/IBAN", + "04_Entites/Personnes", + "04_Entites/Pseudonymes", + "04_Entites/Telephones", + "04_Entites/Autres", + + "05_Rapports", + "06_Exports", + "07_Notes", + "08_Sources", + "09_Hash" +}; + +/** + * @brief Nombre de dossiers présents dans la table de création. + */ +#define INVESTIGATION_PROJECT_DIRECTORY_COUNT \ + G_N_ELEMENTS(investigation_project_directories) + +/** + * @brief Vérifie que les paramètres nécessaires à la création sont valides. + * + * @param parent_directory Dossier dans lequel créer l'enquête. + * @param investigation_name Nom de la nouvelle enquête. + * + * @return TRUE si les paramètres sont valides, sinon FALSE. + */ +static gboolean investigation_project_validate_create_parameters( + const char *parent_directory, + const char *investigation_name +) +{ + if (parent_directory == NULL || + parent_directory[0] == '\0') + { + return FALSE; + } + + if (investigation_name == NULL || + investigation_name[0] == '\0') + { + return FALSE; + } + + /* + * Le nom doit représenter un seul composant de chemin. + * + * On refuse par exemple : + * + * Enquetes/Test + */ + if (strchr(investigation_name, G_DIR_SEPARATOR) != NULL) + { + return FALSE; + } + + if (!g_file_test( + parent_directory, + G_FILE_TEST_EXISTS + )) + { + return FALSE; + } + + if (!g_file_test( + parent_directory, + G_FILE_TEST_IS_DIR + )) + { + return FALSE; + } + + return TRUE; +} + +/** + * @brief Supprime les fichiers et dossiers créés pendant une tentative. + * + * Les chemins sont parcourus dans l'ordre inverse afin de supprimer : + * + * 1. les fichiers ; + * 2. les sous-dossiers ; + * 3. le dossier racine. + * + * Cette fonction ne reçoit que des chemins créés par le module pendant + * l'appel courant. + * + * @param created_paths Tableau contenant les chemins créés. + */ +static void investigation_project_cleanup_created_paths( + GPtrArray *created_paths +) +{ + if (created_paths == NULL) + { + return; + } + + for (gsize index = created_paths->len; index > 0; --index) + { + const char *path = NULL; + + path = g_ptr_array_index( + created_paths, + index - 1 + ); + + if (path == NULL) + { + continue; + } + + if (g_file_test(path, G_FILE_TEST_IS_DIR)) + { + if (g_rmdir(path) != 0) + { + g_warning( + "Impossible de supprimer le dossier temporaire '%s' : %s", + path, + g_strerror(errno) + ); + } + } + else if (g_file_test(path, G_FILE_TEST_EXISTS)) + { + if (g_remove(path) != 0) + { + g_warning( + "Impossible de supprimer le fichier temporaire '%s' : %s", + path, + g_strerror(errno) + ); + } + } + } +} + +/** + * @brief Crée un dossier et mémorise son chemin pour un éventuel nettoyage. + * + * La fonction prend possession de directory_path uniquement si la création + * réussit et que le chemin est ajouté au tableau. + * + * @param directory_path Chemin alloué du dossier à créer. + * @param created_paths Tableau des éléments créés. + * + * @return TRUE si le dossier a été créé, sinon FALSE. + */ +static gboolean investigation_project_create_directory( + char *directory_path, + GPtrArray *created_paths +) +{ + if (directory_path == NULL || created_paths == NULL) + { + g_free(directory_path); + return FALSE; + } + + if (g_mkdir(directory_path, 0700) != 0) + { + g_warning( + "Impossible de créer le dossier '%s' : %s", + directory_path, + g_strerror(errno) + ); + + g_free(directory_path); + + return FALSE; + } + + /* + * Le tableau devient propriétaire de directory_path. + */ + g_ptr_array_add( + created_paths, + directory_path + ); + + return TRUE; +} + +char *investigation_project_create( + const char *parent_directory, + const char *investigation_name +) +{ + char *investigation_path = NULL; + char *directory_path = NULL; + char *database_path = NULL; + + GPtrArray *created_paths = NULL; + GError *error = NULL; + + if (!investigation_project_validate_create_parameters( + parent_directory, + investigation_name + )) + { + return NULL; + } + + investigation_path = g_build_filename( + parent_directory, + investigation_name, + NULL + ); + + if (investigation_path == NULL) + { + return NULL; + } + + /* + * Une enquête existante ne doit jamais être écrasée. + */ + if (g_file_test( + investigation_path, + G_FILE_TEST_EXISTS + )) + { + g_free(investigation_path); + return NULL; + } + + /* + * Le tableau conserve tous les chemins créés. + * + * Ses chaînes seront libérées automatiquement par g_ptr_array_free(). + */ + created_paths = g_ptr_array_new_with_free_func( + g_free + ); + + if (created_paths == NULL) + { + g_free(investigation_path); + return NULL; + } + + /* + * On crée d'abord le dossier racine. + * + * Une copie est placée dans created_paths, car investigation_path + * doit être retourné au code appelant en cas de succès. + */ + directory_path = g_strdup(investigation_path); + + if (!investigation_project_create_directory( + directory_path, + created_paths + )) + { + g_ptr_array_free(created_paths, TRUE); + g_free(investigation_path); + + return NULL; + } + + /* + * Création de toute l'arborescence déclarée dans la table. + */ + for (gsize index = 0; + index < INVESTIGATION_PROJECT_DIRECTORY_COUNT; + ++index) + { + directory_path = g_build_filename( + investigation_path, + investigation_project_directories[index], + NULL + ); + + if (!investigation_project_create_directory( + directory_path, + created_paths + )) + { + investigation_project_cleanup_created_paths( + created_paths + ); + + g_ptr_array_free( + created_paths, + TRUE + ); + + g_free(investigation_path); + + return NULL; + } + } + + database_path = g_build_filename( + investigation_path, + "00_BaseDeDonnees", + "Enquete.sqlite", + NULL + ); + + if (database_path == NULL) + { + investigation_project_cleanup_created_paths( + created_paths + ); + + g_ptr_array_free( + created_paths, + TRUE + ); + + g_free(investigation_path); + + return NULL; + } + + /* + * Pour ce ticket, on crée seulement un fichier vide. + * + * Le schéma SQLite sera ajouté dans un prochain ticket. + */ + if (!g_file_set_contents( + database_path, + "", + 0, + &error + )) + { + if (error != NULL) + { + g_warning( + "Impossible de créer '%s' : %s", + database_path, + error->message + ); + + g_clear_error(&error); + } + + g_free(database_path); + + investigation_project_cleanup_created_paths( + created_paths + ); + + g_ptr_array_free( + created_paths, + TRUE + ); + + g_free(investigation_path); + + return NULL; + } + + /* + * Le fichier doit également être supprimé avant ses dossiers + * si une évolution future ajoute une étape pouvant encore échouer. + * + * Le tableau devient propriétaire de database_path. + */ + g_ptr_array_add( + created_paths, + database_path + ); + + /* + * Tout est créé avec succès. + * + * On libère seulement le tableau et ses copies de chemins. + * Les fichiers et dossiers restent sur le disque. + */ + g_ptr_array_free( + created_paths, + TRUE + ); + + return investigation_path; +} diff --git a/tests/test_investigation_node b/tests/test_investigation_node new file mode 100755 index 0000000000000000000000000000000000000000..b7614efa3254dd41638e0c2b0d87f582c089bf9e GIT binary patch literal 25824 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 literal 0 HcmV?d00001 diff --git a/tests/test_investigation_project b/tests/test_investigation_project new file mode 100755 index 0000000000000000000000000000000000000000..14e5237bd29c2ec29e24284520bdeb41b7f25159 GIT binary patch literal 30688 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 qBmT(vOY6qsI}WXMuuUI6Dh +#include +#include + +#include +#include + +/** + * @brief Liste des dossiers attendus dans une enquête. + * + * Cette liste doit rester cohérente avec la structure créée par + * investigation_project_create(). + */ +static const char *const expected_directories[] = +{ + "00_BaseDeDonnees", + + "01_Preuves_Originales", + "01_Preuves_Originales/Captures_Ecran", + "01_Preuves_Originales/Conversations", + "01_Preuves_Originales/Documents", + "01_Preuves_Originales/Emails", + "01_Preuves_Originales/Photos", + "01_Preuves_Originales/Videos", + + "02_Preuves_Traitees", + "02_Preuves_Traitees/Annotations", + "02_Preuves_Traitees/Extractions", + "02_Preuves_Traitees/OCR", + "02_Preuves_Traitees/Redactions", + + "03_Chronologie", + + "04_Entites", + "04_Entites/Adresses_Email", + "04_Entites/Comptes_Bancaires", + "04_Entites/Comptes_Facebook", + "04_Entites/Comptes_Instagram", + "04_Entites/Documents_Identite", + "04_Entites/IBAN", + "04_Entites/Personnes", + "04_Entites/Pseudonymes", + "04_Entites/Telephones", + "04_Entites/Autres", + + "05_Rapports", + "06_Exports", + "07_Notes", + "08_Sources", + "09_Hash" +}; + +#define EXPECTED_DIRECTORY_COUNT \ + G_N_ELEMENTS(expected_directories) + +/** + * @brief Supprime récursivement un fichier ou un dossier de test. + * + * Cette fonction est réservée au nettoyage des répertoires temporaires créés + * par les tests. + * + * @param path Chemin à supprimer. + * + * @return TRUE si le chemin a été supprimé ou n'existait pas, sinon FALSE. + */ +static gboolean test_remove_path_recursively( + const char *path +) +{ + GDir *directory = NULL; + const char *entry_name = NULL; + GError *error = NULL; + + if (path == NULL) + { + return FALSE; + } + + if (!g_file_test(path, G_FILE_TEST_EXISTS)) + { + return TRUE; + } + + if (!g_file_test(path, G_FILE_TEST_IS_DIR)) + { + return g_remove(path) == 0; + } + + directory = g_dir_open( + path, + 0, + &error + ); + + if (directory == NULL) + { + if (error != NULL) + { + g_printerr( + "Impossible d'ouvrir '%s' pendant le nettoyage : %s\n", + path, + error->message + ); + + g_clear_error(&error); + } + + return FALSE; + } + + while ((entry_name = g_dir_read_name(directory)) != NULL) + { + char *entry_path = NULL; + gboolean removal_success = FALSE; + + entry_path = g_build_filename( + path, + entry_name, + NULL + ); + + assert(entry_path != NULL); + + removal_success = test_remove_path_recursively( + entry_path + ); + + g_free(entry_path); + + if (!removal_success) + { + g_dir_close(directory); + return FALSE; + } + } + + g_dir_close(directory); + + return g_rmdir(path) == 0; +} + +/** + * @brief Vérifie qu'un chemin désigne un dossier existant. + */ +static void test_assert_directory_exists( + const char *root_path, + const char *relative_path +) +{ + char *directory_path = NULL; + + assert(root_path != NULL); + assert(relative_path != NULL); + + directory_path = g_build_filename( + root_path, + relative_path, + NULL + ); + + assert(directory_path != NULL); + assert( + g_file_test( + directory_path, + G_FILE_TEST_IS_DIR + ) + ); + + g_free(directory_path); +} + +/** + * @brief Vérifie la création complète d'une enquête. + */ +static void test_create_valid_investigation(void) +{ + char *temporary_parent = NULL; + char *investigation_path = NULL; + char *expected_path = NULL; + char *database_path = NULL; + GError *error = NULL; + + temporary_parent = g_dir_make_tmp( + "labfy-investigation-project-test-XXXXXX", + &error + ); + + assert(temporary_parent != NULL); + assert(error == NULL); + + investigation_path = investigation_project_create( + temporary_parent, + "Enquete_Test" + ); + + assert(investigation_path != NULL); + + expected_path = g_build_filename( + temporary_parent, + "Enquete_Test", + NULL + ); + + assert(expected_path != NULL); + + /* + * Le chemin retourné doit être exactement celui de la nouvelle enquête. + */ + assert(strcmp(investigation_path, expected_path) == 0); + + /* + * Le dossier racine doit exister. + */ + assert( + g_file_test( + investigation_path, + G_FILE_TEST_IS_DIR + ) + ); + + /* + * Vérification de tous les dossiers attendus. + */ + for (gsize index = 0; + index < EXPECTED_DIRECTORY_COUNT; + ++index) + { + test_assert_directory_exists( + investigation_path, + expected_directories[index] + ); + } + + database_path = g_build_filename( + investigation_path, + "00_BaseDeDonnees", + "Enquete.sqlite", + NULL + ); + + assert(database_path != NULL); + + /* + * Enquete.sqlite doit exister et être un fichier régulier. + */ + assert( + g_file_test( + database_path, + G_FILE_TEST_EXISTS + ) + ); + + assert( + g_file_test( + database_path, + G_FILE_TEST_IS_REGULAR + ) + ); + + /* + * Une deuxième création au même endroit doit être refusée. + */ + assert( + investigation_project_create( + temporary_parent, + "Enquete_Test" + ) == NULL + ); + + /* + * Nettoyage complet. + */ + assert( + test_remove_path_recursively( + temporary_parent + ) + ); + + assert( + !g_file_test( + temporary_parent, + G_FILE_TEST_EXISTS + ) + ); + + g_free(database_path); + g_free(expected_path); + g_free(investigation_path); + g_free(temporary_parent); +} + +/** + * @brief Vérifie les paramètres NULL et les chaînes vides. + */ +static void test_invalid_parameters(void) +{ + char *temporary_parent = NULL; + GError *error = NULL; + + temporary_parent = g_dir_make_tmp( + "labfy-investigation-invalid-test-XXXXXX", + &error + ); + + assert(temporary_parent != NULL); + assert(error == NULL); + + assert( + investigation_project_create( + NULL, + "Enquete" + ) == NULL + ); + + assert( + investigation_project_create( + "", + "Enquete" + ) == NULL + ); + + assert( + investigation_project_create( + temporary_parent, + NULL + ) == NULL + ); + + assert( + investigation_project_create( + temporary_parent, + "" + ) == NULL + ); + + assert( + test_remove_path_recursively( + temporary_parent + ) + ); + + g_free(temporary_parent); +} + +/** + * @brief Vérifie le refus d'un nom contenant un séparateur de chemin. + */ +static void test_invalid_investigation_name(void) +{ + char *temporary_parent = NULL; + GError *error = NULL; + + temporary_parent = g_dir_make_tmp( + "labfy-investigation-name-test-XXXXXX", + &error + ); + + assert(temporary_parent != NULL); + assert(error == NULL); + + assert( + investigation_project_create( + temporary_parent, + "Enquetes/Test" + ) == NULL + ); + + assert( + test_remove_path_recursively( + temporary_parent + ) + ); + + g_free(temporary_parent); +} + +/** + * @brief Vérifie le refus d'un dossier parent inexistant. + */ +static void test_missing_parent_directory(void) +{ + assert( + investigation_project_create( + "/tmp/labfy-parent-directory-that-does-not-exist", + "Enquete" + ) == NULL + ); +} + +/** + * @brief Vérifie le refus d'un chemin parent représentant un fichier. + */ +static void test_parent_is_file(void) +{ + char *temporary_directory = NULL; + char *temporary_file = NULL; + GError *error = NULL; + + temporary_directory = g_dir_make_tmp( + "labfy-investigation-parent-file-test-XXXXXX", + &error + ); + + assert(temporary_directory != NULL); + assert(error == NULL); + + temporary_file = g_build_filename( + temporary_directory, + "parent.txt", + NULL + ); + + assert(temporary_file != NULL); + + assert( + g_file_set_contents( + temporary_file, + "test\n", + -1, + &error + ) + ); + + assert(error == NULL); + + assert( + investigation_project_create( + temporary_file, + "Enquete" + ) == NULL + ); + + assert( + test_remove_path_recursively( + temporary_directory + ) + ); + + g_free(temporary_file); + g_free(temporary_directory); +} + +int main(void) +{ + test_create_valid_investigation(); + test_invalid_parameters(); + test_invalid_investigation_name(); + test_missing_parent_directory(); + test_parent_is_file(); + + printf( + "InvestigationProject : tous les tests sont valides.\n" + ); + + return 0; +} diff --git a/tests/test_investigation_tree_builder b/tests/test_investigation_tree_builder new file mode 100755 index 0000000000000000000000000000000000000000..d6cd4844176447be928bb7a3bda21977ee4298de GIT binary patch literal 22848 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