diff --git a/docs/tickets/closed/TICKET-021.md b/docs/tickets/closed/TICKET-021.md new file mode 100644 index 0000000..d601285 --- /dev/null +++ b/docs/tickets/closed/TICKET-021.md @@ -0,0 +1,254 @@ +# Ticket #021 + +## Titre + +Valider une enquête existante. + +--- + +## Objectif + +Ajouter au module `InvestigationProject` la capacité de vérifier qu’un dossier +correspond bien à une enquête Labfy Investigation valide. + +La validation doit contrôler la présence et le type des éléments obligatoires, +sans modifier le contenu du dossier. + +--- + +## Architecture + +```text +Application + │ + ▼ +InvestigationProject + │ + ▼ +FileSystem +``` + +Toute ouverture d’enquête existante doit passer par +`InvestigationProject`. + +La GUI ne doit jamais décider seule si un dossier est valide. + +--- + +## Responsabilités + +### InvestigationProject + +Le module doit : + +- recevoir le chemin d’un dossier ; +- vérifier que le chemin existe ; +- vérifier qu’il désigne un dossier ; +- vérifier la présence de l’arborescence obligatoire ; +- vérifier que les dossiers attendus sont bien des dossiers ; +- vérifier la présence de `00_BaseDeDonnees/Enquete.sqlite` ; +- vérifier que `Enquete.sqlite` est un fichier régulier ; +- retourner un résultat clair sans modifier le dossier. + +--- + +## Interface publique attendue + +Faire évoluer : + +```text +include/core/investigation_project.h +src/core/investigation_project.c +``` + +Ajouter : + +```c +bool investigation_project_validate( + const char *investigation_path +); +``` + +--- + +## Contrat + +La fonction retourne : + +```c +true +``` + +si le dossier est une enquête valide. + +Elle retourne : + +```c +false +``` + +si : + +- le chemin est invalide ; +- le dossier n’existe pas ; +- le chemin désigne un fichier ; +- un dossier obligatoire manque ; +- un élément attendu comme dossier est en réalité un fichier ; +- `Enquete.sqlite` manque ; +- `Enquete.sqlite` n’est pas un fichier régulier. + +La fonction ne doit jamais modifier le système de fichiers. + +--- + +## Structure obligatoire + +Les dossiers suivants doivent exister : + +```text +00_BaseDeDonnees +01_Preuves_Originales +02_Preuves_Traitees +03_Chronologie +04_Entites +05_Rapports +06_Exports +07_Notes +08_Sources +09_Hash +``` + +Les sous-dossiers suivants doivent également exister : + +```text +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/Annotations +02_Preuves_Traitees/Extractions +02_Preuves_Traitees/OCR +02_Preuves_Traitees/Redactions + +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 +``` + +Le fichier suivant doit exister : + +```text +00_BaseDeDonnees/Enquete.sqlite +``` + +--- + +## Réutilisation de la structure déclarative + +La validation doit réutiliser la même liste de chemins que la création. + +Il ne doit pas exister deux listes indépendantes décrivant l’arborescence. + +La structure de référence doit rester centralisée dans +`investigation_project.c`. + +--- + +## Hors périmètre + +Ce ticket ne doit pas : + +- ouvrir SQLite ; +- lire le schéma SQL ; +- vérifier la version de la base ; +- créer les éléments manquants ; +- réparer une enquête ; +- importer automatiquement des fichiers ; +- modifier GTK ; +- ouvrir automatiquement l’enquête dans l’application. + +--- + +## Gestion des erreurs + +Dans ce ticket, la fonction retourne uniquement un booléen. + +Les détails d’erreur plus précis pourront être ajoutés plus tard avec : + +```c +GError +``` + +ou une énumération métier dédiée. + +La validation ne doit produire aucun `g_warning()` pour un dossier simplement +invalide : un résultat `false` suffit. + +--- + +## Contraintes techniques + +- C17 ; +- GLib autorisée ; +- aucune dépendance GTK ; +- aucune dépendance SQLite requise ; +- aucune écriture sur le disque ; +- aucun état global ; +- documentation Doxygen ; +- compilation sans warning. + +--- + +## Tests + +Faire évoluer : + +```text +tests/test_investigation_project.c +``` + +Le test doit vérifier : + +- une enquête complète est valide ; +- `NULL` est refusé ; +- une chaîne vide est refusée ; +- un chemin inexistant est refusé ; +- un fichier simple est refusé ; +- une enquête sans `Enquete.sqlite` est refusée ; +- une enquête avec un dossier obligatoire manquant est refusée ; +- un élément attendu comme dossier mais remplacé par un fichier est refusé ; +- une enquête valide reste inchangée après validation. + +--- + +## Critères d’acceptation + +- [ ] Le projet compile sans warning. +- [ ] `make test` reste entièrement valide. +- [ ] Une enquête créée par `investigation_project_create()` est valide. +- [ ] Un chemin invalide est refusé. +- [ ] Un dossier incomplet est refusé. +- [ ] Un faux fichier `Enquete.sqlite` incorrect est refusé. +- [ ] Aucun élément n’est créé pendant la validation. +- [ ] Aucun élément n’est supprimé pendant la validation. +- [ ] La structure de référence n’est pas dupliquée. +- [ ] Aucune dépendance GTK. +- [ ] Aucun `Gtk-CRITICAL`. + +--- + +## Commit attendu + +```text +feat(core): validate investigation project structure +``` diff --git a/include/core/investigation_project.h b/include/core/investigation_project.h index b45727e..3c4cf23 100644 --- a/include/core/investigation_project.h +++ b/include/core/investigation_project.h @@ -6,6 +6,8 @@ #ifndef LABFY_INVESTIGATION_INVESTIGATION_PROJECT_H #define LABFY_INVESTIGATION_INVESTIGATION_PROJECT_H +#include + /** * @brief Crée une nouvelle enquête dans un dossier parent. * @@ -36,4 +38,20 @@ char *investigation_project_create( const char *investigation_name ); +/****************************************************************************** + * @brief Vérifie qu'un dossier est une enquête valide. + * + * La fonction contrôle que tous les éléments obligatoires de la structure + * d'une enquête existent et sont du type attendu. + * + * Aucune modification n'est effectuée sur le système de fichiers. + * + * @param investigation_path Chemin de l'enquête à vérifier. + * + * @return true si l'enquête est valide, sinon false. + ******************************************************************************/ +bool investigation_project_validate( + const char *investigation_path +); + #endif diff --git a/labfy-investigation b/labfy-investigation index ec6ccc3..d5a654e 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 index 5d4e342..fc04986 100644 --- a/src/core/investigation_project.c +++ b/src/core/investigation_project.c @@ -11,56 +11,84 @@ #include #include +/** + * @brief Type d'un élément de la structure d'une enquête. + */ +typedef enum +{ + INVESTIGATION_PROJECT_DIRECTORY, + INVESTIGATION_PROJECT_FILE +} InvestigationProjectEntryType; + +/** + * @brief Décrit un élément attendu dans une enquête. + */ +typedef struct +{ + /** + * Chemin relatif depuis la racine de l'enquête. + */ + const char *relative_path; + + /** + * Nature de l'élément. + */ + InvestigationProjectEntryType type; + +} InvestigationProjectEntry; + /** * @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[] = +static const InvestigationProjectEntry +investigation_project_entries[] = { - "00_BaseDeDonnees", + { "00_BaseDeDonnees", INVESTIGATION_PROJECT_DIRECTORY }, + { "00_BaseDeDonnees/Enquete.sqlite", INVESTIGATION_PROJECT_FILE }, - "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", + { "01_Preuves_Originales", INVESTIGATION_PROJECT_DIRECTORY }, + { "01_Preuves_Originales/Captures_Ecran", INVESTIGATION_PROJECT_DIRECTORY }, + { "01_Preuves_Originales/Conversations", INVESTIGATION_PROJECT_DIRECTORY }, + { "01_Preuves_Originales/Documents", INVESTIGATION_PROJECT_DIRECTORY }, + { "01_Preuves_Originales/Emails", INVESTIGATION_PROJECT_DIRECTORY }, + { "01_Preuves_Originales/Photos", INVESTIGATION_PROJECT_DIRECTORY }, + { "01_Preuves_Originales/Videos", INVESTIGATION_PROJECT_DIRECTORY }, - "02_Preuves_Traitees", - "02_Preuves_Traitees/Annotations", - "02_Preuves_Traitees/Extractions", - "02_Preuves_Traitees/OCR", - "02_Preuves_Traitees/Redactions", + { "02_Preuves_Traitees", INVESTIGATION_PROJECT_DIRECTORY }, + { "02_Preuves_Traitees/Annotations", INVESTIGATION_PROJECT_DIRECTORY }, + { "02_Preuves_Traitees/Extractions", INVESTIGATION_PROJECT_DIRECTORY }, + { "02_Preuves_Traitees/OCR", INVESTIGATION_PROJECT_DIRECTORY }, + { "02_Preuves_Traitees/Redactions", INVESTIGATION_PROJECT_DIRECTORY }, - "03_Chronologie", + { "03_Chronologie", INVESTIGATION_PROJECT_DIRECTORY }, - "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", + { "04_Entites", INVESTIGATION_PROJECT_DIRECTORY }, + { "04_Entites/Adresses_Email", INVESTIGATION_PROJECT_DIRECTORY }, + { "04_Entites/Comptes_Bancaires", INVESTIGATION_PROJECT_DIRECTORY }, + { "04_Entites/Comptes_Facebook", INVESTIGATION_PROJECT_DIRECTORY }, + { "04_Entites/Comptes_Instagram", INVESTIGATION_PROJECT_DIRECTORY }, + { "04_Entites/Documents_Identite", INVESTIGATION_PROJECT_DIRECTORY }, + { "04_Entites/IBAN", INVESTIGATION_PROJECT_DIRECTORY }, + { "04_Entites/Personnes", INVESTIGATION_PROJECT_DIRECTORY }, + { "04_Entites/Pseudonymes", INVESTIGATION_PROJECT_DIRECTORY }, + { "04_Entites/Telephones", INVESTIGATION_PROJECT_DIRECTORY }, + { "04_Entites/Autres", INVESTIGATION_PROJECT_DIRECTORY }, - "05_Rapports", - "06_Exports", - "07_Notes", - "08_Sources", - "09_Hash" + { "05_Rapports", INVESTIGATION_PROJECT_DIRECTORY }, + { "06_Exports", INVESTIGATION_PROJECT_DIRECTORY }, + { "07_Notes", INVESTIGATION_PROJECT_DIRECTORY }, + { "08_Sources", INVESTIGATION_PROJECT_DIRECTORY }, + { "09_Hash", INVESTIGATION_PROJECT_DIRECTORY } }; /** * @brief Nombre de dossiers présents dans la table de création. */ -#define INVESTIGATION_PROJECT_DIRECTORY_COUNT \ - G_N_ELEMENTS(investigation_project_directories) +#define INVESTIGATION_PROJECT_ENTRY_COUNT \ + G_N_ELEMENTS(investigation_project_entries) /** * @brief Vérifie que les paramètres nécessaires à la création sont valides. @@ -226,6 +254,70 @@ static gboolean investigation_project_create_directory( return TRUE; } +/** + * @brief Vérifie qu'un élément existe avec le type attendu. + * + * @param investigation_root Racine de l'enquête. + * @param entry Élément à vérifier. + * + * @return TRUE si l'élément est valide. + */ +static gboolean investigation_project_validate_entry( + const char *investigation_root, + const InvestigationProjectEntry *entry +) +{ + char *path = NULL; + gboolean valid = FALSE; + + if (investigation_root == NULL || + entry == NULL) + { + return FALSE; + } + + path = g_build_filename( + investigation_root, + entry->relative_path, + NULL + ); + + if (path == NULL) + { + return FALSE; + } + + switch (entry->type) + { + case INVESTIGATION_PROJECT_DIRECTORY: + + valid = g_file_test( + path, + G_FILE_TEST_IS_DIR + ); + + break; + + case INVESTIGATION_PROJECT_FILE: + + valid = g_file_test( + path, + G_FILE_TEST_IS_REGULAR + ); + + break; + + default: + + valid = FALSE; + break; + } + + g_free(path); + + return valid; +} + char *investigation_project_create( const char *parent_directory, const char *investigation_name @@ -307,12 +399,21 @@ char *investigation_project_create( * Création de toute l'arborescence déclarée dans la table. */ for (gsize index = 0; - index < INVESTIGATION_PROJECT_DIRECTORY_COUNT; - ++index) + index < INVESTIGATION_PROJECT_ENTRY_COUNT; + ++index) { + const InvestigationProjectEntry *entry = NULL; + + entry = &investigation_project_entries[index]; + + if (entry->type != INVESTIGATION_PROJECT_DIRECTORY) + { + continue; + } + directory_path = g_build_filename( investigation_path, - investigation_project_directories[index], + entry->relative_path, NULL ); @@ -422,3 +523,50 @@ char *investigation_project_create( return investigation_path; } + +bool investigation_project_validate( + const char *investigation_path +) +{ + if (investigation_path == NULL) + { + return false; + } + + if (investigation_path[0] == '\0') + { + return false; + } + + if (!g_file_test( + investigation_path, + G_FILE_TEST_EXISTS)) + { + return false; + } + + if (!g_file_test( + investigation_path, + G_FILE_TEST_IS_DIR)) + { + return false; + } + + for (gsize index = 0; + index < INVESTIGATION_PROJECT_ENTRY_COUNT; + ++index) + { + const InvestigationProjectEntry *entry = NULL; + + entry = &investigation_project_entries[index]; + + if (!investigation_project_validate_entry( + investigation_path, + entry)) + { + return false; + } + } + + return true; +} diff --git a/tests/test_investigation_project b/tests/test_investigation_project index 14e5237..6fdc14d 100755 Binary files a/tests/test_investigation_project and b/tests/test_investigation_project differ diff --git a/tests/test_investigation_project.c b/tests/test_investigation_project.c index e5c2a4e..404620c 100644 --- a/tests/test_investigation_project.c +++ b/tests/test_investigation_project.c @@ -447,6 +447,256 @@ static void test_parent_is_file(void) g_free(temporary_directory); } +static void test_validate_created_investigation(void) +{ + char *temporary_parent = NULL; + char *investigation_path = NULL; + GError *error = NULL; + + temporary_parent = g_dir_make_tmp( + "labfy-investigation-validation-test-XXXXXX", + &error + ); + + assert(temporary_parent != NULL); + assert(error == NULL); + + investigation_path = investigation_project_create( + temporary_parent, + "Enquete_Valide" + ); + + assert(investigation_path != NULL); + + assert( + investigation_project_validate( + investigation_path + ) + ); + + assert( + test_remove_path_recursively( + temporary_parent + ) + ); + + g_free(investigation_path); + g_free(temporary_parent); +} + +static void test_validate_invalid_paths(void) +{ + char *temporary_directory = NULL; + char *temporary_file = NULL; + GError *error = NULL; + + assert(!investigation_project_validate(NULL)); + assert(!investigation_project_validate("")); + assert( + !investigation_project_validate( + "/tmp/labfy-investigation-does-not-exist" + ) + ); + + temporary_directory = g_dir_make_tmp( + "labfy-investigation-validation-file-test-XXXXXX", + &error + ); + + assert(temporary_directory != NULL); + assert(error == NULL); + + temporary_file = g_build_filename( + temporary_directory, + "not-an-investigation.txt", + NULL + ); + + assert(temporary_file != NULL); + + assert( + g_file_set_contents( + temporary_file, + "test\n", + -1, + &error + ) + ); + + assert(error == NULL); + + assert( + !investigation_project_validate( + temporary_file + ) + ); + + assert( + test_remove_path_recursively( + temporary_directory + ) + ); + + g_free(temporary_file); + g_free(temporary_directory); +} + +static void test_validate_missing_database(void) +{ + char *temporary_parent = NULL; + char *investigation_path = NULL; + char *database_path = NULL; + GError *error = NULL; + + temporary_parent = g_dir_make_tmp( + "labfy-investigation-missing-database-test-XXXXXX", + &error + ); + + assert(temporary_parent != NULL); + assert(error == NULL); + + investigation_path = investigation_project_create( + temporary_parent, + "Enquete_Sans_Base" + ); + + assert(investigation_path != NULL); + + database_path = g_build_filename( + investigation_path, + "00_BaseDeDonnees", + "Enquete.sqlite", + NULL + ); + + assert(database_path != NULL); + assert(g_remove(database_path) == 0); + + assert( + !investigation_project_validate( + investigation_path + ) + ); + + assert( + test_remove_path_recursively( + temporary_parent + ) + ); + + g_free(database_path); + g_free(investigation_path); + g_free(temporary_parent); +} + +static void test_validate_missing_directory(void) +{ + char *temporary_parent = NULL; + char *investigation_path = NULL; + char *directory_path = NULL; + GError *error = NULL; + + temporary_parent = g_dir_make_tmp( + "labfy-investigation-missing-directory-test-XXXXXX", + &error + ); + + assert(temporary_parent != NULL); + assert(error == NULL); + + investigation_path = investigation_project_create( + temporary_parent, + "Enquete_Incomplete" + ); + + assert(investigation_path != NULL); + + directory_path = g_build_filename( + investigation_path, + "05_Rapports", + NULL + ); + + assert(directory_path != NULL); + assert(g_rmdir(directory_path) == 0); + + assert( + !investigation_project_validate( + investigation_path + ) + ); + + assert( + test_remove_path_recursively( + temporary_parent + ) + ); + + g_free(directory_path); + g_free(investigation_path); + g_free(temporary_parent); +} + +static void test_validate_directory_replaced_by_file(void) +{ + char *temporary_parent = NULL; + char *investigation_path = NULL; + char *directory_path = NULL; + GError *error = NULL; + + temporary_parent = g_dir_make_tmp( + "labfy-investigation-wrong-type-test-XXXXXX", + &error + ); + + assert(temporary_parent != NULL); + assert(error == NULL); + + investigation_path = investigation_project_create( + temporary_parent, + "Enquete_Mauvais_Type" + ); + + assert(investigation_path != NULL); + + directory_path = g_build_filename( + investigation_path, + "07_Notes", + NULL + ); + + assert(directory_path != NULL); + assert(g_rmdir(directory_path) == 0); + + assert( + g_file_set_contents( + directory_path, + "faux dossier\n", + -1, + &error + ) + ); + + assert(error == NULL); + + assert( + !investigation_project_validate( + investigation_path + ) + ); + + assert( + test_remove_path_recursively( + temporary_parent + ) + ); + + g_free(directory_path); + g_free(investigation_path); + g_free(temporary_parent); +} + int main(void) { test_create_valid_investigation(); @@ -455,6 +705,12 @@ int main(void) test_missing_parent_directory(); test_parent_is_file(); + test_validate_created_investigation(); + test_validate_invalid_paths(); + test_validate_missing_database(); + test_validate_missing_directory(); + test_validate_directory_replaced_by_file(); + printf( "InvestigationProject : tous les tests sont valides.\n" );