diff --git a/Makefile b/Makefile index b352b95..d797e70 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,8 @@ TEST_INVESTIGATION_RECORD = tests/test_investigation_record TEST_INVESTIGATION_DAO := tests/test_investigation_dao TEST_INVESTIGATION_SESSION := tests/test_investigation_session TEST_BACKGROUND_TASK := tests/test_background_task +TEST_TASK_MANAGER := tests/test_task_manager +TEST_TOOL_REGISTRY := tests/test_tool_registry all: $(TARGET) @@ -143,6 +145,17 @@ $(TEST_BACKGROUND_TASK): \ src/core/background_task.c $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) +$(TEST_TASK_MANAGER): \ + tests/test_task_manager.c \ + src/core/task_manager.c \ + src/core/background_task.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + +$(TEST_TOOL_REGISTRY): \ + tests/test_tool_registry.c \ + src/core/tool_registry.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + test: \ $(TEST_NODE) \ $(TEST_TREE_MODEL) \ @@ -155,7 +168,9 @@ test: \ $(TEST_INVESTIGATION_RECORD) \ $(TEST_INVESTIGATION_DAO) \ $(TEST_INVESTIGATION_SESSION) \ - $(TEST_BACKGROUND_TASK) + $(TEST_BACKGROUND_TASK) \ + $(TEST_TASK_MANAGER) \ + $(TEST_TOOL_REGISTRY) @echo "Exécution des tests..." @./$(TEST_NODE) @./$(TEST_TREE_MODEL) @@ -169,6 +184,8 @@ test: \ @$(TEST_INVESTIGATION_DAO) @$(TEST_INVESTIGATION_SESSION) @$(TEST_BACKGROUND_TASK) + @$(TEST_TASK_MANAGER) + @$(TEST_TOOL_REGISTRY) @echo "Tous les tests sont valides." %.o: %.c @@ -190,6 +207,8 @@ clean: $(TEST_INVESTIGATION_RECORD) \ $(TEST_INVESTIGATION_DAO) \ $(TEST_INVESTIGATION_SESSION) \ - $(TEST_BACKGROUND_TASK) + $(TEST_BACKGROUND_TASK) \ + $(TEST_TASK_MANAGER) \ + $(TEST_TOOL_REGISTRY) .PHONY: clean run test diff --git a/docs/tickets/closed/TICKET-036.md b/docs/tickets/closed/TICKET-036.md new file mode 100644 index 0000000..c65bbc5 --- /dev/null +++ b/docs/tickets/closed/TICKET-036.md @@ -0,0 +1,825 @@ +# TICKET-036 — Registre des outils et dépendances externes + +## Statut + +À faire + +## Priorité + +Haute + +## Objectif + +Créer un module central capable de représenter les outils externes utilisés par Labfy Investigation et de déterminer s’ils sont disponibles sur la machine. + +Ce registre deviendra la source unique permettant à l’application de savoir : + +- quel outil est demandé par une fonctionnalité ; +- si cet outil est obligatoire ou optionnel ; +- sous quel nom il est recherché dans le `PATH` ; +- où se trouve son exécutable ; +- si sa version est connue ; +- pourquoi une fonctionnalité doit être désactivée lorsqu’une dépendance manque. + +Le module doit rester indépendant de GTK. + +--- + +## Contexte + +Labfy Investigation utilisera progressivement plusieurs outils externes : + +- outils DNS ; +- outils RDAP et WHOIS ; +- outils TLS ; +- outils HTTP ; +- outils de traitement de métadonnées ; +- outils de conversion ou d’analyse ; +- adaptateurs spécialisés OSINT. + +Ces outils ne seront pas tous installés sur chaque machine. + +La cible Ubuntu de la gendarmerie peut également disposer : + +- de dépôts limités ; +- d’une liste de paquets spécifique ; +- d’outils installés dans des chemins non standards ; +- de versions différentes de celles présentes sur Arch Linux. + +Le code métier ne doit donc jamais supposer qu’un exécutable est disponible. + +--- + +## Périmètre + +Ce ticket doit fournir : + +1. un registre opaque `ToolRegistry` ; +2. une représentation opaque ou privée d’un outil enregistré ; +3. l’enregistrement d’un outil à partir de métadonnées stables ; +4. la détection de l’exécutable dans le `PATH` ; +5. la mémorisation du chemin détecté ; +6. la distinction entre dépendance obligatoire et optionnelle ; +7. la recherche d’un outil par identifiant ; +8. l’énumération des outils enregistrés ; +9. la détection des dépendances obligatoires manquantes ; +10. un champ permettant de mémoriser une version lorsqu’elle sera détectée ; +11. des tests unitaires sans dépendre des outils réellement installés sur la machine. + +--- + +## Hors périmètre + +Ce ticket ne doit pas encore : + +- créer d’interface GTK ; +- lancer une commande OSINT réelle ; +- interpréter la sortie de `dig`, `whois`, `curl` ou `openssl` ; +- exécuter automatiquement `--version` ; +- installer des paquets ; +- modifier les dépôts du système ; +- télécharger un outil manquant ; +- utiliser une commande construite sous forme de chaîne shell ; +- contenir une liste définitive des outils OSINT de l’application. + +L’exécution contrôlée des programmes externes sera traitée dans un ticket dédié. + +--- + +## Fichiers attendus + +```text +include/core/tool_registry.h +src/core/tool_registry.c +tests/test_tool_registry.c +``` + +Le `Makefile` devra être mis à jour pour compiler le module et son test. + +--- + +## Contraintes générales + +- Standard C17. +- Compilation avec : + +```text +-Wall -Wextra -Wpedantic -Werror +``` + +- Utiliser GLib. +- Respecter les conventions du projet : + - fichiers en `snake_case` ; + - fonctions préfixées par `tool_registry_` ou `tool_info_` ; + - noms de variables explicites ; + - structure publique opaque ; + - aucune variable globale mutable ; + - aucune dépendance à GTK. +- Ne jamais construire une commande shell. +- Ne jamais appeler `system()`, `popen()` ou équivalent. +- Ne pas coder en dur un chemin comme `/usr/bin/dig`. + +--- + +## Modèle de données + +### ToolRequirement +nvim.lsp.b_248_save BufWritePost + + [vim.lsp: textDocument/didSave handler] + Modifié la dernière fois dans ~/.dotfiles/nvim/.config/nvim/init.lua (run Nvim with -V1 for more details +) +nvim.lsp.b_249_save BufWritePost + + [vim.lsp: textDocument/didSave handler] + Modifié la dernière fois dans ~/.dotfiles/nvim/.config/nvim/init.lua (run Nvim with -V1 for more details +) +nvim.lsp.b_252_save BufWritePost + + [vim.lsp: textDocument/didSave handler] + Modifié la dernière fois dans ~/.dotfiles/nvim/.config/nvim/init.lua (run Nvim with -V1 for more details +) +nvim.lsp.b_255_save BufWritePost + + [vim.lsp: textDocument/didSave handler] + Modifié la dernière fois dans ~/.dotfiles/nvim/.config/nvim/init.lua (run Nvim with -V1 for more details +) +nvim.lsp.b_259_save BufWritePost + + [vim.lsp: textDocument/didSave handler] + Modifié la dernière fois dans ~/.dotfiles/nvim/.config/nvim/init.lua (run Nvim with -V1 for more details +) +nvim.lsp.b_262_save BufWritePost + + [vim.lsp: textDocument/didSave handler] + Modifié la dernière fois dans ~/.dotfiles/nvim/.config/nvim/init.lua (run Nvim with -V1 for more details +) +nvim.lsp.b_273_save BufWritePost + + + +G_BEGIN_DECLS + +/** + * @brief Importance d’un outil externe pour l’application. + */ +typedef enum +{ + /** + * L’absence de l’outil ne bloque pas l’application. + */ + TOOL_REQUIREMENT_OPTIONAL, + + /** + * L’outil est indispensable à la fonctionnalité concernée. + */ + TOOL_REQUIREMENT_REQUIRED +} ToolRequirement; + +/** + * @brief État de disponibilité d’un outil externe. + */ +typedef enum +{ + /** + * L’outil n’a pas encore été recherché. + */ + TOOL_AVAILABILITY_UNKNOWN, + + /** + * L’exécutable a été trouvé. + */ + TOOL_AVAILABILITY_AVAILABLE, + + /** + * L’exécutable n’a pas été trouvé. + */ + TOOL_AVAILABILITY_MISSING +} ToolAvailability; + +/** + * @brief Erreurs produites par ToolRegistry. + */ +typedef enum +{ + /** + * Un argument transmis à une fonction est invalide. + */ + TOOL_REGISTRY_ERROR_INVALID_ARGUMENT, + + /** + * Un outil possède le même identifiant qu’un outil déjà enregistré. + */ + TOOL_REGISTRY_ERROR_DUPLICATE_IDENTIFIER, + + /** + * L’outil demandé n’existe pas dans le registre. + */ + TOOL_REGISTRY_ERROR_NOT_FOUND +} ToolRegistryError; + +/** + * @brief Domaine d’erreur du registre d’outils. + */ +#define TOOL_REGISTRY_ERROR \ + tool_registry_error_quark() + +/** + * @brief Registre opaque des outils externes. + */ +typedef struct ToolRegistry ToolRegistry; + +/** + * @brief Informations opaques décrivant un outil externe. + */ +typedef struct ToolInfo ToolInfo; + +/** + * @brief Retourne le domaine d’erreur du registre. + * + * @return Quark GLib du domaine d’erreur. + */ +GQuark tool_registry_error_quark(void); + +/** + * @brief Crée un registre d’outils vide. + * + * @return Nouveau registre, ou NULL en cas d’échec. + */ +ToolRegistry *tool_registry_new(void); + +/** + * @brief Libère un registre et tous les outils qu’il contient. + * + * @param tool_registry Registre à libérer, ou NULL. + */ +void tool_registry_free( + ToolRegistry *tool_registry +); + +/** + * @brief Enregistre un nouvel outil externe. + * + * Le registre duplique toutes les chaînes reçues. + * + * L’outil commence avec : + * + * - une disponibilité inconnue ; + * - aucun chemin résolu ; + * - aucune version détectée. + * + * @param tool_registry Registre cible. + * @param identifier Identifiant interne unique. + * @param display_name Nom affiché à l’utilisateur. + * @param executable_name Nom de l’exécutable recherché dans le PATH. + * @param requirement Importance de la dépendance. + * @param error Emplacement facultatif pour l’erreur. + * + * @return TRUE en cas de succès, sinon FALSE. + */ +gboolean tool_registry_register( + ToolRegistry *tool_registry, + const char *identifier, + const char *display_name, + const char *executable_name, + ToolRequirement requirement, + GError **error +); + +/** + * @brief Recherche tous les exécutables enregistrés dans le PATH. + * + * Chaque ancien chemin résolu est remplacé par le résultat de la nouvelle + * détection. + * + * @param tool_registry Registre à actualiser. + * @param error Emplacement facultatif pour l’erreur. + * + * @return TRUE lorsque le rafraîchissement a pu être effectué. + */ +gboolean tool_registry_refresh( + ToolRegistry *tool_registry, + GError **error +); + +/** + * @brief Retourne le nombre d’outils enregistrés. + * + * @param tool_registry Registre consulté. + * + * @return Nombre d’outils, ou zéro si le registre est NULL. + */ +gsize tool_registry_get_count( + const ToolRegistry *tool_registry +); + +/** + * @brief Retourne un outil à partir de son index. + * + * Le pointeur retourné est emprunté. Il ne doit pas être libéré. + * + * @param tool_registry Registre consulté. + * @param index Index de l’outil. + * + * @return Outil correspondant, ou NULL si l’index est invalide. + */ +const ToolInfo *tool_registry_get_tool( + const ToolRegistry *tool_registry, + gsize index +); + +/** + * @brief Recherche un outil à partir de son identifiant. + * + * Le pointeur retourné est emprunté. Il ne doit pas être libéré. + * + * @param tool_registry Registre consulté. + * @param identifier Identifiant recherché. + * + * @return Outil correspondant, ou NULL s’il est absent. + */ +const ToolInfo *tool_registry_find( + const ToolRegistry *tool_registry, + const char *identifier +); + +/** + * @brief Mémorise la version détectée d’un outil. + * + * Une version NULL ou vide efface la version actuellement enregistrée. + * + * @param tool_registry Registre contenant l’outil. + * @param identifier Identifiant de l’outil. + * @param detected_version Version détectée, ou NULL. + * @param error Emplacement facultatif pour l’erreur. + * + * @return TRUE en cas de succès, sinon FALSE. + */ +gboolean tool_registry_set_version( + ToolRegistry *tool_registry, + const char *identifier, + const char *detected_version, + GError **error +); + +/** + * @brief Indique si une dépendance obligatoire est explicitement absente. + * + * Les outils encore dans l’état UNKNOWN ne sont pas considérés ici comme + * explicitement absents. + * + * @param tool_registry Registre consulté. + * + * @return TRUE si au moins un outil obligatoire est MISSING. + */ +gboolean tool_registry_has_missing_required_tools( + const ToolRegistry *tool_registry +); + +/** + * @brief Indique si toutes les dépendances obligatoires sont disponibles. + * + * Un outil obligatoire dans l’état UNKNOWN empêche cette fonction de + * retourner TRUE. + * + * @param tool_registry Registre consulté. + * + * @return TRUE si tous les outils obligatoires sont AVAILABLE. + */ +gboolean tool_registry_all_required_tools_available( + const ToolRegistry *tool_registry +); + +/** + * @brief Retourne l’identifiant interne d’un outil. + * + * La chaîne retournée est empruntée. + * + * @param tool_info Outil consulté. + * + * @return Identifiant, ou NULL. + */ +const char *tool_info_get_identifier( + const ToolInfo *tool_info +); + +/** + * @brief Retourne le nom affiché d’un outil. + * + * La chaîne retournée est empruntée. + * + * @param tool_info Outil consulté. + * + * @return Nom affiché, ou NULL. + */ +const char *tool_info_get_display_name( + const ToolInfo *tool_info +); + +/** + * @brief Retourne le nom de l’exécutable d’un outil. + * + * La chaîne retournée est empruntée. + * + * @param tool_info Outil consulté. + * + * @return Nom de l’exécutable, ou NULL. + */ +const char *tool_info_get_executable_name( + const ToolInfo *tool_info +); + +/** + * @brief Retourne l’importance d’un outil. + * + * @param tool_info Outil consulté. + * + * @return Importance de l’outil. + */ +ToolRequirement tool_info_get_requirement( + const ToolInfo *tool_info +); + +/** + * @brief Retourne l’état de disponibilité d’un outil. + * + * @param tool_info Outil consulté. + * + * @return État de disponibilité. + */ +ToolAvailability tool_info_get_availability( + const ToolInfo *tool_info +); + +/** + * @brief Retourne le chemin absolu détecté d’un outil. + * + * La chaîne retournée est empruntée. + * + * @param tool_info Outil consulté. + * + * @return Chemin détecté, ou NULL. + */ +const char *tool_info_get_resolved_path( + const ToolInfo *tool_info +); + +/** + * @brief Retourne la version détectée d’un outil. + * + * La chaîne retournée est empruntée. + * + * @param tool_info Outil consulté. + * + * @return Version détectée, ou NULL. + */ +const char *tool_info_get_detected_version( + const ToolInfo *tool_info +); + +G_END_DECLS + +#endif diff --git a/labfy-investigation b/labfy-investigation index 19ab421..ddbf124 100755 Binary files a/labfy-investigation and b/labfy-investigation differ diff --git a/src/core/tool_registry.c b/src/core/tool_registry.c new file mode 100644 index 0000000..e6848b9 --- /dev/null +++ b/src/core/tool_registry.c @@ -0,0 +1,635 @@ +/****************************************************************************** + * @file tool_registry.c + * @brief Implémentation du registre des outils externes. + ******************************************************************************/ + +#include "core/tool_registry.h" + +/** + * @struct ToolInfo + * @brief Informations internes décrivant un outil externe. + */ +struct ToolInfo +{ + char *identifier; + char *display_name; + char *executable_name; + + ToolRequirement requirement; + ToolAvailability availability; + + char *resolved_path; + char *detected_version; +}; + +/** + * @struct ToolRegistry + * @brief Registre interne des outils externes. + */ +struct ToolRegistry +{ + GPtrArray *tools; +}; + +/** + * @brief Vérifie qu'une chaîne est définie et non vide. + * + * @param text Chaîne à vérifier. + * + * @return TRUE si la chaîne est valide. + */ +static gboolean tool_registry_string_is_valid( + const char *text +) +{ + return text != NULL && + text[0] != '\0'; +} + +/** + * @brief Vérifie qu'une importance de dépendance est valide. + * + * @param requirement Valeur à vérifier. + * + * @return TRUE si la valeur est reconnue. + */ +static gboolean tool_registry_requirement_is_valid( + ToolRequirement requirement +) +{ + return requirement == TOOL_REQUIREMENT_OPTIONAL || + requirement == TOOL_REQUIREMENT_REQUIRED; +} + +/** + * @brief Libère un outil et toutes ses chaînes. + * + * Cette fonction est utilisée comme fonction de destruction du GPtrArray. + * + * @param user_data Pointeur vers ToolInfo. + */ +static void tool_info_free( + gpointer user_data +) +{ + ToolInfo *tool_info = user_data; + + if (tool_info == NULL) + { + return; + } + + g_clear_pointer( + &tool_info->identifier, + g_free + ); + + g_clear_pointer( + &tool_info->display_name, + g_free + ); + + g_clear_pointer( + &tool_info->executable_name, + g_free + ); + + g_clear_pointer( + &tool_info->resolved_path, + g_free + ); + + g_clear_pointer( + &tool_info->detected_version, + g_free + ); + + g_free( + tool_info + ); +} + +/** + * @brief Recherche un outil modifiable dans le registre. + * + * @param tool_registry Registre consulté. + * @param identifier Identifiant recherché. + * + * @return Outil correspondant, ou NULL. + */ +static ToolInfo *tool_registry_find_internal( + const ToolRegistry *tool_registry, + const char *identifier +) +{ + guint index = 0; + + if (tool_registry == NULL || + tool_registry->tools == NULL || + !tool_registry_string_is_valid(identifier)) + { + return NULL; + } + + for (index = 0; + index < tool_registry->tools->len; + index++) + { + ToolInfo *tool_info = NULL; + + tool_info = g_ptr_array_index( + tool_registry->tools, + index + ); + + if (tool_info != NULL && + g_strcmp0( + tool_info->identifier, + identifier + ) == 0) + { + return tool_info; + } + } + + return NULL; +} + +GQuark tool_registry_error_quark(void) +{ + return g_quark_from_static_string( + "labfy-investigation-tool-registry-error" + ); +} + +ToolRegistry *tool_registry_new(void) +{ + ToolRegistry *tool_registry = NULL; + + tool_registry = g_new0( + ToolRegistry, + 1 + ); + + tool_registry->tools = + g_ptr_array_new_with_free_func( + tool_info_free + ); + + return tool_registry; +} + +void tool_registry_free( + ToolRegistry *tool_registry +) +{ + if (tool_registry == NULL) + { + return; + } + + if (tool_registry->tools != NULL) + { + g_ptr_array_unref( + tool_registry->tools + ); + + tool_registry->tools = NULL; + } + + g_free( + tool_registry + ); +} + +gboolean tool_registry_register( + ToolRegistry *tool_registry, + const char *identifier, + const char *display_name, + const char *executable_name, + ToolRequirement requirement, + GError **error +) +{ + ToolInfo *tool_info = NULL; + + g_return_val_if_fail( + error == NULL || *error == NULL, + FALSE + ); + + if (tool_registry == NULL || + tool_registry->tools == NULL || + !tool_registry_string_is_valid(identifier) || + !tool_registry_string_is_valid(display_name) || + !tool_registry_string_is_valid(executable_name) || + !tool_registry_requirement_is_valid(requirement)) + { + g_set_error_literal( + error, + TOOL_REGISTRY_ERROR, + TOOL_REGISTRY_ERROR_INVALID_ARGUMENT, + "Les arguments fournis au registre d'outils sont invalides." + ); + + return FALSE; + } + + if (tool_registry_find_internal( + tool_registry, + identifier + ) != NULL) + { + g_set_error( + error, + TOOL_REGISTRY_ERROR, + TOOL_REGISTRY_ERROR_DUPLICATE_IDENTIFIER, + "Un outil portant l'identifiant '%s' est déjà enregistré.", + identifier + ); + + return FALSE; + } + + tool_info = g_new0( + ToolInfo, + 1 + ); + + tool_info->identifier = g_strdup( + identifier + ); + + tool_info->display_name = g_strdup( + display_name + ); + + tool_info->executable_name = g_strdup( + executable_name + ); + + tool_info->requirement = requirement; + tool_info->availability = + TOOL_AVAILABILITY_UNKNOWN; + + tool_info->resolved_path = NULL; + tool_info->detected_version = NULL; + + g_ptr_array_add( + tool_registry->tools, + tool_info + ); + + return TRUE; +} + +gboolean tool_registry_refresh( + ToolRegistry *tool_registry, + GError **error +) +{ + guint index = 0; + + g_return_val_if_fail( + error == NULL || *error == NULL, + FALSE + ); + + if (tool_registry == NULL || + tool_registry->tools == NULL) + { + g_set_error_literal( + error, + TOOL_REGISTRY_ERROR, + TOOL_REGISTRY_ERROR_INVALID_ARGUMENT, + "Le registre d'outils est invalide." + ); + + return FALSE; + } + + for (index = 0; + index < tool_registry->tools->len; + index++) + { + ToolInfo *tool_info = NULL; + char *found_path = NULL; + char *canonical_path = NULL; + + tool_info = g_ptr_array_index( + tool_registry->tools, + index + ); + + if (tool_info == NULL) + { + continue; + } + + found_path = g_find_program_in_path( + tool_info->executable_name + ); + + g_clear_pointer( + &tool_info->resolved_path, + g_free + ); + + if (found_path == NULL) + { + tool_info->availability = + TOOL_AVAILABILITY_MISSING; + + continue; + } + + /* + * g_find_program_in_path() retourne une nouvelle chaîne. + * + * La canonicalisation garantit que le registre mémorise un + * chemin absolu et normalisé. + */ + canonical_path = g_canonicalize_filename( + found_path, + NULL + ); + + g_free( + found_path + ); + + tool_info->resolved_path = + canonical_path; + + tool_info->availability = + TOOL_AVAILABILITY_AVAILABLE; + } + + return TRUE; +} + +gsize tool_registry_get_count( + const ToolRegistry *tool_registry +) +{ + if (tool_registry == NULL || + tool_registry->tools == NULL) + { + return 0; + } + + return tool_registry->tools->len; +} + +const ToolInfo *tool_registry_get_tool( + const ToolRegistry *tool_registry, + gsize index +) +{ + if (tool_registry == NULL || + tool_registry->tools == NULL || + index >= tool_registry->tools->len) + { + return NULL; + } + + return g_ptr_array_index( + tool_registry->tools, + (guint) index + ); +} + +const ToolInfo *tool_registry_find( + const ToolRegistry *tool_registry, + const char *identifier +) +{ + return tool_registry_find_internal( + tool_registry, + identifier + ); +} + +gboolean tool_registry_set_version( + ToolRegistry *tool_registry, + const char *identifier, + const char *detected_version, + GError **error +) +{ + ToolInfo *tool_info = NULL; + + g_return_val_if_fail( + error == NULL || *error == NULL, + FALSE + ); + + if (tool_registry == NULL || + tool_registry->tools == NULL || + !tool_registry_string_is_valid(identifier)) + { + g_set_error_literal( + error, + TOOL_REGISTRY_ERROR, + TOOL_REGISTRY_ERROR_INVALID_ARGUMENT, + "Le registre ou l'identifiant de l'outil est invalide." + ); + + return FALSE; + } + + tool_info = tool_registry_find_internal( + tool_registry, + identifier + ); + + if (tool_info == NULL) + { + g_set_error( + error, + TOOL_REGISTRY_ERROR, + TOOL_REGISTRY_ERROR_NOT_FOUND, + "Aucun outil portant l'identifiant '%s' n'est enregistré.", + identifier + ); + + return FALSE; + } + + g_clear_pointer( + &tool_info->detected_version, + g_free + ); + + if (tool_registry_string_is_valid( + detected_version + )) + { + tool_info->detected_version = + g_strdup( + detected_version + ); + } + + return TRUE; +} + +gboolean tool_registry_has_missing_required_tools( + const ToolRegistry *tool_registry +) +{ + guint index = 0; + + if (tool_registry == NULL || + tool_registry->tools == NULL) + { + return FALSE; + } + + for (index = 0; + index < tool_registry->tools->len; + index++) + { + const ToolInfo *tool_info = NULL; + + tool_info = g_ptr_array_index( + tool_registry->tools, + index + ); + + if (tool_info != NULL && + tool_info->requirement == + TOOL_REQUIREMENT_REQUIRED && + tool_info->availability == + TOOL_AVAILABILITY_MISSING) + { + return TRUE; + } + } + + return FALSE; +} + +gboolean tool_registry_all_required_tools_available( + const ToolRegistry *tool_registry +) +{ + guint index = 0; + + if (tool_registry == NULL || + tool_registry->tools == NULL) + { + return FALSE; + } + + for (index = 0; + index < tool_registry->tools->len; + index++) + { + const ToolInfo *tool_info = NULL; + + tool_info = g_ptr_array_index( + tool_registry->tools, + index + ); + + if (tool_info != NULL && + tool_info->requirement == + TOOL_REQUIREMENT_REQUIRED && + tool_info->availability != + TOOL_AVAILABILITY_AVAILABLE) + { + return FALSE; + } + } + + /* + * Lorsqu'aucun outil obligatoire n'est enregistré, toutes les + * dépendances obligatoires sont considérées comme satisfaites. + */ + return TRUE; +} + +const char *tool_info_get_identifier( + const ToolInfo *tool_info +) +{ + if (tool_info == NULL) + { + return NULL; + } + + return tool_info->identifier; +} + +const char *tool_info_get_display_name( + const ToolInfo *tool_info +) +{ + if (tool_info == NULL) + { + return NULL; + } + + return tool_info->display_name; +} + +const char *tool_info_get_executable_name( + const ToolInfo *tool_info +) +{ + if (tool_info == NULL) + { + return NULL; + } + + return tool_info->executable_name; +} + +ToolRequirement tool_info_get_requirement( + const ToolInfo *tool_info +) +{ + if (tool_info == NULL) + { + return TOOL_REQUIREMENT_OPTIONAL; + } + + return tool_info->requirement; +} + +ToolAvailability tool_info_get_availability( + const ToolInfo *tool_info +) +{ + if (tool_info == NULL) + { + return TOOL_AVAILABILITY_UNKNOWN; + } + + return tool_info->availability; +} + +const char *tool_info_get_resolved_path( + const ToolInfo *tool_info +) +{ + if (tool_info == NULL) + { + return NULL; + } + + return tool_info->resolved_path; +} + +const char *tool_info_get_detected_version( + const ToolInfo *tool_info +) +{ + if (tool_info == NULL) + { + return NULL; + } + + return tool_info->detected_version; +} diff --git a/tests/test_background_task b/tests/test_background_task new file mode 100755 index 0000000..0c76ca0 Binary files /dev/null and b/tests/test_background_task differ diff --git a/tests/test_database b/tests/test_database new file mode 100755 index 0000000..2a1ae25 Binary files /dev/null and b/tests/test_database differ diff --git a/tests/test_error b/tests/test_error new file mode 100755 index 0000000..1185635 Binary files /dev/null and b/tests/test_error differ diff --git a/tests/test_investigation_dao b/tests/test_investigation_dao new file mode 100755 index 0000000..0234ddd Binary files /dev/null and b/tests/test_investigation_dao differ diff --git a/tests/test_investigation_node b/tests/test_investigation_node new file mode 100755 index 0000000..b7614ef Binary files /dev/null and b/tests/test_investigation_node differ diff --git a/tests/test_investigation_project b/tests/test_investigation_project new file mode 100755 index 0000000..3bd6369 Binary files /dev/null and b/tests/test_investigation_project differ diff --git a/tests/test_investigation_record b/tests/test_investigation_record new file mode 100755 index 0000000..ba2880b Binary files /dev/null and b/tests/test_investigation_record differ diff --git a/tests/test_investigation_session b/tests/test_investigation_session new file mode 100755 index 0000000..04f8d66 Binary files /dev/null and b/tests/test_investigation_session differ diff --git a/tests/test_investigation_tree_builder b/tests/test_investigation_tree_builder new file mode 100755 index 0000000..d6cd484 Binary files /dev/null and b/tests/test_investigation_tree_builder differ diff --git a/tests/test_investigation_tree_model b/tests/test_investigation_tree_model new file mode 100755 index 0000000..2c823e1 Binary files /dev/null and b/tests/test_investigation_tree_model differ diff --git a/tests/test_statement b/tests/test_statement new file mode 100755 index 0000000..79b937b Binary files /dev/null and b/tests/test_statement differ diff --git a/tests/test_tool_registry b/tests/test_tool_registry new file mode 100755 index 0000000..75486bf Binary files /dev/null and b/tests/test_tool_registry differ diff --git a/tests/test_tool_registry.c b/tests/test_tool_registry.c new file mode 100644 index 0000000..9ebd5ee --- /dev/null +++ b/tests/test_tool_registry.c @@ -0,0 +1,1250 @@ +/****************************************************************************** + * @file test_tool_registry.c + * @brief Tests unitaires du registre des outils externes. + ******************************************************************************/ + +#include "core/tool_registry.h" + +#include +#include + +#include + +/** + * @struct ToolRegistryPathFixture + * @brief Environnement temporaire contenant un faux exécutable. + */ +typedef struct +{ + char *temporary_directory; + char *fake_executable_path; + char *original_path; +} ToolRegistryPathFixture; + +/** + * @brief Crée un faux exécutable. + * + * @param executable_path Chemin du fichier à créer. + */ +static void test_tool_registry_create_fake_executable( + const char *executable_path +) +{ + GError *error = NULL; + gboolean write_success = FALSE; + int chmod_result = 0; + + g_assert_nonnull( + executable_path + ); + + write_success = g_file_set_contents( + executable_path, + "#!/bin/sh\n" + "exit 0\n", + -1, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_true( + write_success + ); + + chmod_result = g_chmod( + executable_path, + S_IRUSR | + S_IWUSR | + S_IXUSR + ); + + g_assert_cmpint( + chmod_result, + ==, + 0 + ); +} + +/** + * @brief Prépare un PATH contenant uniquement un faux outil. + * + * @param fixture Fixture à initialiser. + * @param user_data Données inutilisées. + */ +static void test_tool_registry_path_fixture_setup( + ToolRegistryPathFixture *fixture, + gconstpointer user_data +) +{ + GError *error = NULL; + + (void) user_data; + + fixture->original_path = g_strdup( + g_getenv("PATH") + ); + + fixture->temporary_directory = + g_dir_make_tmp( + "labfy-tool-registry-XXXXXX", + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + fixture->temporary_directory + ); + + fixture->fake_executable_path = + g_build_filename( + fixture->temporary_directory, + "fake_osint_tool", + NULL + ); + + test_tool_registry_create_fake_executable( + fixture->fake_executable_path + ); + + g_assert_true( + g_setenv( + "PATH", + fixture->temporary_directory, + TRUE + ) + ); +} + +/** + * @brief Restaure le PATH et supprime les fichiers temporaires. + * + * @param fixture Fixture à nettoyer. + * @param user_data Données inutilisées. + */ +static void test_tool_registry_path_fixture_teardown( + ToolRegistryPathFixture *fixture, + gconstpointer user_data +) +{ + (void) user_data; + + if (fixture->original_path != NULL) + { + g_setenv( + "PATH", + fixture->original_path, + TRUE + ); + } + else + { + g_unsetenv( + "PATH" + ); + } + + if (fixture->fake_executable_path != NULL) + { + g_remove( + fixture->fake_executable_path + ); + } + + if (fixture->temporary_directory != NULL) + { + g_rmdir( + fixture->temporary_directory + ); + } + + g_clear_pointer( + &fixture->original_path, + g_free + ); + + g_clear_pointer( + &fixture->fake_executable_path, + g_free + ); + + g_clear_pointer( + &fixture->temporary_directory, + g_free + ); +} + +/** + * @brief Vérifie la création d’un registre vide. + */ +static void test_tool_registry_construction(void) +{ + ToolRegistry *tool_registry = NULL; + + tool_registry = tool_registry_new(); + + g_assert_nonnull( + tool_registry + ); + + g_assert_cmpuint( + tool_registry_get_count( + tool_registry + ), + ==, + 0 + ); + + g_assert_null( + tool_registry_get_tool( + tool_registry, + 0 + ) + ); + + g_assert_null( + tool_registry_find( + tool_registry, + "unknown.tool" + ) + ); + + tool_registry_free( + tool_registry + ); + + tool_registry_free( + NULL + ); +} + +/** + * @brief Vérifie l’enregistrement et les accesseurs. + */ +static void test_tool_registry_register(void) +{ + ToolRegistry *tool_registry = NULL; + const ToolInfo *tool_info = NULL; + GError *error = NULL; + + tool_registry = tool_registry_new(); + + g_assert_true( + tool_registry_register( + tool_registry, + "dns.dig", + "dig", + "dig", + TOOL_REQUIREMENT_OPTIONAL, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_cmpuint( + tool_registry_get_count( + tool_registry + ), + ==, + 1 + ); + + tool_info = tool_registry_get_tool( + tool_registry, + 0 + ); + + g_assert_nonnull( + tool_info + ); + + g_assert_cmpstr( + tool_info_get_identifier( + tool_info + ), + ==, + "dns.dig" + ); + + g_assert_cmpstr( + tool_info_get_display_name( + tool_info + ), + ==, + "dig" + ); + + g_assert_cmpstr( + tool_info_get_executable_name( + tool_info + ), + ==, + "dig" + ); + + g_assert_cmpint( + tool_info_get_requirement( + tool_info + ), + ==, + TOOL_REQUIREMENT_OPTIONAL + ); + + g_assert_cmpint( + tool_info_get_availability( + tool_info + ), + ==, + TOOL_AVAILABILITY_UNKNOWN + ); + + g_assert_null( + tool_info_get_resolved_path( + tool_info + ) + ); + + g_assert_null( + tool_info_get_detected_version( + tool_info + ) + ); + + tool_registry_free( + tool_registry + ); +} + +/** + * @brief Vérifie que le registre duplique les chaînes reçues. + */ +static void test_tool_registry_string_ownership(void) +{ + ToolRegistry *tool_registry = NULL; + const ToolInfo *tool_info = NULL; + + char *identifier = NULL; + char *display_name = NULL; + char *executable_name = NULL; + + GError *error = NULL; + + identifier = g_strdup( + "network.fake" + ); + + display_name = g_strdup( + "Fake Tool" + ); + + executable_name = g_strdup( + "fake_tool" + ); + + tool_registry = tool_registry_new(); + + g_assert_true( + tool_registry_register( + tool_registry, + identifier, + display_name, + executable_name, + TOOL_REQUIREMENT_REQUIRED, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_free( + identifier + ); + + g_free( + display_name + ); + + g_free( + executable_name + ); + + tool_info = tool_registry_find( + tool_registry, + "network.fake" + ); + + g_assert_nonnull( + tool_info + ); + + g_assert_cmpstr( + tool_info_get_identifier( + tool_info + ), + ==, + "network.fake" + ); + + g_assert_cmpstr( + tool_info_get_display_name( + tool_info + ), + ==, + "Fake Tool" + ); + + g_assert_cmpstr( + tool_info_get_executable_name( + tool_info + ), + ==, + "fake_tool" + ); + + tool_registry_free( + tool_registry + ); +} + +/** + * @brief Vérifie le refus des identifiants en double. + */ +static void test_tool_registry_duplicate_identifier(void) +{ + ToolRegistry *tool_registry = NULL; + GError *error = NULL; + + tool_registry = tool_registry_new(); + + g_assert_true( + tool_registry_register( + tool_registry, + "dns.dig", + "dig", + "dig", + TOOL_REQUIREMENT_OPTIONAL, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_false( + tool_registry_register( + tool_registry, + "dns.dig", + "Autre outil", + "other_tool", + TOOL_REQUIREMENT_REQUIRED, + &error + ) + ); + + g_assert_error( + error, + TOOL_REGISTRY_ERROR, + TOOL_REGISTRY_ERROR_DUPLICATE_IDENTIFIER + ); + + g_clear_error( + &error + ); + + g_assert_cmpuint( + tool_registry_get_count( + tool_registry + ), + ==, + 1 + ); + + tool_registry_free( + tool_registry + ); +} + +/** + * @brief Vérifie les arguments invalides. + */ +static void test_tool_registry_invalid_arguments(void) +{ + ToolRegistry *tool_registry = NULL; + GError *error = NULL; + + tool_registry = tool_registry_new(); + + g_assert_false( + tool_registry_register( + NULL, + "dns.dig", + "dig", + "dig", + TOOL_REQUIREMENT_OPTIONAL, + &error + ) + ); + + g_assert_error( + error, + TOOL_REGISTRY_ERROR, + TOOL_REGISTRY_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + g_assert_false( + tool_registry_register( + tool_registry, + "", + "dig", + "dig", + TOOL_REQUIREMENT_OPTIONAL, + &error + ) + ); + + g_assert_error( + error, + TOOL_REGISTRY_ERROR, + TOOL_REGISTRY_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + g_assert_false( + tool_registry_register( + tool_registry, + "dns.dig", + "", + "dig", + TOOL_REQUIREMENT_OPTIONAL, + &error + ) + ); + + g_assert_error( + error, + TOOL_REGISTRY_ERROR, + TOOL_REGISTRY_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + g_assert_false( + tool_registry_register( + tool_registry, + "dns.dig", + "dig", + "", + TOOL_REQUIREMENT_OPTIONAL, + &error + ) + ); + + g_assert_error( + error, + TOOL_REGISTRY_ERROR, + TOOL_REGISTRY_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + g_assert_false( + tool_registry_register( + tool_registry, + "dns.dig", + "dig", + "dig", + (ToolRequirement) 999, + &error + ) + ); + + g_assert_error( + error, + TOOL_REGISTRY_ERROR, + TOOL_REGISTRY_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + g_assert_false( + tool_registry_refresh( + NULL, + &error + ) + ); + + g_assert_error( + error, + TOOL_REGISTRY_ERROR, + TOOL_REGISTRY_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + tool_registry_free( + tool_registry + ); +} + +/** + * @brief Vérifie la recherche par identifiant. + */ +static void test_tool_registry_find_tool(void) +{ + ToolRegistry *tool_registry = NULL; + const ToolInfo *tool_info = NULL; + GError *error = NULL; + + tool_registry = tool_registry_new(); + + g_assert_true( + tool_registry_register( + tool_registry, + "tls.openssl", + "OpenSSL", + "openssl", + TOOL_REQUIREMENT_REQUIRED, + &error + ) + ); + + g_assert_no_error( + error + ); + + tool_info = tool_registry_find( + tool_registry, + "tls.openssl" + ); + + g_assert_nonnull( + tool_info + ); + + g_assert_null( + tool_registry_find( + tool_registry, + "unknown.tool" + ) + ); + + g_assert_null( + tool_registry_find( + tool_registry, + NULL + ) + ); + + g_assert_null( + tool_registry_find( + tool_registry, + "" + ) + ); + + tool_registry_free( + tool_registry + ); +} + +/** + * @brief Vérifie la détection d’un faux exécutable. + */ +static void test_tool_registry_available_tool( + ToolRegistryPathFixture *fixture, + gconstpointer user_data +) +{ + ToolRegistry *tool_registry = NULL; + const ToolInfo *tool_info = NULL; + GError *error = NULL; + + (void) user_data; + + tool_registry = tool_registry_new(); + + g_assert_true( + tool_registry_register( + tool_registry, + "test.fake", + "Fake OSINT Tool", + "fake_osint_tool", + TOOL_REQUIREMENT_REQUIRED, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + tool_registry_refresh( + tool_registry, + &error + ) + ); + + g_assert_no_error( + error + ); + + tool_info = tool_registry_find( + tool_registry, + "test.fake" + ); + + g_assert_nonnull( + tool_info + ); + + g_assert_cmpint( + tool_info_get_availability( + tool_info + ), + ==, + TOOL_AVAILABILITY_AVAILABLE + ); + + g_assert_nonnull( + tool_info_get_resolved_path( + tool_info + ) + ); + + g_assert_cmpstr( + tool_info_get_resolved_path( + tool_info + ), + ==, + fixture->fake_executable_path + ); + + tool_registry_free( + tool_registry + ); +} + +/** + * @brief Vérifie la détection d’un outil absent. + */ +static void test_tool_registry_missing_tool(void) +{ + ToolRegistry *tool_registry = NULL; + const ToolInfo *tool_info = NULL; + GError *error = NULL; + + tool_registry = tool_registry_new(); + + g_assert_true( + tool_registry_register( + tool_registry, + "test.missing", + "Missing Tool", + "labfy_tool_that_must_not_exist_036", + TOOL_REQUIREMENT_OPTIONAL, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + tool_registry_refresh( + tool_registry, + &error + ) + ); + + g_assert_no_error( + error + ); + + tool_info = tool_registry_find( + tool_registry, + "test.missing" + ); + + g_assert_nonnull( + tool_info + ); + + g_assert_cmpint( + tool_info_get_availability( + tool_info + ), + ==, + TOOL_AVAILABILITY_MISSING + ); + + g_assert_null( + tool_info_get_resolved_path( + tool_info + ) + ); + + tool_registry_free( + tool_registry + ); +} + +/** + * @brief Vérifie les transitions de disponibilité. + */ +static void test_tool_registry_successive_refresh( + ToolRegistryPathFixture *fixture, + gconstpointer user_data +) +{ + ToolRegistry *tool_registry = NULL; + const ToolInfo *tool_info = NULL; + GError *error = NULL; + + (void) user_data; + + tool_registry = tool_registry_new(); + + g_assert_true( + tool_registry_register( + tool_registry, + "test.fake", + "Fake OSINT Tool", + "fake_osint_tool", + TOOL_REQUIREMENT_REQUIRED, + &error + ) + ); + + g_assert_no_error( + error + ); + + /* + * Première détection : le fichier existe. + */ + g_assert_true( + tool_registry_refresh( + tool_registry, + &error + ) + ); + + g_assert_no_error( + error + ); + + tool_info = tool_registry_find( + tool_registry, + "test.fake" + ); + + g_assert_cmpint( + tool_info_get_availability( + tool_info + ), + ==, + TOOL_AVAILABILITY_AVAILABLE + ); + + /* + * Deuxième détection : le fichier a été supprimé. + */ + g_assert_cmpint( + g_remove( + fixture->fake_executable_path + ), + ==, + 0 + ); + + g_assert_true( + tool_registry_refresh( + tool_registry, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_cmpint( + tool_info_get_availability( + tool_info + ), + ==, + TOOL_AVAILABILITY_MISSING + ); + + g_assert_null( + tool_info_get_resolved_path( + tool_info + ) + ); + + /* + * Troisième détection : le fichier est recréé. + */ + test_tool_registry_create_fake_executable( + fixture->fake_executable_path + ); + + g_assert_true( + tool_registry_refresh( + tool_registry, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_cmpint( + tool_info_get_availability( + tool_info + ), + ==, + TOOL_AVAILABILITY_AVAILABLE + ); + + g_assert_nonnull( + tool_info_get_resolved_path( + tool_info + ) + ); + + tool_registry_free( + tool_registry + ); +} + +/** + * @brief Vérifie le stockage et l’effacement d’une version. + */ +static void test_tool_registry_version(void) +{ + ToolRegistry *tool_registry = NULL; + const ToolInfo *tool_info = NULL; + GError *error = NULL; + + tool_registry = tool_registry_new(); + + g_assert_true( + tool_registry_register( + tool_registry, + "tls.openssl", + "OpenSSL", + "openssl", + TOOL_REQUIREMENT_OPTIONAL, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + tool_registry_set_version( + tool_registry, + "tls.openssl", + "3.5.1", + &error + ) + ); + + g_assert_no_error( + error + ); + + tool_info = tool_registry_find( + tool_registry, + "tls.openssl" + ); + + g_assert_cmpstr( + tool_info_get_detected_version( + tool_info + ), + ==, + "3.5.1" + ); + + g_assert_true( + tool_registry_set_version( + tool_registry, + "tls.openssl", + "3.6.0", + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_cmpstr( + tool_info_get_detected_version( + tool_info + ), + ==, + "3.6.0" + ); + + g_assert_true( + tool_registry_set_version( + tool_registry, + "tls.openssl", + NULL, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_null( + tool_info_get_detected_version( + tool_info + ) + ); + + g_assert_false( + tool_registry_set_version( + tool_registry, + "unknown.tool", + "1.0", + &error + ) + ); + + g_assert_error( + error, + TOOL_REGISTRY_ERROR, + TOOL_REGISTRY_ERROR_NOT_FOUND + ); + + g_clear_error( + &error + ); + + tool_registry_free( + tool_registry + ); +} + +/** + * @brief Vérifie l’état global des dépendances obligatoires. + */ +static void test_tool_registry_required_tools( + ToolRegistryPathFixture *fixture, + gconstpointer user_data +) +{ + ToolRegistry *tool_registry = NULL; + GError *error = NULL; + + (void) fixture; + (void) user_data; + + tool_registry = tool_registry_new(); + + g_assert_true( + tool_registry_all_required_tools_available( + tool_registry + ) + ); + + g_assert_false( + tool_registry_has_missing_required_tools( + tool_registry + ) + ); + + g_assert_true( + tool_registry_register( + tool_registry, + "test.required", + "Required Fake Tool", + "fake_osint_tool", + TOOL_REQUIREMENT_REQUIRED, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + tool_registry_register( + tool_registry, + "test.optional", + "Optional Missing Tool", + "labfy_optional_tool_that_must_not_exist_036", + TOOL_REQUIREMENT_OPTIONAL, + &error + ) + ); + + g_assert_no_error( + error + ); + + /* + * L’outil obligatoire est encore UNKNOWN. + */ + g_assert_false( + tool_registry_all_required_tools_available( + tool_registry + ) + ); + + g_assert_false( + tool_registry_has_missing_required_tools( + tool_registry + ) + ); + + g_assert_true( + tool_registry_refresh( + tool_registry, + &error + ) + ); + + g_assert_no_error( + error + ); + + /* + * L’outil obligatoire existe. + * L’outil optionnel absent ne bloque pas l’état global. + */ + g_assert_true( + tool_registry_all_required_tools_available( + tool_registry + ) + ); + + g_assert_false( + tool_registry_has_missing_required_tools( + tool_registry + ) + ); + + tool_registry_free( + tool_registry + ); +} + +int main( + int argc, + char **argv +) +{ + g_test_init( + &argc, + &argv, + NULL + ); + + g_test_add_func( + "/tool_registry/construction", + test_tool_registry_construction + ); + + g_test_add_func( + "/tool_registry/register", + test_tool_registry_register + ); + + g_test_add_func( + "/tool_registry/string_ownership", + test_tool_registry_string_ownership + ); + + g_test_add_func( + "/tool_registry/duplicate_identifier", + test_tool_registry_duplicate_identifier + ); + + g_test_add_func( + "/tool_registry/invalid_arguments", + test_tool_registry_invalid_arguments + ); + + g_test_add_func( + "/tool_registry/find", + test_tool_registry_find_tool + ); + + g_test_add( + "/tool_registry/available_tool", + ToolRegistryPathFixture, + NULL, + test_tool_registry_path_fixture_setup, + test_tool_registry_available_tool, + test_tool_registry_path_fixture_teardown + ); + + g_test_add_func( + "/tool_registry/missing_tool", + test_tool_registry_missing_tool + ); + + g_test_add( + "/tool_registry/successive_refresh", + ToolRegistryPathFixture, + NULL, + test_tool_registry_path_fixture_setup, + test_tool_registry_successive_refresh, + test_tool_registry_path_fixture_teardown + ); + + g_test_add_func( + "/tool_registry/version", + test_tool_registry_version + ); + + g_test_add( + "/tool_registry/required_tools", + ToolRegistryPathFixture, + NULL, + test_tool_registry_path_fixture_setup, + test_tool_registry_required_tools, + test_tool_registry_path_fixture_teardown + ); + + return g_test_run(); +} diff --git a/tests/test_transaction b/tests/test_transaction new file mode 100755 index 0000000..3533f19 Binary files /dev/null and b/tests/test_transaction differ