feat(core): validate investigation project structure

This commit is contained in:
grayTerminal-sh 2026-07-14 22:41:51 +02:00
parent e5f3479f71
commit 08d568a002
6 changed files with 712 additions and 36 deletions

View file

@ -0,0 +1,254 @@
# Ticket #021
## Titre
Valider une enquête existante.
---
## Objectif
Ajouter au module `InvestigationProject` la capacité de vérifier quun 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 denquê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 dun dossier ;
- vérifier que le chemin existe ;
- vérifier quil désigne un dossier ;
- vérifier la présence de larborescence 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 nexiste 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` nest 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 larborescence.
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 lenquête dans lapplication.
---
## Gestion des erreurs
Dans ce ticket, la fonction retourne uniquement un booléen.
Les détails derreur 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 dacceptation
- [ ] 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 nest créé pendant la validation.
- [ ] Aucun élément nest supprimé pendant la validation.
- [ ] La structure de référence nest pas dupliquée.
- [ ] Aucune dépendance GTK.
- [ ] Aucun `Gtk-CRITICAL`.
---
## Commit attendu
```text
feat(core): validate investigation project structure
```

View file

@ -6,6 +6,8 @@
#ifndef LABFY_INVESTIGATION_INVESTIGATION_PROJECT_H
#define LABFY_INVESTIGATION_INVESTIGATION_PROJECT_H
#include <stdbool.h>
/**
* @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

Binary file not shown.

View file

@ -11,56 +11,84 @@
#include <glib.h>
#include <glib/gstdio.h>
/**
* @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 < 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;
}

Binary file not shown.

View file

@ -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"
);