From a0e6743ac8e39fdb256535a15eafe937f2c8d363 Mon Sep 17 00:00:00 2001 From: grayTerminal-sh Date: Mon, 13 Jul 2026 16:14:41 +0200 Subject: [PATCH] feat(core): build investigation tree from filesystem --- Makefile | 19 +- docs/tickets/closed/TICKET-010.md | 243 ++++++++++++++++ include/core/investigation_tree_builder.h | 36 +++ labfy-investigation | Bin 54160 -> 58664 bytes src/core/investigation_tree_builder.c | 285 ++++++++++++++++++ tests/test_investigation_node | Bin 17496 -> 17496 bytes tests/test_investigation_tree_builder | Bin 0 -> 22744 bytes tests/test_investigation_tree_builder.c | 338 ++++++++++++++++++++++ tests/test_investigation_tree_model | Bin 17312 -> 17312 bytes 9 files changed, 917 insertions(+), 4 deletions(-) create mode 100644 docs/tickets/closed/TICKET-010.md create mode 100644 include/core/investigation_tree_builder.h create mode 100644 src/core/investigation_tree_builder.c create mode 100755 tests/test_investigation_tree_builder create mode 100644 tests/test_investigation_tree_builder.c diff --git a/Makefile b/Makefile index f5307e6..259c841 100644 --- a/Makefile +++ b/Makefile @@ -19,9 +19,9 @@ TEST_CFLAGS = -std=c17 \ -Wextra \ -Werror \ -Iinclude \ - $(shell $(PKG_CONFIG) --cflags glib-2.0) + $(shell $(PKG_CONFIG) --cflags glib-2.0 gio-2.0) -TEST_LDFLAGS = $(shell $(PKG_CONFIG) --libs glib-2.0) +TEST_LDFLAGS = $(shell $(PKG_CONFIG) --libs glib-2.0 gio-2.0) SRC := $(shell find src -name "*.c") @@ -31,6 +31,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 all: $(TARGET) @@ -48,10 +49,19 @@ $(TEST_TREE_MODEL): \ src/core/investigation_tree_model.c $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -test: $(TEST_NODE) $(TEST_TREE_MODEL) +$(TEST_TREE_BUILDER): \ + tests/test_investigation_tree_builder.c \ + src/core/investigation_node.c \ + src/core/investigation_tree_model.c \ + src/core/investigation_tree_builder.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) \ + $(shell $(PKG_CONFIG) --libs gio-2.0) + +test: $(TEST_NODE) $(TEST_TREE_MODEL) $(TEST_TREE_BUILDER) @echo "Exécution des tests..." @./$(TEST_NODE) @./$(TEST_TREE_MODEL) + @./$(TEST_TREE_BUILDER) @echo "Tous les tests sont valides." %.o: %.c @@ -63,6 +73,7 @@ run: $(TARGET) clean: rm -f $(OBJ) $(TARGET) \ $(TEST_NODE) \ - $(TEST_TREE_MODEL) + $(TEST_TREE_MODEL) \ + $(TEST_TREE_BUILDER) .PHONY: clean run test diff --git a/docs/tickets/closed/TICKET-010.md b/docs/tickets/closed/TICKET-010.md new file mode 100644 index 0000000..b4aae49 --- /dev/null +++ b/docs/tickets/closed/TICKET-010.md @@ -0,0 +1,243 @@ +# Ticket #010 + +## Titre + +Construire l'arborescence d'une enquête depuis le système de fichiers. + +--- + +## Objectif + +Créer le module `InvestigationTreeBuilder`. + +Ce module doit parcourir récursivement le dossier racine d'une enquête et +construire un `InvestigationTreeModel` représentant son contenu. + +--- + +## Responsabilités + +Le module `InvestigationTreeBuilder` doit : + +- recevoir le chemin racine d'une enquête ; +- vérifier que ce chemin désigne un dossier existant ; +- créer le nœud racine ; +- parcourir récursivement les dossiers ; +- créer un `InvestigationNode` pour chaque dossier ; +- créer un `InvestigationNode` pour chaque fichier ; +- assembler les relations parent/enfants ; +- retourner un `InvestigationTreeModel` complet ; +- nettoyer toutes les ressources en cas d'erreur. + +--- + +## Hors périmètre + +Ce ticket ne doit pas : + +- afficher l'arborescence avec GTK ; +- modifier le système de fichiers ; +- créer ou supprimer des fichiers ; +- trier les éléments ; +- filtrer les fichiers cachés ; +- surveiller les changements du disque ; +- suivre les liens symboliques ; +- communiquer avec SQLite. + +--- + +## Architecture + +```text +Investigation + │ + ▼ +InvestigationTreeBuilder + │ + ▼ +InvestigationTreeModel + │ + ▼ +InvestigationNode +``` + +Le module appartient à la couche `core`. + +Il peut utiliser GLib et GIO, mais ne doit jamais dépendre de GTK. + +--- + +## Fichiers concernés + +```text +include/core/investigation_tree_builder.h +src/core/investigation_tree_builder.c +``` + +Les tests seront placés dans : + +```text +tests/test_investigation_tree_builder.c +``` + +--- + +## Interface publique attendue + +```c +InvestigationTreeModel *investigation_tree_builder_build( + const char *root_path +); +``` + +--- + +## Principe de propriété + +En cas de succès, la fonction retourne un nouveau +`InvestigationTreeModel`. + +Le code appelant devient propriétaire du modèle retourné et doit le libérer +avec : + +```c +investigation_tree_model_free(tree_model); +``` + +En cas d'échec, la fonction retourne `NULL` et doit avoir libéré toutes les +ressources créées pendant la construction. + +--- + +## Comportement attendu + +À partir de : + +```text +Enquete_Test/ +├── 00_BaseDeDonnees/ +│ └── Enquete.sqlite +├── 01_Preuves_Originales/ +│ ├── Captures_Ecran/ +│ └── Emails/ +└── README.md +``` + +le module doit construire en mémoire : + +```text +Enquete_Test +├── 00_BaseDeDonnees +│ └── Enquete.sqlite +├── 01_Preuves_Originales +│ ├── Captures_Ecran +│ └── Emails +└── README.md +``` + +--- + +## Gestion des liens symboliques + +Les liens symboliques ne sont pas suivis. + +Cette règle évite : + +- les boucles récursives ; +- la sortie involontaire du dossier d'enquête ; +- l'analyse de fichiers extérieurs à l'enquête. + +Leur prise en charge éventuelle fera l'objet d'un ticket distinct. + +--- + +## Dépendances + +- C17 ; +- GLib ; +- GIO. + +Aucune dépendance GTK ou SQLite. + +--- + +## Contraintes techniques + +- aucune variable globale ; +- aucune modification du système de fichiers ; +- parcours récursif ; +- utilisation de `GFile` et `GFileEnumerator` ; +- libération correcte des `GObject` avec `g_object_unref()` ; +- respect de la règle « le propriétaire détruit » ; +- documentation Doxygen ; +- compilation sans warning ; +- aucune fuite mémoire. + +--- + +## Cas à gérer + +- chemin valide ; +- chemin relatif ; +- chemin `NULL` ; +- chemin vide ; +- chemin inexistant ; +- chemin désignant un fichier ; +- dossier vide ; +- plusieurs niveaux de sous-dossiers ; +- fichiers et dossiers mélangés ; +- erreur rencontrée pendant le parcours. + +--- + +## Critères d'acceptation + +- [ ] Le projet compile sans warning. +- [ ] Un dossier valide produit un modèle. +- [ ] Le nom du dossier racine est correct. +- [ ] Les dossiers produisent des nœuds de type `DIRECTORY`. +- [ ] Les fichiers produisent des nœuds de type `FILE`. +- [ ] Les relations parent/enfants sont correctes. +- [ ] Plusieurs niveaux de profondeur sont pris en charge. +- [ ] Un dossier vide est pris en charge. +- [ ] Un chemin invalide retourne `NULL`. +- [ ] Les liens symboliques ne sont pas suivis. +- [ ] Toutes les ressources sont libérées en cas d'erreur. +- [ ] Aucun code GTK. +- [ ] Aucun code SQLite. +- [ ] `make test` exécute le nouveau test. + +--- + +## Tests + +Le test doit créer une arborescence temporaire isolée : + +```text +TestCase/ +├── DirectoryA/ +│ └── FileA.txt +├── DirectoryB/ +└── RootFile.md +``` + +Il doit vérifier : + +- le nom du nœud racine ; +- le nombre d'enfants de la racine ; +- la présence des deux dossiers ; +- la présence du fichier racine ; +- la présence de `FileA.txt` dans `DirectoryA` ; +- le type de chaque nœud ; +- le parent de chaque enfant ; +- la gestion d'un dossier vide ; +- les chemins invalides ; +- la destruction complète du modèle. + +--- + +## Commit attendu + +```text +feat(core): build investigation tree from filesystem +``` diff --git a/include/core/investigation_tree_builder.h b/include/core/investigation_tree_builder.h new file mode 100644 index 0000000..dd47ecc --- /dev/null +++ b/include/core/investigation_tree_builder.h @@ -0,0 +1,36 @@ +/****************************************************************************** + * @file investigation_tree_builder.h + * @brief Interface publique du constructeur d'arborescence d'une enquête. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_INVESTIGATION_TREE_BUILDER_H +#define LABFY_INVESTIGATION_INVESTIGATION_TREE_BUILDER_H + +#include "core/investigation_tree_model.h" + +/** + * @brief Construit le modèle d'arborescence d'une enquête depuis le disque. + * + * Le chemin fourni doit désigner un dossier existant. + * + * La fonction parcourt récursivement son contenu et crée : + * + * - un nœud racine pour le dossier sélectionné ; + * - un nœud de type DIRECTORY pour chaque sous-dossier ; + * - un nœud de type FILE pour chaque fichier. + * + * Les liens symboliques ne sont pas suivis. + * + * Le code appelant devient propriétaire du modèle retourné et doit le + * libérer avec investigation_tree_model_free(). + * + * @param root_path Chemin du dossier racine à parcourir. + * + * @return Un nouveau modèle d'arborescence, ou NULL si le chemin est invalide + * ou si une erreur empêche la construction du modèle. + */ +InvestigationTreeModel *investigation_tree_builder_build( + const char *root_path +); + +#endif diff --git a/labfy-investigation b/labfy-investigation index 521ce432c8617ce06da5a2b955702476a02b9021..6a7dc5b1a3069519e700542a95303197009905df 100755 GIT binary patch delta 16963 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 DxMEtO diff --git a/src/core/investigation_tree_builder.c b/src/core/investigation_tree_builder.c new file mode 100644 index 0000000..3c76f54 --- /dev/null +++ b/src/core/investigation_tree_builder.c @@ -0,0 +1,285 @@ +/****************************************************************************** + * @file investigation_tree_builder.c + * @brief Construction d'une arborescence d'enquête depuis le système de fichiers. + ******************************************************************************/ + +#include "core/investigation_tree_builder.h" + +#include "core/investigation_node.h" +#include "core/investigation_tree_model.h" + +#include + +#include +#include + +/** + * @brief Attributs demandés à GIO pendant le parcours d'un dossier. + */ +#define INVESTIGATION_TREE_FILE_ATTRIBUTES \ + G_FILE_ATTRIBUTE_STANDARD_NAME "," \ + G_FILE_ATTRIBUTE_STANDARD_TYPE "," \ + G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK + +/** + * @brief Construit récursivement les enfants d'un nœud dossier. + * + * @param directory Dossier GIO actuellement parcouru. + * @param parent_node Nœud représentant ce dossier dans notre modèle. + * @param error Adresse recevant une éventuelle erreur GIO. + * + * @return true si le parcours s'est terminé correctement, sinon false. + */ +static bool investigation_tree_builder_build_children( + GFile *directory, + InvestigationNode *parent_node, + GError **error +) +{ + GFileEnumerator *enumerator = NULL; + GFileInfo *file_info = NULL; + bool success = true; + + if (directory == NULL || parent_node == NULL) + { + return false; + } + + enumerator = g_file_enumerate_children( + directory, + INVESTIGATION_TREE_FILE_ATTRIBUTES, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, + error + ); + + if (enumerator == NULL) + { + return false; + } + + while (success) + { + const char *child_name = NULL; + GFileType child_file_type; + GFile *child_file = NULL; + InvestigationNode *child_node = NULL; + InvestigationNodeType child_node_type; + + file_info = g_file_enumerator_next_file( + enumerator, + NULL, + error + ); + + if (file_info == NULL) + { + /* + * NULL signifie soit que le dossier est entièrement parcouru, + * soit qu'une erreur a eu lieu. + */ + if (error != NULL && *error != NULL) + { + success = false; + } + + break; + } + + /* + * Les liens symboliques sont volontairement ignorés afin d'éviter + * les boucles et les sorties involontaires du dossier d'enquête. + */ + if (g_file_info_get_is_symlink(file_info)) + { + g_object_unref(file_info); + file_info = NULL; + continue; + } + + child_name = g_file_info_get_name(file_info); + + if (child_name == NULL || child_name[0] == '\0') + { + success = false; + g_object_unref(file_info); + file_info = NULL; + break; + } + + child_file_type = g_file_info_get_file_type(file_info); + + if (child_file_type == G_FILE_TYPE_DIRECTORY) + { + child_node_type = INVESTIGATION_NODE_DIRECTORY; + } + else + { + child_node_type = INVESTIGATION_NODE_FILE; + } + + child_node = investigation_node_new( + child_name, + child_node_type + ); + + if (child_node == NULL) + { + success = false; + g_object_unref(file_info); + file_info = NULL; + break; + } + + /* + * En cas de succès, parent_node devient propriétaire de child_node. + */ + if (!investigation_node_add_child(parent_node, child_node)) + { + investigation_node_free(child_node); + success = false; + g_object_unref(file_info); + file_info = NULL; + break; + } + + /* + * Seuls les dossiers sont parcourus récursivement. + */ + if (child_node_type == INVESTIGATION_NODE_DIRECTORY) + { + child_file = g_file_get_child( + directory, + child_name + ); + + if (child_file == NULL) + { + success = false; + g_object_unref(file_info); + file_info = NULL; + break; + } + + success = investigation_tree_builder_build_children( + child_file, + child_node, + error + ); + + g_object_unref(child_file); + } + + g_object_unref(file_info); + file_info = NULL; + } + + /* + * Si la boucle a été interrompue avant la libération de file_info, + * cette vérification garantit un nettoyage correct. + */ + if (file_info != NULL) + { + g_object_unref(file_info); + } + + g_object_unref(enumerator); + + return success; +} + +InvestigationTreeModel *investigation_tree_builder_build( + const char *root_path +) +{ + GFile *root_file = NULL; + GFileType root_file_type; + char *root_name = NULL; + InvestigationNode *root_node = NULL; + InvestigationTreeModel *tree_model = NULL; + GError *error = NULL; + + if (root_path == NULL || root_path[0] == '\0') + { + return NULL; + } + + root_file = g_file_new_for_path(root_path); + + if (root_file == NULL) + { + return NULL; + } + + root_file_type = g_file_query_file_type( + root_file, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL + ); + + if (root_file_type != G_FILE_TYPE_DIRECTORY) + { + g_object_unref(root_file); + return NULL; + } + + root_name = g_file_get_basename(root_file); + + if (root_name == NULL || root_name[0] == '\0') + { + g_free(root_name); + g_object_unref(root_file); + return NULL; + } + + root_node = investigation_node_new( + root_name, + INVESTIGATION_NODE_DIRECTORY + ); + + g_free(root_name); + + if (root_node == NULL) + { + g_object_unref(root_file); + return NULL; + } + + if (!investigation_tree_builder_build_children( + root_file, + root_node, + &error + )) + { + if (error != NULL) + { + g_warning( + "Impossible de construire l'arborescence : %s", + error->message + ); + + g_clear_error(&error); + } + + investigation_node_free(root_node); + g_object_unref(root_file); + + return NULL; + } + + g_object_unref(root_file); + + tree_model = investigation_tree_model_new(root_node); + + if (tree_model == NULL) + { + /* + * Le transfert de propriété n'a pas eu lieu puisque la création + * du modèle a échoué. + */ + investigation_node_free(root_node); + return NULL; + } + + return tree_model; +} diff --git a/tests/test_investigation_node b/tests/test_investigation_node index fc3622417445c8d2310145009b444d4060c96060..9cb1d29e24fcecf36293296044ed6dd59953fec5 100755 GIT binary patch delta 1988 zcmZ`)eQZ-z6u;;7dvv28>t0{Cu27L};@WO)w{>M(#{f6Ow`d^IyuHz~RY$kAodhJ& z4U`X~0!K^~Ll!kUKSCIlI1wUZViscpM2#SSX(TaBOoq`63Fv+A-LfI^{&DU-zw
  • Z$8F72K_Qmr~8O?S^{IMtuPA>h5o80#2g?gW0MSr34q<-w@f3j9LLW4qP~ zb+V70bk(9@`?XFCP;aa3!2u<@M(2dLP}2GjYMlnsEpARH68J!)-`Fi?vg!C>Kej|X zLVq?bO6jz;O_bts%Gx6J_V#TGh$+0?SnBQ;XX0``D# zA9zhqh=x=#Y|xc7$(bSQ%On4u*gGU=tSUar-V9FbL!iNG11}iVh+@yTs1B`73h)U7 z4`v)R_ytx~2^wQ6N6zE$w1LM{26Lfv7OKN*6#dO$MvHL)m@sVg;|`;_uzeP%C^ux> zWH@M<17m2mT!BfvZb`r(Zm?Druk+E;?R&3e3)^38`ieULIAVCnx(gmbmu(&lW7Jm3 znQmjk7KHEcbz2yY;5ll0P-pMrbhj{R_i&+GxX)ezpW$(P7@o&p?H|C8h{cQGD1J`u zFisXnVL4VfqFljEj61^cExzHXgE#SlqYXBoqofYz;|gl8;?9z@P=U+&MQ{_-)Skm5 z)V_^h^NV3K8k~!v3YR*YWhS*B;#p@D`tUYg2e3)l4kvJ2XoT(f2emEeb2V`Kzp%&k zFr37ks|j}Cl&cY1@xjs{SNJDNrL}MgUoBk%PvZ}z15k_|W%Y0hhsyeC?dxUt!y$CK zgCOAw_c@SqW#zl(;+g77@89&;s!lmdl}y4 zRsj#g_b~h&_KZwH$V)QL70hT-?ou7c;V%tUMN2oXYF*jUHcMcI{=sY}He2!TDDHES zQoXo2nMx8ISYXGak#emf%T$RgXOvb}VzFa2mFajgB@Sj1{}pDpD=uXv8kf>p+IS|D z7DGPj#={Z2JC)cQ!+>}j@OLvz41 zAuB8$6FAUZZ<=P_G>%a(|22;1rkX$DOnE6E!LinQQ(nMFajLc6mjAf&KVJHSZCp~$ F{R;pavMc}q delta 1999 zcmZ`)du)?c6u;+Nx3+T|eXQ$q>sVmS0PVK!UG|>X$~&2fOyZ@ZWsK}a+bJWU0R|2+ zybd!63E7M>#76{5qN34?#w_>;Ga+#c(P$zDK@+ADgW%(Q-*?Mn#@qDVbARXY-Fxo6 zzdJPoQzLL_Qa_?&N*g&m{o>r@Vs((Lf4Co$)bT0Y3D%-X;Wa6|QvFR(y>3F{Je-98 z&DJEcHQ`X2J9l&Nhk}>OH*{q`U7#KNeAjvBGd&0K?X-4Woo+j#(O`up7n2$r2I#+) z{x#_~Jf*S0Aq0&T^0AEMDn*Yfa;YL;P~;-niaLroU7Mf7c~@_LACJXD(KzS$ZF+u7 zEFKLH4zMoX8|vxl-%!f?NFRttc_%5SOPwKq(EHy-tfd@RZ6XQCr#nZZZM+}d^r-o1u3(hpyLgi1ZoFt+0!vYATLOA) zvo*+`B=_S5o1f0t-=rI`-o6b+@qK$8bl`6!-RN=DNI%}fO^zqvI3^tRunuP&bubqf zIepSEzhlT*4jUj6qj5+{K-)0l7@B`LG9V`94^K%kwY6nuM!hcn*G- zR)}MZOQbstuP^pM673#0Okk~N6}WJphZ~Q{J4a1)Yzl9C8sQYydfhOLYrO}h$$Ffd zmUp%^02CN3^FlH)RCWcbfaS5&eN}VN;Zx4f9M)cvLexHwa9}oetr1`I72AU}G_fr9 zy;C(`5#H-@yuxb#gRF(j&Q>=GSajR*jL%BtC0TZqd<^8XewNP(IkS|_E*Cr;JFdij zh+8VGWz(_`eZ4bZ^>GW=TO#M|?@Cx!X|-GE*vnD7MCNi4<(I9nOp%)rNFTz(mDbQ+ z@;9;^c_~WM2>(%$=OdR2ydm&+2sbbX+tefg(cKZVofOx+Sm0-IhGzA#$o9IZq~KBZ znKI`~!a62ax>v;htjzYdEb}Frm{Iy~C36Tj(axLG-J}&NS}kcsvew)#TcynV*G{DoN6|tzFr?BG`)4H48D*?@jBAJV*TME>d`MU@#tQN^zjf zhddGv#Y2p5?C$Hvj#?)!t25()+5+T$y_s{_s8TW`GTzWTfDLtpc%#TN+1w%rv|w=~I-S#52v+`U!D5CUB;? R+LXGxsW+J3+Pank>0c4Tx>o=I diff --git a/tests/test_investigation_tree_builder b/tests/test_investigation_tree_builder new file mode 100755 index 0000000000000000000000000000000000000000..fd9d9016dee9be0479cb68587ac4f6ce7b70868d GIT binary patch literal 22744 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 XcwMg6rTySs_@ir;#ta9*!5aSyspyY! literal 0 HcmV?d00001 diff --git a/tests/test_investigation_tree_builder.c b/tests/test_investigation_tree_builder.c new file mode 100644 index 0000000..801dee7 --- /dev/null +++ b/tests/test_investigation_tree_builder.c @@ -0,0 +1,338 @@ +/****************************************************************************** + * @file test_investigation_tree_builder.c + * @brief Tests d'intégration du module InvestigationTreeBuilder. + ******************************************************************************/ + +#include "core/investigation_node.h" +#include "core/investigation_tree_builder.h" +#include "core/investigation_tree_model.h" + +#include +#include +#include + +#include +#include + +/** + * @brief Recherche un enfant par son nom. + * + * @param parent Nœud dans lequel effectuer la recherche. + * @param name Nom recherché. + * + * @return L'enfant correspondant, ou NULL s'il n'existe pas. + */ +static const InvestigationNode *test_find_child( + const InvestigationNode *parent, + const char *name +) +{ + size_t child_count = 0; + + if (parent == NULL || name == NULL) + { + return NULL; + } + + child_count = investigation_node_get_children_count(parent); + + for (size_t index = 0; index < child_count; ++index) + { + const InvestigationNode *child = NULL; + const char *child_name = NULL; + + child = investigation_node_get_child(parent, index); + + if (child == NULL) + { + continue; + } + + child_name = investigation_node_get_name(child); + + if (child_name != NULL && strcmp(child_name, name) == 0) + { + return child; + } + } + + return NULL; +} + +/** + * @brief Crée un fichier texte utilisé par le test. + */ +static void test_create_file(const char *file_path) +{ + GError *error = NULL; + + assert(file_path != NULL); + + assert( + g_file_set_contents( + file_path, + "test\n", + -1, + &error + ) + ); + + assert(error == NULL); +} + +/** + * @brief Vérifie la construction récursive d'une arborescence. + */ +static void test_build_tree_from_filesystem(void) +{ + char *root_path = NULL; + char *root_name = NULL; + + char *directory_a_path = NULL; + char *directory_b_path = NULL; + char *file_a_path = NULL; + char *root_file_path = NULL; + + GError *error = NULL; + + InvestigationTreeModel *tree_model = NULL; + const InvestigationNode *root_node = NULL; + const InvestigationNode *directory_a_node = NULL; + const InvestigationNode *directory_b_node = NULL; + const InvestigationNode *file_a_node = NULL; + const InvestigationNode *root_file_node = NULL; + + /* + * GLib crée un dossier temporaire unique, par exemple : + * + * /tmp/labfy-investigation-test-ABC123 + */ + root_path = g_dir_make_tmp( + "labfy-investigation-test-XXXXXX", + &error + ); + + assert(root_path != NULL); + assert(error == NULL); + + directory_a_path = g_build_filename( + root_path, + "DirectoryA", + NULL + ); + + directory_b_path = g_build_filename( + root_path, + "DirectoryB", + NULL + ); + + file_a_path = g_build_filename( + directory_a_path, + "FileA.txt", + NULL + ); + + root_file_path = g_build_filename( + root_path, + "RootFile.md", + NULL + ); + + assert(g_mkdir(directory_a_path, 0700) == 0); + assert(g_mkdir(directory_b_path, 0700) == 0); + + test_create_file(file_a_path); + test_create_file(root_file_path); + + /* + * Arborescence créée : + * + * racine temporaire/ + * ├── DirectoryA/ + * │ └── FileA.txt + * ├── DirectoryB/ + * └── RootFile.md + */ + tree_model = investigation_tree_builder_build(root_path); + + assert(tree_model != NULL); + + root_node = investigation_tree_model_get_root(tree_model); + + assert(root_node != NULL); + assert( + investigation_node_get_type(root_node) == + INVESTIGATION_NODE_DIRECTORY + ); + + root_name = g_path_get_basename(root_path); + + assert(root_name != NULL); + assert( + strcmp( + investigation_node_get_name(root_node), + root_name + ) == 0 + ); + + assert(investigation_node_get_children_count(root_node) == 3); + + /* + * L'ordre retourné par le système de fichiers n'est pas garanti. + * On recherche donc chaque enfant par son nom. + */ + directory_a_node = test_find_child( + root_node, + "DirectoryA" + ); + + directory_b_node = test_find_child( + root_node, + "DirectoryB" + ); + + root_file_node = test_find_child( + root_node, + "RootFile.md" + ); + + assert(directory_a_node != NULL); + assert(directory_b_node != NULL); + assert(root_file_node != NULL); + + assert( + investigation_node_get_type(directory_a_node) == + INVESTIGATION_NODE_DIRECTORY + ); + + assert( + investigation_node_get_type(directory_b_node) == + INVESTIGATION_NODE_DIRECTORY + ); + + assert( + investigation_node_get_type(root_file_node) == + INVESTIGATION_NODE_FILE + ); + + assert( + investigation_node_get_parent(directory_a_node) == + root_node + ); + + assert( + investigation_node_get_parent(directory_b_node) == + root_node + ); + + assert( + investigation_node_get_parent(root_file_node) == + root_node + ); + + /* + * DirectoryB est vide. + */ + assert( + investigation_node_get_children_count(directory_b_node) == 0 + ); + + /* + * DirectoryA contient FileA.txt. + */ + assert( + investigation_node_get_children_count(directory_a_node) == 1 + ); + + file_a_node = test_find_child( + directory_a_node, + "FileA.txt" + ); + + assert(file_a_node != NULL); + + assert( + investigation_node_get_type(file_a_node) == + INVESTIGATION_NODE_FILE + ); + + assert( + investigation_node_get_parent(file_a_node) == + directory_a_node + ); + + /* + * Le modèle possède la racine et toute son arborescence. + */ + investigation_tree_model_free(tree_model); + + /* + * Nettoyage du système de fichiers dans l'ordre inverse + * de la création. + */ + assert(g_remove(file_a_path) == 0); + assert(g_remove(root_file_path) == 0); + assert(g_rmdir(directory_a_path) == 0); + assert(g_rmdir(directory_b_path) == 0); + assert(g_rmdir(root_path) == 0); + + g_free(root_file_path); + g_free(file_a_path); + g_free(directory_b_path); + g_free(directory_a_path); + g_free(root_name); + g_free(root_path); +} + +/** + * @brief Vérifie les chemins invalides. + */ +static void test_invalid_paths(void) +{ + char *temporary_file_path = NULL; + GError *error = NULL; + + assert(investigation_tree_builder_build(NULL) == NULL); + assert(investigation_tree_builder_build("") == NULL); + + assert( + investigation_tree_builder_build( + "/chemin/qui/nexiste/pas" + ) == NULL + ); + + temporary_file_path = g_build_filename( + g_get_tmp_dir(), + "labfy-investigation-builder-file-test.txt", + NULL + ); + + test_create_file(temporary_file_path); + + /* + * Le chemin existe, mais il désigne un fichier et non un dossier. + */ + assert( + investigation_tree_builder_build( + temporary_file_path + ) == NULL + ); + + assert(g_remove(temporary_file_path) == 0); + + g_free(temporary_file_path); + g_clear_error(&error); +} + +int main(void) +{ + test_build_tree_from_filesystem(); + test_invalid_paths(); + + printf( + "InvestigationTreeBuilder : tous les tests sont valides.\n" + ); + + return 0; +} diff --git a/tests/test_investigation_tree_model b/tests/test_investigation_tree_model index 298935d2dc89aee38f334f9d7711aa256b15b4d9..a439b7694364b47c544a14c3cee1f1c986b8223c 100755 GIT binary patch delta 1941 zcmZ`)U2IfE6rS0i?k==te|z`R$aV{Ai|O6&wx!)-?UsVwCKQUP2^evD+r2GI+HLJ_ zgS923#E69$TMqhwL>EZ3CM1@w;l~J}60y-lP-6r%KA2#LmKPIEsbZbG_gtt(=V8u# z-}&au%$YmqrVr5c0XlxoHfbfShmAY7`8{lJOUn7!>40 zeDS`rkW?-u`=oNx6YuEm>)5SyrtWjFEoaTXib~S7xp_k)3rQiVN=qp3)ns5ovYdvE zgmTtL39+N{gNZkC2r-gUk#&_USlD0M$2@zBw+TdBP0p}H#^EP5jFy5k6K zgtgA~2FqX2?W`^S{NCbX^w6gH=-8*XPw_lO$3Bj}YJNIT9bJizeS3QWE;~!;D9kx4 z>1Ob|o-~;6LEIIhU&1k06&;4JT+dSxHoB_~Gk?HdcORXCJMM?+QE+)GXcshjuF|fI zP|!kYG@}UX3*b^&N$~>To7(i0h}(0adpn@S0(&4*bS~>GCM$L~}4e)0v@QA@vZlg3SB1W%WJUhxN>87Ijl*YL}E1SLmdTZLdg&*vBMMO}O!9Mc=w{ztXn_b2x`4SD34 zphwlL0V570??d+*^_nlLdi4zAbw08ak+eoofaX?K_dE9%XLSyvmXq&Yn&3OsmB37uKr7*R zr4VdD&6_;?YI|8TR1xa((;9|oDveFcKNKcbGscTu?-^Ryk6p8%~Af}f#x1mo*j z{E74G8QLQ#M{`Gw*}RM&UA@h{xOxb^<;d^+SG3K@XYiz@sNV+ubwXy>x(*6oSC{5* z+_9yhrMYoQSrq#RQ_0BE3U$9uPSg~_?wS%fQ7gdZnw4-Syb2zxDV82oMYVRa=B>Q2 z#^MP!m{k6|lnQHOa!TeZc_77?elR~6PQW=*JWBROVvsegl8Q4RrzAq*@qxn}ngdYTGQL ySvb$pJR<(CInrPIxlH(pvj&@9kJE5BQfVH!H$K2nr%J4USUz~yf delta 1856 zcmZ`)eQZ-z6u;-KU0d9+pL^}bJ`D>Xv|G2aw#!@x16t!4#6iS}*M}{mx-Z(!IN}s> zIwmnP4j7^Yw|~qMNCawv8Zku;Eb$K-Vj>15MnWV+6J}x}0fOhfce{)kZ}aZG=lp)> zynD~>{k_RCm>h$HIm>=C)7t)tpT?@Qt8^pV`tUd@>Gm|f335$NuyTTxA8&)sx(%5x z;W+$nwy?++#^HiW=jOMS?cJYGb^0fV&y1`;{Nz-yt^-dNbmJzY`;ftajRq%X4Q`Ck z^C~?7qZ_9UZa9cwkf9vwsqfO}Nv&V6^>1kXN)<&jMO+J%FXp_bZ=j#2(n>PTIbJiC zuSunov7tfM!~2xp-hmhEct6bt(@Cx*lgcnxVllFJCgX82@Kj}5I9(w;=Gl%u5i3S7y zVv@1QTvY6y=PkryvkWF&YWBe*++y~5iu4ezkIZ8hXHLs+nGRVWf%8~oy#e3jU)DH` z;4`-6&S$+ukBr|)7e@}XULfOvD}qOChv6|?VQ-OszmGlkP|b<^4<1AgtotW2e(~l> z+Dc^no5*{{Eye2WLS+2Mn;+u0_GO^pANEFAiPetv(v5qVaQNXg9&__7YC`+cfVN}LxKa3qSgOU3M%{BW!pPdCW00;d|~Xot$76?|HvbE*V& zhwyKA>5fX_f1Uj5yB4nJfSS#7TttnCY<>~-oreA(&`SdC7U-;s8m;v2)bxF`0:f6w5uiW;vIs_o5qpJp}c$$}dpqUwR$e2tWJQbW822|&gfd4X?`5HhJ1K6P*bBx)8J4#70%0!DnUFR zQ_>3KI}`m0JRc}E_r%jYIWUmMXrK&-gAV*SP=O~xmO{=|CjK2QqN9XC9s(ln& zfl|O*O0?7f-=*}&dgF{oyGTQ0iGDtmiep{Kh35hlwc1K}yg$Y`-;qk`D$DwmgsP}> z>WcLtCw>)Nu zqD$M}>a&chnn!IEhg+L1)54m@FUYzgEV{^K<*)bAOz?@bk|nRPS-cZ&w#xb17Nkp2ZX^pmCl