From 4efdfe4865df2f469a7a988a578ed73f8d1e625f Mon Sep 17 00:00:00 2001 From: grayTerminal-sh Date: Thu, 16 Jul 2026 13:38:15 +0200 Subject: [PATCH] feat(core): add investigation session loader --- Makefile | 21 +- docs/tickets/{open => closed}/TICKET-027.md | 0 docs/tickets/open/TICKET-028.md | 776 ++++++++++++++++++++ include/core/investigation_project.h | 66 +- include/core/investigation_session.h | 143 ++++ labfy-investigation | Bin 150216 -> 159784 bytes src/core/investigation_project.c | 162 +++- src/core/investigation_session.c | 374 ++++++++++ tests/test_investigation_project | Bin 50984 -> 51616 bytes tests/test_investigation_project.c | 180 +++++ tests/test_investigation_session | Bin 0 -> 56880 bytes tests/test_investigation_session.c | 684 +++++++++++++++++ 12 files changed, 2382 insertions(+), 24 deletions(-) rename docs/tickets/{open => closed}/TICKET-027.md (100%) create mode 100644 docs/tickets/open/TICKET-028.md create mode 100644 include/core/investigation_session.h create mode 100644 src/core/investigation_session.c create mode 100755 tests/test_investigation_session create mode 100644 tests/test_investigation_session.c diff --git a/Makefile b/Makefile index 32b3f2b..1dad8b8 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,7 @@ TEST_TRANSACTION = tests/test_transaction TEST_ERROR = tests/test_error TEST_INVESTIGATION_RECORD = tests/test_investigation_record TEST_INVESTIGATION_DAO := tests/test_investigation_dao +TEST_INVESTIGATION_SESSION := tests/test_investigation_session all: $(TARGET) @@ -123,6 +124,19 @@ $(TEST_INVESTIGATION_DAO): \ src/database/error.c $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 +$(TEST_INVESTIGATION_SESSION): \ + tests/test_investigation_session.c \ + src/core/investigation_session.c \ + src/core/investigation_project.c \ + src/dao/investigation_dao.c \ + src/models/investigation_record.c \ + src/database/database.c \ + src/database/schema.c \ + src/database/statement.c \ + src/database/transaction.c \ + src/database/error.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 + test: \ $(TEST_NODE) \ $(TEST_TREE_MODEL) \ @@ -133,7 +147,8 @@ test: \ $(TEST_TRANSACTION) \ $(TEST_ERROR) \ $(TEST_INVESTIGATION_RECORD) \ - $(TEST_INVESTIGATION_DAO) + $(TEST_INVESTIGATION_DAO) \ + $(TEST_INVESTIGATION_SESSION) @echo "Exécution des tests..." @./$(TEST_NODE) @./$(TEST_TREE_MODEL) @@ -145,6 +160,7 @@ test: \ @$(TEST_ERROR) @$(TEST_INVESTIGATION_RECORD) @$(TEST_INVESTIGATION_DAO) + @$(TEST_INVESTIGATION_SESSION) @echo "Tous les tests sont valides." %.o: %.c @@ -164,6 +180,7 @@ clean: $(TEST_TRANSACTION) \ $(TEST_ERROR) \ $(TEST_INVESTIGATION_RECORD) \ - $(TEST_INVESTIGATION_DAO) + $(TEST_INVESTIGATION_DAO) \ + $(TEST_INVESTIGATION_SESSION) .PHONY: clean run test diff --git a/docs/tickets/open/TICKET-027.md b/docs/tickets/closed/TICKET-027.md similarity index 100% rename from docs/tickets/open/TICKET-027.md rename to docs/tickets/closed/TICKET-027.md diff --git a/docs/tickets/open/TICKET-028.md b/docs/tickets/open/TICKET-028.md new file mode 100644 index 0000000..c314102 --- /dev/null +++ b/docs/tickets/open/TICKET-028.md @@ -0,0 +1,776 @@ +# Ticket #028 — Ajouter l’ouverture d’une enquête existante + +## Contexte + +Les tickets précédents ont permis de mettre en place : + +- la création transactionnelle d’une base d’enquête ; +- la couche `Database` ; +- les requêtes préparées `DatabaseStatement` ; +- la gestion des transactions et des erreurs ; +- le modèle `InvestigationRecord` ; +- le DAO `InvestigationDao` permettant de charger l’unique ligne de la table `investigation`. + +Le projet possède également un type `InvestigationProject` chargé de représenter les chemins du projet sur le système de fichiers, notamment : + +- le dossier racine de l’enquête ; +- le chemin du fichier `Enquete.sqlite`. + +Cependant, l’application ne possède pas encore d’objet représentant une enquête réellement ouverte pendant son exécution. + +Les différentes ressources sont encore séparées : + +```text +InvestigationProject +Database +InvestigationRecord +``` + +Il faut désormais les regrouper dans un contexte cohérent dont la durée de vie correspond à celle d’une enquête ouverte. + +## Objectif + +Créer un type opaque `InvestigationSession` chargé d’ouvrir une enquête existante et de conserver : + +- son contexte de fichiers `InvestigationProject` ; +- sa connexion `Database` ; +- ses informations persistées `InvestigationRecord`. + +L’ouverture doit vérifier que le dossier sélectionné correspond bien aux informations enregistrées dans la base SQLite. + +La connexion SQLite doit rester ouverte pendant toute la durée de vie de la session afin de permettre les futurs appels aux DAO. + +## Architecture attendue + +```text +Application + │ + ▼ +InvestigationSession + ├── InvestigationProject + ├── Database + └── InvestigationRecord + ▲ + │ + InvestigationDao + │ + ▼ + DatabaseStatement + │ + ▼ + SQLite +``` + +## Travail à réaliser + +### 1. Créer le type `InvestigationSession` + +Créer les fichiers : + +```text +include/core/investigation_session.h +src/core/investigation_session.c +``` + +Le type doit être opaque : + +```c +typedef struct InvestigationSession InvestigationSession; +``` + +Sa représentation privée doit contenir au minimum : + +```c +struct InvestigationSession +{ + InvestigationProject *project; + Database *database; + InvestigationRecord *record; +}; +``` + +Le header public ne doit pas exposer cette structure. + +### 2. Définir les erreurs d’ouverture + +Créer une énumération dédiée : + +```c +typedef enum +{ + INVESTIGATION_SESSION_ERROR_INVALID_ARGUMENT, + INVESTIGATION_SESSION_ERROR_ROOT_NOT_FOUND, + INVESTIGATION_SESSION_ERROR_DATABASE_NOT_FOUND, + INVESTIGATION_SESSION_ERROR_PROJECT, + INVESTIGATION_SESSION_ERROR_DATABASE, + INVESTIGATION_SESSION_ERROR_RECORD, + INVESTIGATION_SESSION_ERROR_ROOT_MISMATCH, + INVESTIGATION_SESSION_ERROR_MEMORY +} InvestigationSessionError; +``` + +Définir un domaine d’erreur GLib : + +```c +#define INVESTIGATION_SESSION_ERROR \ + investigation_session_error_quark() + +GQuark investigation_session_error_quark(void); +``` + +Les erreurs doivent être transmises avec un paramètre : + +```c +GError **error +``` + +Lorsqu’une erreur provenant de `Database` doit être propagée, son message doit être copié dans le `GError` avant la fermeture de la connexion. + +### 3. Ajouter la fonction d’ouverture + +Déclarer : + +```c +InvestigationSession *investigation_session_open( + const char *investigation_root_path, + GError **error +); +``` + +La fonction doit ouvrir une enquête déjà existante à partir de son dossier racine. + +Elle ne doit pas créer une nouvelle enquête. + +### 4. Valider les paramètres + +La fonction doit refuser : + +```text +investigation_root_path == NULL +investigation_root_path vide +``` + +Le code d’erreur attendu est : + +```c +INVESTIGATION_SESSION_ERROR_INVALID_ARGUMENT +``` + +Le paramètre `error` peut être `NULL`. + +Si `error` n’est pas `NULL`, il doit respecter les conventions GLib : + +```c +*error == NULL +``` + +au moment de l’appel. + +### 5. Vérifier le dossier racine + +Le chemin fourni doit correspondre à un dossier existant. + +La fonction doit vérifier : + +```c +G_FILE_TEST_IS_DIR +``` + +Un chemin inexistant ou qui ne représente pas un dossier doit produire : + +```c +INVESTIGATION_SESSION_ERROR_ROOT_NOT_FOUND +``` + +Le chemin doit être normalisé avec : + +```c +g_canonicalize_filename() +``` + +La session doit travailler à partir de ce chemin canonique. + +### 6. Construire `InvestigationProject` + +Réutiliser l’API existante de `InvestigationProject`. + +La logique de construction du chemin de la base ne doit pas être dupliquée dans `InvestigationSession`. + +Le chemin attendu reste géré par `InvestigationProject` : + +```text +/00_BaseDeDonnees/Enquete.sqlite +``` + +Si l’API actuelle de `InvestigationProject` ne permet pas de représenter un projet existant, elle peut être étendue de manière minimale. + +Aucune logique SQLite ne doit être ajoutée dans `InvestigationProject`. + +### 7. Vérifier le fichier SQLite + +Avant l’ouverture de la connexion, vérifier que le chemin retourné par `InvestigationProject` correspond à un fichier régulier : + +```c +G_FILE_TEST_IS_REGULAR +``` + +Si le fichier n’existe pas, retourner : + +```c +INVESTIGATION_SESSION_ERROR_DATABASE_NOT_FOUND +``` + +L’ouverture d’une enquête existante ne doit jamais créer silencieusement une nouvelle base vide. + +### 8. Ouvrir la connexion Database + +Utiliser : + +```c +database_open() +``` + +La fonction `investigation_session_open()` ne doit pas appeler directement : + +```c +sqlite3_open() +sqlite3_open_v2() +sqlite3_close() +``` + +Si `database_open()` échoue, retourner : + +```c +INVESTIGATION_SESSION_ERROR_DATABASE +``` + +La connexion doit rester ouverte si la session est créée avec succès. + +### 9. Charger l’enquête persistée + +Utiliser : + +```c +investigation_dao_load() +``` + +Le DAO doit retourner un `InvestigationRecord`. + +Si le chargement échoue : + +- récupérer le code et le message de la dernière erreur `Database` ; +- copier le message dans un `GError` ; +- retourner `NULL` ; +- fermer proprement la connexion ; +- libérer le projet ; +- ne laisser aucune ressource allouée. + +Le code d’erreur de session attendu est : + +```c +INVESTIGATION_SESSION_ERROR_RECORD +``` + +### 10. Vérifier la cohérence du chemin racine + +Le chemin sélectionné doit correspondre au champ persistant : + +```text +investigation.root_path +``` + +Comparer les versions canoniques de : + +```text +chemin racine sélectionné +chemin racine enregistré dans InvestigationRecord +``` + +La comparaison doit être faite après normalisation avec : + +```c +g_canonicalize_filename() +``` + +Si les chemins ne correspondent pas, l’ouverture doit échouer avec : + +```c +INVESTIGATION_SESSION_ERROR_ROOT_MISMATCH +``` + +Cette vérification évite d’ouvrir une base copiée ou déplacée sans détecter l’incohérence. + +Le déplacement volontaire d’une enquête sera traité dans un ticket distinct. + +### 11. Construire la session + +La session ne doit être créée qu’après validation complète : + +```text +dossier racine valide + ↓ +InvestigationProject valide + ↓ +fichier SQLite présent + ↓ +Database ouverte + ↓ +InvestigationRecord chargé + ↓ +chemin racine cohérent + ↓ +InvestigationSession créée +``` + +En cas d’échec d’allocation, retourner : + +```c +INVESTIGATION_SESSION_ERROR_MEMORY +``` + +### 12. Ajouter la fonction de fermeture + +Déclarer : + +```c +void investigation_session_close( + InvestigationSession *session +); +``` + +Cette fonction doit accepter `NULL`. + +Elle doit libérer toutes les ressources possédées par la session : + +```text +InvestigationRecord +Database +InvestigationProject +InvestigationSession +``` + +La session devient propriétaire de ces trois objets dès que son ouverture réussit. + +### 13. Ajouter les accesseurs + +Ajouter : + +```c +const InvestigationProject *investigation_session_get_project( + const InvestigationSession *session +); + +const InvestigationRecord *investigation_session_get_record( + const InvestigationSession *session +); + +Database *investigation_session_get_database( + InvestigationSession *session +); +``` + +Les pointeurs retournés appartiennent à la session et ne doivent pas être libérés par l’appelant. + +Les accesseurs doivent retourner `NULL` si la session reçue est `NULL`. + +`Database` reste non constante car les futurs DAO auront besoin d’une connexion modifiable. + +### 14. Interdire les dépendances SQLite et GTK + +Le module `InvestigationSession` ne doit pas inclure : + +```c +#include +#include +``` + +Il doit exclusivement utiliser les abstractions existantes : + +```text +InvestigationProject +Database +InvestigationDao +InvestigationRecord +GLib +``` + +## Tests à ajouter + +Créer : + +```text +tests/test_investigation_session.c +``` + +### Test d’ouverture valide + +Créer un dossier temporaire. + +Initialiser une base avec : + +```c +database_initialize() +``` + +Ouvrir ensuite l’enquête avec : + +```c +investigation_session_open() +``` + +Vérifier : + +- la session n’est pas `NULL` ; +- aucune erreur n’est produite ; +- le projet est disponible ; +- la connexion Database est disponible ; +- le record est disponible ; +- le nom de l’enquête est correct ; +- le chemin racine est correct ; +- l’UUID est valide ; +- `created_at` n’est pas vide ; +- `updated_at` n’est pas vide ; +- la connexion peut encore être utilisée par un DAO ; +- la fermeture libère correctement les ressources. + +### Test des paramètres invalides + +Vérifier : + +```c +investigation_session_open(NULL, &error) == NULL +investigation_session_open("", &error) == NULL +``` + +Le code attendu est : + +```c +INVESTIGATION_SESSION_ERROR_INVALID_ARGUMENT +``` + +Vérifier également le comportement avec : + +```c +error == NULL +``` + +### Test d’un dossier inexistant + +Utiliser un chemin inexistant. + +Vérifier : + +```text +résultat == NULL +erreur == INVESTIGATION_SESSION_ERROR_ROOT_NOT_FOUND +message non vide +``` + +### Test d’un chemin qui n’est pas un dossier + +Créer un fichier temporaire et utiliser son chemin comme racine. + +Vérifier : + +```text +résultat == NULL +erreur == INVESTIGATION_SESSION_ERROR_ROOT_NOT_FOUND +``` + +### Test d’une base absente + +Créer une structure de projet valide sans fichier : + +```text +00_BaseDeDonnees/Enquete.sqlite +``` + +Vérifier : + +```text +résultat == NULL +erreur == INVESTIGATION_SESSION_ERROR_DATABASE_NOT_FOUND +``` + +La fonction ne doit pas créer de nouveau fichier SQLite. + +### Test d’une base invalide + +Créer un fichier SQLite vide ou une base ne contenant pas la table : + +```text +investigation +``` + +Vérifier : + +```text +résultat == NULL +erreur == INVESTIGATION_SESSION_ERROR_RECORD +message non vide +``` + +### Test d’un chemin racine incohérent + +Créer une base avec un chemin racine enregistré différent du dossier utilisé pour l’ouverture. + +Vérifier : + +```text +résultat == NULL +erreur == INVESTIGATION_SESSION_ERROR_ROOT_MISMATCH +message non vide +``` + +### Test des accesseurs avec NULL + +Vérifier : + +```c +investigation_session_get_project(NULL) == NULL +investigation_session_get_record(NULL) == NULL +investigation_session_get_database(NULL) == NULL +``` + +Vérifier également : + +```c +investigation_session_close(NULL); +``` + +### Test de réutilisation du DAO + +Après l’ouverture valide d’une session, appeler de nouveau : + +```c +investigation_dao_load( + investigation_session_get_database(session) +); +``` + +Vérifier que la connexion reste fonctionnelle pendant toute la durée de vie de la session. + +## Gestion de la mémoire + +Toutes les sorties d’échec de `investigation_session_open()` doivent libérer les ressources déjà créées. + +Le nettoyage doit couvrir les cas suivants : + +```text +échec avant création du projet +échec après création du projet +échec après ouverture de Database +échec après chargement du record +échec lors de la comparaison des chemins +échec lors de l’allocation de la session +``` + +Aucun objet ne doit être libéré deux fois. + +Aucune ressource temporaire ne doit rester allouée : + +```text +chemins canoniques +messages copiés +GError temporaires +InvestigationProject +Database +InvestigationRecord +``` + +## Makefile + +Ajouter : + +```make +TEST_INVESTIGATION_SESSION := tests/test_investigation_session +``` + +Ajouter une règle compilant au minimum : + +```text +tests/test_investigation_session.c + +src/core/investigation_session.c +src/core/investigation_project.c + +src/dao/investigation_dao.c +src/models/investigation_record.c + +src/database/database.c +src/database/schema.c +src/database/statement.c +src/database/transaction.c +src/database/error.c +``` + +Lier avec : + +```text +GLib +SQLite +``` + +Ajouter le test aux cibles : + +```text +test +clean +``` + +La nouvelle sortie attendue est : + +```text +InvestigationSession : tous les tests sont valides. +``` + +## Critères d’acceptation + +- [ ] Le type `InvestigationSession` est opaque. +- [ ] Une session possède un `InvestigationProject`. +- [ ] Une session possède une connexion `Database`. +- [ ] Une session possède un `InvestigationRecord`. +- [ ] Le dossier racine est validé avant l’ouverture. +- [ ] Le chemin racine est normalisé. +- [ ] Le chemin de la base provient de `InvestigationProject`. +- [ ] Le fichier SQLite doit exister avant l’appel à `database_open()`. +- [ ] L’ouverture ne crée jamais silencieusement une nouvelle base. +- [ ] Le record est chargé avec `InvestigationDao`. +- [ ] Le chemin enregistré est comparé au chemin sélectionné. +- [ ] Une incohérence de chemin empêche l’ouverture. +- [ ] La connexion reste ouverte pendant la durée de vie de la session. +- [ ] La fermeture libère toutes les ressources. +- [ ] Les accesseurs acceptent une session `NULL`. +- [ ] Les erreurs sont propagées avec `GError`. +- [ ] Aucun type SQLite n’apparaît dans l’API de la session. +- [ ] Aucune dépendance GTK n’est ajoutée. +- [ ] Les tests d’ouverture valide sont présents. +- [ ] Les tests de paramètres invalides sont présents. +- [ ] Les tests de dossier absent sont présents. +- [ ] Les tests de base absente sont présents. +- [ ] Les tests de base invalide sont présents. +- [ ] Les tests de chemin incohérent sont présents. +- [ ] Les anciens tests restent valides. +- [ ] `make` réussit sans erreur. +- [ ] `make test` réussit. +- [ ] `git diff --check` ne retourne aucune erreur. + +## Audit attendu + +Les commandes suivantes ne doivent rien afficher : + +```bash +rg -n 'sqlite3_|#include |#include +/** + * @brief Contexte opaque représentant les chemins d'un projet d'enquête. + */ +typedef struct InvestigationProject InvestigationProject; + /** * @brief Crée une nouvelle enquête dans un dossier parent. * @@ -15,7 +20,7 @@ * * - le dossier racine de l'enquête ; * - l'arborescence standard ; - * - le fichier vide 00_BaseDeDonnees/Enquete.sqlite. + * - le fichier 00_BaseDeDonnees/Enquete.sqlite. * * Le dossier parent doit déjà exister. * @@ -28,7 +33,7 @@ * 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 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. @@ -38,7 +43,7 @@ char *investigation_project_create( const char *investigation_name ); -/****************************************************************************** +/** * @brief Vérifie qu'un dossier est une enquête valide. * * La fonction contrôle que tous les éléments obligatoires de la structure @@ -49,9 +54,62 @@ char *investigation_project_create( * @param investigation_path Chemin de l'enquête à vérifier. * * @return true si l'enquête est valide, sinon false. - ******************************************************************************/ + */ bool investigation_project_validate( const char *investigation_path ); +/** + * @brief Crée un contexte de chemins pour une enquête existante. + * + * Le chemin racine est converti en chemin canonique. + * + * Cette fonction ne crée aucun fichier et ne modifie pas le projet. + * Elle construit uniquement les chemins nécessaires à son utilisation. + * + * @param investigation_root_path Chemin racine de l'enquête. + * + * @return Un nouveau contexte InvestigationProject, ou NULL en cas d'échec. + */ +InvestigationProject *investigation_project_open( + const char *investigation_root_path +); + +/** + * @brief Libère un contexte InvestigationProject. + * + * Cette fonction accepte NULL. + * + * @param project Contexte à libérer. + */ +void investigation_project_free( + InvestigationProject *project +); + +/** + * @brief Retourne le chemin racine canonique de l'enquête. + * + * Le pointeur retourné appartient au contexte et ne doit pas être libéré. + * + * @param project Contexte du projet. + * + * @return Chemin racine, ou NULL si project vaut NULL. + */ +const char *investigation_project_get_root_path( + const InvestigationProject *project +); + +/** + * @brief Retourne le chemin du fichier SQLite de l'enquête. + * + * Le pointeur retourné appartient au contexte et ne doit pas être libéré. + * + * @param project Contexte du projet. + * + * @return Chemin du fichier Enquete.sqlite, ou NULL si project vaut NULL. + */ +const char *investigation_project_get_database_path( + const InvestigationProject *project +); + #endif diff --git a/include/core/investigation_session.h b/include/core/investigation_session.h new file mode 100644 index 0000000..84ec853 --- /dev/null +++ b/include/core/investigation_session.h @@ -0,0 +1,143 @@ +/****************************************************************************** + * @file investigation_session.h + * @brief Interface publique d'une session d'enquête ouverte. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_INVESTIGATION_SESSION_H +#define LABFY_INVESTIGATION_INVESTIGATION_SESSION_H + +#include + +#include "core/investigation_project.h" +#include "database/database.h" +#include "models/investigation_record.h" + +/** + * @brief Session opaque représentant une enquête ouverte. + * + * Une session possède : + * + * - le contexte de fichiers InvestigationProject ; + * - la connexion Database ; + * - les informations persistées InvestigationRecord. + */ +typedef struct InvestigationSession InvestigationSession; + +/** + * @brief Codes d'erreur produits lors de l'ouverture d'une session. + */ +typedef enum +{ + INVESTIGATION_SESSION_ERROR_INVALID_ARGUMENT, + INVESTIGATION_SESSION_ERROR_ROOT_NOT_FOUND, + INVESTIGATION_SESSION_ERROR_DATABASE_NOT_FOUND, + INVESTIGATION_SESSION_ERROR_PROJECT, + INVESTIGATION_SESSION_ERROR_DATABASE, + INVESTIGATION_SESSION_ERROR_RECORD, + INVESTIGATION_SESSION_ERROR_ROOT_MISMATCH, + INVESTIGATION_SESSION_ERROR_MEMORY +} InvestigationSessionError; + +/** + * @brief Domaine d'erreur GLib du module InvestigationSession. + */ +#define INVESTIGATION_SESSION_ERROR \ + investigation_session_error_quark() + +/** + * @brief Retourne le domaine d'erreur du module. + * + * @return Le GQuark associé aux erreurs InvestigationSession. + */ +GQuark investigation_session_error_quark(void); + +/** + * @brief Ouvre une enquête existante. + * + * La fonction : + * + * - vérifie que le chemin racine représente un dossier ; + * - construit un InvestigationProject ; + * - vérifie la présence du fichier SQLite ; + * - ouvre la connexion Database ; + * - charge le record persistant ; + * - vérifie la cohérence du chemin racine. + * + * Cette fonction ne crée aucune enquête et ne modifie pas la base. + * + * En cas de succès, la session devient propriétaire du projet, + * de la connexion Database et du record. + * + * Le paramètre error peut être NULL. + * + * @param investigation_root_path Chemin racine de l'enquête. + * @param error Adresse recevant une éventuelle erreur GLib. + * + * @return Une nouvelle session, ou NULL en cas d'échec. + */ +InvestigationSession *investigation_session_open( + const char *investigation_root_path, + GError **error +); + +/** + * @brief Ferme une session d'enquête. + * + * Cette fonction libère : + * + * - le record ; + * - la connexion Database ; + * - le contexte InvestigationProject ; + * - la session. + * + * Cette fonction accepte NULL. + * + * @param session Session à fermer. + */ +void investigation_session_close( + InvestigationSession *session +); + +/** + * @brief Retourne le contexte de fichiers de la session. + * + * Le pointeur retourné appartient à la session et ne doit pas être libéré. + * + * @param session Session d'enquête. + * + * @return Le projet associé, ou NULL si session vaut NULL. + */ +const InvestigationProject *investigation_session_get_project( + const InvestigationSession *session +); + +/** + * @brief Retourne les informations persistées de l'enquête. + * + * Le pointeur retourné appartient à la session et ne doit pas être libéré. + * + * @param session Session d'enquête. + * + * @return Le record associé, ou NULL si session vaut NULL. + */ +const InvestigationRecord *investigation_session_get_record( + const InvestigationSession *session +); + +/** + * @brief Retourne la connexion Database de la session. + * + * Le pointeur retourné appartient à la session et ne doit pas être fermé + * par le code appelant. + * + * La connexion reste modifiable afin d'être utilisée par les futurs DAO. + * + * @param session Session d'enquête. + * + * @return La connexion associée, ou NULL si session vaut NULL. + */ +Database *investigation_session_get_database( + InvestigationSession *session +); + +#endif diff --git a/labfy-investigation b/labfy-investigation index c28c4e8179c80245578372690058119e5f987ab8..6f07ba39e028a0e3e933897fa05895543f2c07a0 100755 GIT binary patch delta 55222 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 DiXTkH delta 48063 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 Ta2RuYJN4%Da{>;JlXv_tnyT7U diff --git a/src/core/investigation_project.c b/src/core/investigation_project.c index e70fa0b..1bcf57b 100644 --- a/src/core/investigation_project.c +++ b/src/core/investigation_project.c @@ -12,6 +12,27 @@ #include #include +/** + * @brief Nom du dossier contenant la base de données. + */ +#define INVESTIGATION_PROJECT_DATABASE_DIRECTORY \ + "00_BaseDeDonnees" + +/** + * @brief Nom du fichier SQLite d'une enquête. + */ +#define INVESTIGATION_PROJECT_DATABASE_FILENAME \ + "Enquete.sqlite" + +/** + * @brief Représentation privée des chemins d'un projet d'enquête. + */ +struct InvestigationProject +{ + char *root_path; + char *database_path; +}; + /** * @brief Type d'un élément de la structure d'une enquête. */ @@ -436,11 +457,10 @@ char *investigation_project_create( return NULL; } } - database_path = g_build_filename( investigation_path, - "00_BaseDeDonnees", - "Enquete.sqlite", + INVESTIGATION_PROJECT_DATABASE_DIRECTORY, + INVESTIGATION_PROJECT_DATABASE_FILENAME, NULL ); @@ -460,14 +480,25 @@ char *investigation_project_create( return NULL; } + /* + * Le chemin est enregistré avant l'initialisation. + * + * Ainsi, si database_initialize() crée le fichier puis échoue, + * le nettoyage pourra supprimer ce fichier avant ses dossiers parents. + * + * Le tableau devient propriétaire de database_path. + */ + g_ptr_array_add( + created_paths, + database_path + ); + if (!database_initialize( database_path, investigation_name, investigation_path )) { - g_free(database_path); - investigation_project_cleanup_created_paths( created_paths ); @@ -482,22 +513,11 @@ char *investigation_project_create( 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. + * On libère uniquement le tableau et ses chaînes. + * Les fichiers et dossiers restent présents sur le disque. */ g_ptr_array_free( created_paths, @@ -553,3 +573,109 @@ bool investigation_project_validate( return true; } + +InvestigationProject *investigation_project_open( + const char *investigation_root_path +) +{ + InvestigationProject *project = NULL; + char *canonical_root_path = NULL; + + if (investigation_root_path == NULL || + investigation_root_path[0] == '\0') + { + return NULL; + } + + canonical_root_path = g_canonicalize_filename( + investigation_root_path, + NULL + ); + + if (canonical_root_path == NULL) + { + return NULL; + } + + /* + * Le contexte représente une enquête existante. + * + * On vérifie seulement ici que sa racine est un dossier. + * La présence de la base SQLite sera contrôlée séparément par + * InvestigationSession afin de produire une erreur précise. + */ + if (!g_file_test( + canonical_root_path, + G_FILE_TEST_IS_DIR + )) + { + g_free(canonical_root_path); + return NULL; + } + + project = g_try_new0( + InvestigationProject, + 1 + ); + + if (project == NULL) + { + g_free(canonical_root_path); + return NULL; + } + + project->root_path = canonical_root_path; + + project->database_path = g_build_filename( + project->root_path, + INVESTIGATION_PROJECT_DATABASE_DIRECTORY, + INVESTIGATION_PROJECT_DATABASE_FILENAME, + NULL + ); + + if (project->database_path == NULL) + { + investigation_project_free(project); + return NULL; + } + + return project; +} + +void investigation_project_free( + InvestigationProject *project +) +{ + if (project == NULL) + { + return; + } + + g_free(project->database_path); + g_free(project->root_path); + g_free(project); +} + +const char *investigation_project_get_root_path( + const InvestigationProject *project +) +{ + if (project == NULL) + { + return NULL; + } + + return project->root_path; +} + +const char *investigation_project_get_database_path( + const InvestigationProject *project +) +{ + if (project == NULL) + { + return NULL; + } + + return project->database_path; +} diff --git a/src/core/investigation_session.c b/src/core/investigation_session.c new file mode 100644 index 0000000..ebc5e67 --- /dev/null +++ b/src/core/investigation_session.c @@ -0,0 +1,374 @@ +/****************************************************************************** + * @file investigation_session.c + * @brief Gestion d'une session d'enquête ouverte. + ******************************************************************************/ + +#include "core/investigation_session.h" + +#include "dao/investigation_dao.h" +#include "database/error.h" + +/** + * @brief Représentation privée d'une session d'enquête. + */ +struct InvestigationSession +{ + InvestigationProject *project; + Database *database; + InvestigationRecord *record; +}; + +/** + * @brief Enregistre une erreur littérale si un GError est demandé. + * + * @param error Adresse recevant l'erreur. + * @param error_code Code d'erreur du module. + * @param error_message Message associé. + */ +static void investigation_session_set_error_literal( + GError **error, + InvestigationSessionError error_code, + const char *error_message +) +{ + if (error == NULL) + { + return; + } + + g_set_error_literal( + error, + INVESTIGATION_SESSION_ERROR, + error_code, + error_message + ); +} + +GQuark investigation_session_error_quark(void) +{ + return g_quark_from_static_string( + "investigation-session-error-quark" + ); +} + +InvestigationSession *investigation_session_open( + const char *investigation_root_path, + GError **error +) +{ + InvestigationSession *session = NULL; + InvestigationProject *project = NULL; + InvestigationRecord *record = NULL; + Database *database = NULL; + + char *canonical_root_path = NULL; + char *canonical_record_root_path = NULL; + + const char *database_path = NULL; + const char *record_root_path = NULL; + const char *database_error_message = NULL; + + /* + * Convention GLib : + * + * un GError existant ne doit pas être remplacé. + */ + g_return_val_if_fail( + error == NULL || *error == NULL, + NULL + ); + + if (investigation_root_path == NULL || + investigation_root_path[0] == '\0') + { + investigation_session_set_error_literal( + error, + INVESTIGATION_SESSION_ERROR_INVALID_ARGUMENT, + "Le chemin racine de l'enquête est invalide." + ); + + return NULL; + } + + canonical_root_path = g_canonicalize_filename( + investigation_root_path, + NULL + ); + + if (canonical_root_path == NULL) + { + investigation_session_set_error_literal( + error, + INVESTIGATION_SESSION_ERROR_MEMORY, + "Impossible d'allouer le chemin canonique de l'enquête." + ); + + return NULL; + } + + if (!g_file_test( + canonical_root_path, + G_FILE_TEST_IS_DIR + )) + { + g_set_error( + error, + INVESTIGATION_SESSION_ERROR, + INVESTIGATION_SESSION_ERROR_ROOT_NOT_FOUND, + "Le chemin racine '%s' n'est pas un dossier existant.", + canonical_root_path + ); + + goto cleanup; + } + + project = investigation_project_open( + canonical_root_path + ); + + if (project == NULL) + { + investigation_session_set_error_literal( + error, + INVESTIGATION_SESSION_ERROR_PROJECT, + "Impossible de construire le contexte du projet d'enquête." + ); + + goto cleanup; + } + + database_path = investigation_project_get_database_path( + project + ); + + if (database_path == NULL) + { + investigation_session_set_error_literal( + error, + INVESTIGATION_SESSION_ERROR_PROJECT, + "Le projet ne fournit aucun chemin vers la base de données." + ); + + goto cleanup; + } + + /* + * Cette vérification doit précéder database_open(). + * + * SQLite peut créer automatiquement une base absente lors de + * l'ouverture. Une session ne doit jamais créer silencieusement + * une nouvelle base. + */ + if (!g_file_test( + database_path, + G_FILE_TEST_IS_REGULAR + )) + { + g_set_error( + error, + INVESTIGATION_SESSION_ERROR, + INVESTIGATION_SESSION_ERROR_DATABASE_NOT_FOUND, + "Le fichier SQLite de l'enquête est absent : '%s'.", + database_path + ); + + goto cleanup; + } + + database = database_open( + database_path + ); + + if (database == NULL) + { + g_set_error( + error, + INVESTIGATION_SESSION_ERROR, + INVESTIGATION_SESSION_ERROR_DATABASE, + "Impossible d'ouvrir la base de données '%s'.", + database_path + ); + + goto cleanup; + } + + record = investigation_dao_load( + database + ); + + if (record == NULL) + { + /* + * Le message doit être copié dans le GError avant la fermeture + * de Database, car il appartient à l'objet Database. + */ + database_error_message = database_error_get_message( + database + ); + + if (database_error_message == NULL || + database_error_message[0] == '\0') + { + database_error_message = + "Impossible de charger les informations de l'enquête."; + } + + g_set_error( + error, + INVESTIGATION_SESSION_ERROR, + INVESTIGATION_SESSION_ERROR_RECORD, + "%s", + database_error_message + ); + + goto cleanup; + } + + record_root_path = investigation_record_get_root_path( + record + ); + + if (record_root_path == NULL || + record_root_path[0] == '\0') + { + investigation_session_set_error_literal( + error, + INVESTIGATION_SESSION_ERROR_RECORD, + "Le chemin racine enregistré dans la base est invalide." + ); + + goto cleanup; + } + + canonical_record_root_path = g_canonicalize_filename( + record_root_path, + NULL + ); + + if (canonical_record_root_path == NULL) + { + investigation_session_set_error_literal( + error, + INVESTIGATION_SESSION_ERROR_MEMORY, + "Impossible d'allouer le chemin canonique enregistré." + ); + + goto cleanup; + } + + if (g_strcmp0( + canonical_root_path, + canonical_record_root_path + ) != 0) + { + g_set_error( + error, + INVESTIGATION_SESSION_ERROR, + INVESTIGATION_SESSION_ERROR_ROOT_MISMATCH, + "Le chemin sélectionné '%s' ne correspond pas au chemin " + "enregistré '%s'.", + canonical_root_path, + canonical_record_root_path + ); + + goto cleanup; + } + + session = g_try_new0( + InvestigationSession, + 1 + ); + + if (session == NULL) + { + investigation_session_set_error_literal( + error, + INVESTIGATION_SESSION_ERROR_MEMORY, + "Impossible d'allouer la session d'enquête." + ); + + goto cleanup; + } + + /* + * La session devient propriétaire des trois objets. + */ + session->project = project; + session->database = database; + session->record = record; + + project = NULL; + database = NULL; + record = NULL; + +cleanup: + + g_free(canonical_record_root_path); + g_free(canonical_root_path); + + investigation_record_free(record); + database_close(database); + investigation_project_free(project); + + return session; +} + +void investigation_session_close( + InvestigationSession *session +) +{ + if (session == NULL) + { + return; + } + + investigation_record_free( + session->record + ); + + database_close( + session->database + ); + + investigation_project_free( + session->project + ); + + g_free(session); +} + +const InvestigationProject *investigation_session_get_project( + const InvestigationSession *session +) +{ + if (session == NULL) + { + return NULL; + } + + return session->project; +} + +const InvestigationRecord *investigation_session_get_record( + const InvestigationSession *session +) +{ + if (session == NULL) + { + return NULL; + } + + return session->record; +} + +Database *investigation_session_get_database( + InvestigationSession *session +) +{ + if (session == NULL) + { + return NULL; + } + + return session->database; +} diff --git a/tests/test_investigation_project b/tests/test_investigation_project index ab7a276f93faa9dc940ddf1e3075d95329b6a722..3bd6369a2f5df830c034c78550518290ce4c20a8 100755 GIT binary patch delta 17486 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 diff --git a/tests/test_investigation_project.c b/tests/test_investigation_project.c index ddb8e41..d739011 100644 --- a/tests/test_investigation_project.c +++ b/tests/test_investigation_project.c @@ -735,6 +735,181 @@ static void test_validate_directory_replaced_by_file(void) g_free(temporary_parent); } +/** + * @brief Vérifie l'ouverture d'un projet existant et ses accesseurs. + */ +static void test_open_existing_project(void) +{ + char *temporary_parent = NULL; + char *investigation_path = NULL; + char *path_with_dot = NULL; + char *expected_root_path = NULL; + char *expected_database_path = NULL; + + InvestigationProject *project = NULL; + + GError *error = NULL; + + temporary_parent = g_dir_make_tmp( + "labfy-investigation-project-open-test-XXXXXX", + &error + ); + + assert(temporary_parent != NULL); + assert(error == NULL); + + investigation_path = investigation_project_create( + temporary_parent, + "Enquete_Ouverte" + ); + + assert(investigation_path != NULL); + + /* + * Le segment "." permet de vérifier la canonicalisation du chemin. + */ + path_with_dot = g_build_filename( + investigation_path, + ".", + NULL + ); + + assert(path_with_dot != NULL); + + expected_root_path = g_canonicalize_filename( + investigation_path, + NULL + ); + + assert(expected_root_path != NULL); + + expected_database_path = g_build_filename( + expected_root_path, + "00_BaseDeDonnees", + "Enquete.sqlite", + NULL + ); + + assert(expected_database_path != NULL); + + project = investigation_project_open( + path_with_dot + ); + + assert(project != NULL); + + assert( + strcmp( + investigation_project_get_root_path(project), + expected_root_path + ) == 0 + ); + + assert( + strcmp( + investigation_project_get_database_path(project), + expected_database_path + ) == 0 + ); + + investigation_project_free(project); + + assert( + test_remove_path_recursively( + temporary_parent + ) + ); + + g_free(expected_database_path); + g_free(expected_root_path); + g_free(path_with_dot); + g_free(investigation_path); + g_free(temporary_parent); +} + +/** + * @brief Vérifie le refus des chemins invalides lors de l'ouverture. + */ +static void test_open_invalid_project_paths(void) +{ + char *temporary_directory = NULL; + char *temporary_file = NULL; + + GError *error = NULL; + + assert( + investigation_project_open(NULL) == NULL + ); + + assert( + investigation_project_open("") == NULL + ); + + assert( + investigation_project_open( + "/tmp/labfy-investigation-project-does-not-exist" + ) == NULL + ); + + temporary_directory = g_dir_make_tmp( + "labfy-investigation-project-open-file-test-XXXXXX", + &error + ); + + assert(temporary_directory != NULL); + assert(error == NULL); + + temporary_file = g_build_filename( + temporary_directory, + "not-a-directory.txt", + NULL + ); + + assert(temporary_file != NULL); + + assert( + g_file_set_contents( + temporary_file, + "test\n", + -1, + &error + ) + ); + + assert(error == NULL); + + assert( + investigation_project_open( + temporary_file + ) == NULL + ); + + assert( + test_remove_path_recursively( + temporary_directory + ) + ); + + g_free(temporary_file); + g_free(temporary_directory); +} + +/** + * @brief Vérifie les fonctions acceptant un projet NULL. + */ +static void test_open_project_null_instance(void) +{ + assert( + investigation_project_get_root_path(NULL) == NULL + ); + + assert( + investigation_project_get_database_path(NULL) == NULL + ); + + investigation_project_free(NULL); +} + int main(void) { test_create_valid_investigation(); @@ -749,6 +924,11 @@ int main(void) test_validate_missing_directory(); test_validate_directory_replaced_by_file(); + + test_open_existing_project(); + test_open_invalid_project_paths(); + test_open_project_null_instance(); + printf( "InvestigationProject : tous les tests sont valides.\n" ); diff --git a/tests/test_investigation_session b/tests/test_investigation_session new file mode 100755 index 0000000000000000000000000000000000000000..04f8d66714548cb98a4e3052273af85519218148 GIT binary patch literal 56880 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 literal 0 HcmV?d00001 diff --git a/tests/test_investigation_session.c b/tests/test_investigation_session.c new file mode 100644 index 0000000..c82f368 --- /dev/null +++ b/tests/test_investigation_session.c @@ -0,0 +1,684 @@ +/****************************************************************************** + * @file test_investigation_session.c + * @brief Tests du contexte représentant une enquête ouverte. + ******************************************************************************/ + +#include "core/investigation_session.h" + +#include "core/investigation_project.h" +#include "dao/investigation_dao.h" +#include "database/database.h" +#include "models/investigation_record.h" + +#include +#include +#include + +#include +#include + +static gboolean test_remove_path_recursively( + const char *path +) +{ + GDir *directory = NULL; + const char *entry_name = 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, + NULL + ); + + if (directory == NULL) + { + return FALSE; + } + + while ((entry_name = g_dir_read_name(directory)) != NULL) + { + char *entry_path = NULL; + gboolean removed = FALSE; + + entry_path = g_build_filename( + path, + entry_name, + NULL + ); + + if (entry_path == NULL) + { + g_dir_close(directory); + return FALSE; + } + + removed = test_remove_path_recursively( + entry_path + ); + + g_free(entry_path); + + if (!removed) + { + g_dir_close(directory); + return FALSE; + } + } + + g_dir_close(directory); + + return g_rmdir(path) == 0; +} + +static void test_assert_session_error( + const GError *error, + InvestigationSessionError expected_code +) +{ + assert(error != NULL); + assert(error->domain == INVESTIGATION_SESSION_ERROR); + assert(error->code == (gint) expected_code); + assert(error->message != NULL); + assert(error->message[0] != '\0'); +} + +static void test_open_valid_session(void) +{ + char *temporary_parent = NULL; + char *investigation_path = NULL; + char *canonical_root_path = NULL; + + InvestigationSession *session = NULL; + const InvestigationProject *project = NULL; + const InvestigationRecord *record = NULL; + InvestigationRecord *reloaded_record = NULL; + Database *database = NULL; + + const char *record_id = NULL; + const char *reloaded_record_id = NULL; + + GError *error = NULL; + + temporary_parent = g_dir_make_tmp( + "labfy-investigation-session-valid-XXXXXX", + &error + ); + + assert(temporary_parent != NULL); + assert(error == NULL); + + investigation_path = investigation_project_create( + temporary_parent, + "Enquete_Session" + ); + + assert(investigation_path != NULL); + + canonical_root_path = g_canonicalize_filename( + investigation_path, + NULL + ); + + assert(canonical_root_path != NULL); + + session = investigation_session_open( + investigation_path, + &error + ); + + assert(session != NULL); + assert(error == NULL); + + project = investigation_session_get_project( + session + ); + + database = investigation_session_get_database( + session + ); + + record = investigation_session_get_record( + session + ); + + assert(project != NULL); + assert(database != NULL); + assert(record != NULL); + + assert( + strcmp( + investigation_project_get_root_path(project), + canonical_root_path + ) == 0 + ); + + assert( + strcmp( + investigation_record_get_name(record), + "Enquete_Session" + ) == 0 + ); + + assert( + strcmp( + investigation_record_get_root_path(record), + canonical_root_path + ) == 0 + ); + + record_id = investigation_record_get_id( + record + ); + + assert(record_id != NULL); + assert(g_uuid_string_is_valid(record_id)); + + assert( + investigation_record_get_created_at(record) != NULL + ); + + assert( + investigation_record_get_created_at(record)[0] != '\0' + ); + + assert( + investigation_record_get_updated_at(record) != NULL + ); + + assert( + investigation_record_get_updated_at(record)[0] != '\0' + ); + + reloaded_record = investigation_dao_load( + database + ); + + assert(reloaded_record != NULL); + + reloaded_record_id = investigation_record_get_id( + reloaded_record + ); + + assert(reloaded_record_id != NULL); + + assert( + strcmp( + record_id, + reloaded_record_id + ) == 0 + ); + + investigation_record_free(reloaded_record); + investigation_session_close(session); + + assert( + test_remove_path_recursively( + temporary_parent + ) + ); + + g_free(canonical_root_path); + g_free(investigation_path); + g_free(temporary_parent); +} + +static void test_open_invalid_parameters(void) +{ + InvestigationSession *session = NULL; + GError *error = NULL; + + session = investigation_session_open( + NULL, + &error + ); + + assert(session == NULL); + + test_assert_session_error( + error, + INVESTIGATION_SESSION_ERROR_INVALID_ARGUMENT + ); + + g_clear_error(&error); + + session = investigation_session_open( + "", + &error + ); + + assert(session == NULL); + + test_assert_session_error( + error, + INVESTIGATION_SESSION_ERROR_INVALID_ARGUMENT + ); + + g_clear_error(&error); + + assert( + investigation_session_open( + NULL, + NULL + ) == NULL + ); + + assert( + investigation_session_open( + "", + NULL + ) == NULL + ); +} + +static void test_open_missing_root(void) +{ + char *temporary_parent = NULL; + char *missing_root_path = NULL; + + InvestigationSession *session = NULL; + + GError *error = NULL; + + temporary_parent = g_dir_make_tmp( + "labfy-investigation-session-missing-root-XXXXXX", + &error + ); + + assert(temporary_parent != NULL); + assert(error == NULL); + + missing_root_path = g_build_filename( + temporary_parent, + "does-not-exist", + NULL + ); + + assert(missing_root_path != NULL); + + session = investigation_session_open( + missing_root_path, + &error + ); + + assert(session == NULL); + + test_assert_session_error( + error, + INVESTIGATION_SESSION_ERROR_ROOT_NOT_FOUND + ); + + g_clear_error(&error); + + assert( + !g_file_test( + missing_root_path, + G_FILE_TEST_EXISTS + ) + ); + + assert( + test_remove_path_recursively( + temporary_parent + ) + ); + + g_free(missing_root_path); + g_free(temporary_parent); +} + +static void test_open_root_is_file(void) +{ + char *temporary_parent = NULL; + char *file_path = NULL; + + InvestigationSession *session = NULL; + + GError *error = NULL; + + temporary_parent = g_dir_make_tmp( + "labfy-investigation-session-root-file-XXXXXX", + &error + ); + + assert(temporary_parent != NULL); + assert(error == NULL); + + file_path = g_build_filename( + temporary_parent, + "not-a-directory.txt", + NULL + ); + + assert(file_path != NULL); + + assert( + g_file_set_contents( + file_path, + "test\n", + -1, + &error + ) + ); + + assert(error == NULL); + + session = investigation_session_open( + file_path, + &error + ); + + assert(session == NULL); + + test_assert_session_error( + error, + INVESTIGATION_SESSION_ERROR_ROOT_NOT_FOUND + ); + + g_clear_error(&error); + + assert( + test_remove_path_recursively( + temporary_parent + ) + ); + + g_free(file_path); + g_free(temporary_parent); +} + +static void test_open_missing_database(void) +{ + char *investigation_root = NULL; + char *database_directory = NULL; + char *database_path = NULL; + + InvestigationSession *session = NULL; + + GError *error = NULL; + + investigation_root = g_dir_make_tmp( + "labfy-investigation-session-missing-database-XXXXXX", + &error + ); + + assert(investigation_root != NULL); + assert(error == NULL); + + database_directory = g_build_filename( + investigation_root, + "00_BaseDeDonnees", + NULL + ); + + assert(database_directory != NULL); + + assert( + g_mkdir( + database_directory, + 0700 + ) == 0 + ); + + database_path = g_build_filename( + database_directory, + "Enquete.sqlite", + NULL + ); + + assert(database_path != NULL); + + assert( + !g_file_test( + database_path, + G_FILE_TEST_EXISTS + ) + ); + + session = investigation_session_open( + investigation_root, + &error + ); + + assert(session == NULL); + + test_assert_session_error( + error, + INVESTIGATION_SESSION_ERROR_DATABASE_NOT_FOUND + ); + + g_clear_error(&error); + + assert( + !g_file_test( + database_path, + G_FILE_TEST_EXISTS + ) + ); + + assert( + test_remove_path_recursively( + investigation_root + ) + ); + + g_free(database_path); + g_free(database_directory); + g_free(investigation_root); +} + +static void test_open_invalid_database(void) +{ + char *investigation_root = NULL; + char *database_directory = NULL; + char *database_path = NULL; + + InvestigationSession *session = NULL; + + GError *error = NULL; + + investigation_root = g_dir_make_tmp( + "labfy-investigation-session-invalid-database-XXXXXX", + &error + ); + + assert(investigation_root != NULL); + assert(error == NULL); + + database_directory = g_build_filename( + investigation_root, + "00_BaseDeDonnees", + NULL + ); + + assert(database_directory != NULL); + + assert( + g_mkdir( + database_directory, + 0700 + ) == 0 + ); + + database_path = g_build_filename( + database_directory, + "Enquete.sqlite", + NULL + ); + + assert(database_path != NULL); + + assert( + g_file_set_contents( + database_path, + "", + 0, + &error + ) + ); + + assert(error == NULL); + + session = investigation_session_open( + investigation_root, + &error + ); + + assert(session == NULL); + + test_assert_session_error( + error, + INVESTIGATION_SESSION_ERROR_RECORD + ); + + g_clear_error(&error); + + assert( + test_remove_path_recursively( + investigation_root + ) + ); + + g_free(database_path); + g_free(database_directory); + g_free(investigation_root); +} + +static void test_open_root_mismatch(void) +{ + char *investigation_root = NULL; + char *database_directory = NULL; + char *database_path = NULL; + char *different_root_path = NULL; + + InvestigationSession *session = NULL; + + GError *error = NULL; + + investigation_root = g_dir_make_tmp( + "labfy-investigation-session-root-mismatch-XXXXXX", + &error + ); + + assert(investigation_root != NULL); + assert(error == NULL); + + database_directory = g_build_filename( + investigation_root, + "00_BaseDeDonnees", + NULL + ); + + assert(database_directory != NULL); + + assert( + g_mkdir( + database_directory, + 0700 + ) == 0 + ); + + database_path = g_build_filename( + database_directory, + "Enquete.sqlite", + NULL + ); + + assert(database_path != NULL); + + different_root_path = g_build_filename( + investigation_root, + "autre-emplacement", + NULL + ); + + assert(different_root_path != NULL); + + assert( + database_initialize( + database_path, + "Enquete_Incoherente", + different_root_path + ) + ); + + session = investigation_session_open( + investigation_root, + &error + ); + + assert(session == NULL); + + test_assert_session_error( + error, + INVESTIGATION_SESSION_ERROR_ROOT_MISMATCH + ); + + g_clear_error(&error); + + assert( + test_remove_path_recursively( + investigation_root + ) + ); + + g_free(different_root_path); + g_free(database_path); + g_free(database_directory); + g_free(investigation_root); +} + +static void test_null_session_accessors(void) +{ + assert( + investigation_session_get_project(NULL) == NULL + ); + + assert( + investigation_session_get_record(NULL) == NULL + ); + + assert( + investigation_session_get_database(NULL) == NULL + ); + + investigation_session_close(NULL); +} + +int main(void) +{ + test_open_valid_session(); + test_open_invalid_parameters(); + test_open_missing_root(); + test_open_root_is_file(); + test_open_missing_database(); + test_open_invalid_database(); + test_open_root_mismatch(); + test_null_session_accessors(); + + printf( + "InvestigationSession : tous les tests sont valides.\n" + ); + + return 0; +}