feat(core): create investigation project structure
This commit is contained in:
parent
d5324f9573
commit
e5f3479f71
10 changed files with 1197 additions and 2 deletions
16
Makefile
16
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
|
||||
|
|
|
|||
257
docs/tickets/closed/TICKET-020.md
Normal file
257
docs/tickets/closed/TICKET-020.md
Normal file
|
|
@ -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
|
||||
```
|
||||
39
include/core/investigation_project.h
Normal file
39
include/core/investigation_project.h
Normal file
|
|
@ -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
|
||||
Binary file not shown.
424
src/core/investigation_project.c
Normal file
424
src/core/investigation_project.c
Normal file
|
|
@ -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 <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
BIN
tests/test_investigation_node
Executable file
BIN
tests/test_investigation_node
Executable file
Binary file not shown.
BIN
tests/test_investigation_project
Executable file
BIN
tests/test_investigation_project
Executable file
Binary file not shown.
463
tests/test_investigation_project.c
Normal file
463
tests/test_investigation_project.c
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
/******************************************************************************
|
||||
* @file test_investigation_project.c
|
||||
* @brief Tests d'intégration du module InvestigationProject.
|
||||
******************************************************************************/
|
||||
|
||||
#include "core/investigation_project.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
BIN
tests/test_investigation_tree_builder
Executable file
BIN
tests/test_investigation_tree_builder
Executable file
Binary file not shown.
BIN
tests/test_investigation_tree_model
Executable file
BIN
tests/test_investigation_tree_model
Executable file
Binary file not shown.
Loading…
Reference in a new issue