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 681950a..ec6ccc3 100755 Binary files a/labfy-investigation and b/labfy-investigation differ diff --git a/src/core/investigation_project.c b/src/core/investigation_project.c new file mode 100644 index 0000000..5d4e342 --- /dev/null +++ b/src/core/investigation_project.c @@ -0,0 +1,424 @@ +/****************************************************************************** + * @file investigation_project.c + * @brief Création et gestion de la structure d'un projet d'enquête. + ******************************************************************************/ + +#include "core/investigation_project.h" + +#include +#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 0000000..b7614ef Binary files /dev/null and b/tests/test_investigation_node differ diff --git a/tests/test_investigation_project b/tests/test_investigation_project new file mode 100755 index 0000000..14e5237 Binary files /dev/null and b/tests/test_investigation_project differ diff --git a/tests/test_investigation_project.c b/tests/test_investigation_project.c new file mode 100644 index 0000000..e5c2a4e --- /dev/null +++ b/tests/test_investigation_project.c @@ -0,0 +1,463 @@ +/****************************************************************************** + * @file test_investigation_project.c + * @brief Tests d'intégration du module InvestigationProject. + ******************************************************************************/ + +#include "core/investigation_project.h" + +#include +#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 0000000..d6cd484 Binary files /dev/null and b/tests/test_investigation_tree_builder differ diff --git a/tests/test_investigation_tree_model b/tests/test_investigation_tree_model new file mode 100755 index 0000000..2c823e1 Binary files /dev/null and b/tests/test_investigation_tree_model differ