17 KiB
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
InvestigationDaopermettant de charger l’unique ligne de la tableinvestigation.
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 :
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
Application
│
▼
InvestigationSession
├── InvestigationProject
├── Database
└── InvestigationRecord
▲
│
InvestigationDao
│
▼
DatabaseStatement
│
▼
SQLite
Travail à réaliser
1. Créer le type InvestigationSession
Créer les fichiers :
include/core/investigation_session.h
src/core/investigation_session.c
Le type doit être opaque :
typedef struct InvestigationSession InvestigationSession;
Sa représentation privée doit contenir au minimum :
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 :
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 :
#define INVESTIGATION_SESSION_ERROR \
investigation_session_error_quark()
GQuark investigation_session_error_quark(void);
Les erreurs doivent être transmises avec un paramètre :
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 :
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 :
investigation_root_path == NULL
investigation_root_path vide
Le code d’erreur attendu est :
INVESTIGATION_SESSION_ERROR_INVALID_ARGUMENT
Le paramètre error peut être NULL.
Si error n’est pas NULL, il doit respecter les conventions GLib :
*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 :
G_FILE_TEST_IS_DIR
Un chemin inexistant ou qui ne représente pas un dossier doit produire :
INVESTIGATION_SESSION_ERROR_ROOT_NOT_FOUND
Le chemin doit être normalisé avec :
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 :
<racine>/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 :
G_FILE_TEST_IS_REGULAR
Si le fichier n’existe pas, retourner :
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 :
database_open()
La fonction investigation_session_open() ne doit pas appeler directement :
sqlite3_open()
sqlite3_open_v2()
sqlite3_close()
Si database_open() échoue, retourner :
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 :
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 :
INVESTIGATION_SESSION_ERROR_RECORD
10. Vérifier la cohérence du chemin racine
Le chemin sélectionné doit correspondre au champ persistant :
investigation.root_path
Comparer les versions canoniques de :
chemin racine sélectionné
chemin racine enregistré dans InvestigationRecord
La comparaison doit être faite après normalisation avec :
g_canonicalize_filename()
Si les chemins ne correspondent pas, l’ouverture doit échouer avec :
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 :
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 :
INVESTIGATION_SESSION_ERROR_MEMORY
12. Ajouter la fonction de fermeture
Déclarer :
void investigation_session_close(
InvestigationSession *session
);
Cette fonction doit accepter NULL.
Elle doit libérer toutes les ressources possédées par la session :
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 :
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 :
#include <sqlite3.h>
#include <gtk/gtk.h>
Il doit exclusivement utiliser les abstractions existantes :
InvestigationProject
Database
InvestigationDao
InvestigationRecord
GLib
Tests à ajouter
Créer :
tests/test_investigation_session.c
Test d’ouverture valide
Créer un dossier temporaire.
Initialiser une base avec :
database_initialize()
Ouvrir ensuite l’enquête avec :
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_atn’est pas vide ;updated_atn’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 :
investigation_session_open(NULL, &error) == NULL
investigation_session_open("", &error) == NULL
Le code attendu est :
INVESTIGATION_SESSION_ERROR_INVALID_ARGUMENT
Vérifier également le comportement avec :
error == NULL
Test d’un dossier inexistant
Utiliser un chemin inexistant.
Vérifier :
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 :
résultat == NULL
erreur == INVESTIGATION_SESSION_ERROR_ROOT_NOT_FOUND
Test d’une base absente
Créer une structure de projet valide sans fichier :
00_BaseDeDonnees/Enquete.sqlite
Vérifier :
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 :
investigation
Vérifier :
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 :
résultat == NULL
erreur == INVESTIGATION_SESSION_ERROR_ROOT_MISMATCH
message non vide
Test des accesseurs avec NULL
Vérifier :
investigation_session_get_project(NULL) == NULL
investigation_session_get_record(NULL) == NULL
investigation_session_get_database(NULL) == NULL
Vérifier également :
investigation_session_close(NULL);
Test de réutilisation du DAO
Après l’ouverture valide d’une session, appeler de nouveau :
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 :
é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 :
chemins canoniques
messages copiés
GError temporaires
InvestigationProject
Database
InvestigationRecord
Makefile
Ajouter :
TEST_INVESTIGATION_SESSION := tests/test_investigation_session
Ajouter une règle compilant au minimum :
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 :
GLib
SQLite
Ajouter le test aux cibles :
test
clean
La nouvelle sortie attendue est :
InvestigationSession : tous les tests sont valides.
Critères d’acceptation
- Le type
InvestigationSessionest 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.
makeréussit sans erreur.make testréussit.git diff --checkne retourne aucune erreur.
Audit attendu
Les commandes suivantes ne doivent rien afficher :
rg -n 'sqlite3_|#include <sqlite3.h>|#include <gtk' \
include/core/investigation_session.h \
src/core/investigation_session.c
Vérifier également que la logique du chemin SQLite n’est pas dupliquée :
rg -n '00_BaseDeDonnees|Enquete.sqlite' \
src/core/investigation_session.c
Le résultat attendu est aucune occurrence, sauf éventuellement dans un commentaire de documentation justifié.
Hors périmètre
Ce ticket ne doit pas ajouter :
- la création d’une nouvelle enquête ;
- le déplacement d’une enquête ;
- la réparation automatique d’un chemin racine incohérent ;
- la modification de
investigation.root_path; - l’intégration dans la fenêtre GTK ;
- une boîte de dialogue de sélection ;
- l’affichage du nom de l’enquête ;
- les DAO des preuves, sources ou entités ;
- la fermeture demandée par l’interface utilisateur ;
- la sauvegarde automatique ;
- une migration de schéma ;
- un verrouillage multi-instance de la base.
Fichiers principalement concernés
include/core/investigation_session.h
src/core/investigation_session.c
tests/test_investigation_session.c
Makefile
Une adaptation limitée de ces fichiers est autorisée si nécessaire :
include/core/investigation_project.h
src/core/investigation_project.c
Résultat attendu
À la fin du ticket, le programme doit pouvoir ouvrir une enquête existante à partir de son dossier racine.
Une session valide doit conserver ensemble :
le projet de fichiers
la connexion SQLite
les informations persistées de l’enquête
Cette session deviendra le contexte principal utilisé ultérieurement par l’interface et les futurs DAO.
Commit attendu
Une fois tous les critères d’acceptation validés :
feat(core): add investigation session loader
Avant le commit :
make clean
make
make test
git diff --check
git status --short
Préparer les fichiers :
git add \
Makefile \
include/core/investigation_session.h \
src/core/investigation_session.c \
tests/test_investigation_session.c
Ajouter également les fichiers InvestigationProject uniquement s’ils ont réellement été modifiés :
git add \
include/core/investigation_project.h \
src/core/investigation_project.c
Contrôler le contenu préparé :
git diff --cached --stat
git diff --cached
Créer le commit :
git commit -m "feat(core): add investigation session loader"
Le push ne doit être effectué qu’après validation complète de la compilation, des tests et du contenu du commit.