labfy-investigation/docs/tickets/closed/TICKET-028.md

776 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Ticket #028 — Ajouter louverture dune enquête existante
## Contexte
Les tickets précédents ont permis de mettre en place :
- la création transactionnelle dune base denquê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 lunique 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 lenquête ;
- le chemin du fichier `Enquete.sqlite`.
Cependant, lapplication ne possède pas encore dobjet 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 dune enquête ouverte.
## Objectif
Créer un type opaque `InvestigationSession` chargé douvrir une enquête existante et de conserver :
- son contexte de fichiers `InvestigationProject` ;
- sa connexion `Database` ;
- ses informations persistées `InvestigationRecord`.
Louverture 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 douverture
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 derreur 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
```
Lorsquune 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 douverture
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 derreur attendu est :
```c
INVESTIGATION_SESSION_ERROR_INVALID_ARGUMENT
```
Le paramètre `error` peut être `NULL`.
Si `error` nest pas `NULL`, il doit respecter les conventions GLib :
```c
*error == NULL
```
au moment de lappel.
### 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 lAPI 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
<racine>/00_BaseDeDonnees/Enquete.sqlite
```
Si lAPI 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 louverture 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 nexiste pas, retourner :
```c
INVESTIGATION_SESSION_ERROR_DATABASE_NOT_FOUND
```
Louverture dune 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 lenquê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 derreur 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, louverture doit échouer avec :
```c
INVESTIGATION_SESSION_ERROR_ROOT_MISMATCH
```
Cette vérification évite douvrir une base copiée ou déplacée sans détecter lincohérence.
Le déplacement volontaire dune enquête sera traité dans un ticket distinct.
### 11. Construire la session
La session ne doit être créée quaprè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 dallocation, 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 lappelant.
Les accesseurs doivent retourner `NULL` si la session reçue est `NULL`.
`Database` reste non constante car les futurs DAO auront besoin dune connexion modifiable.
### 14. Interdire les dépendances SQLite et GTK
Le module `InvestigationSession` ne doit pas inclure :
```c
#include <sqlite3.h>
#include <gtk/gtk.h>
```
Il doit exclusivement utiliser les abstractions existantes :
```text
InvestigationProject
Database
InvestigationDao
InvestigationRecord
GLib
```
## Tests à ajouter
Créer :
```text
tests/test_investigation_session.c
```
### Test douverture valide
Créer un dossier temporaire.
Initialiser une base avec :
```c
database_initialize()
```
Ouvrir ensuite lenquête avec :
```c
investigation_session_open()
```
Vérifier :
- la session nest pas `NULL` ;
- aucune erreur nest produite ;
- le projet est disponible ;
- la connexion Database est disponible ;
- le record est disponible ;
- le nom de lenquête est correct ;
- le chemin racine est correct ;
- lUUID est valide ;
- `created_at` nest pas vide ;
- `updated_at` nest 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 dun dossier inexistant
Utiliser un chemin inexistant.
Vérifier :
```text
résultat == NULL
erreur == INVESTIGATION_SESSION_ERROR_ROOT_NOT_FOUND
message non vide
```
### Test dun chemin qui nest 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 dune 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 dune 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 dun chemin racine incohérent
Créer une base avec un chemin racine enregistré différent du dossier utilisé pour louverture.
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 louverture valide dune 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 lallocation 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 dacceptation
- [ ] 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 louverture.
- [ ] Le chemin racine est normalisé.
- [ ] Le chemin de la base provient de `InvestigationProject`.
- [ ] Le fichier SQLite doit exister avant lappel à `database_open()`.
- [ ] Louverture 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 louverture.
- [ ] 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 napparaît dans lAPI de la session.
- [ ] Aucune dépendance GTK nest ajoutée.
- [ ] Les tests douverture 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 <sqlite3.h>|#include <gtk' \
include/core/investigation_session.h \
src/core/investigation_session.c
```
Vérifier également que la logique du chemin SQLite nest pas dupliquée :
```bash
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 dune nouvelle enquête ;
- le déplacement dune enquête ;
- la réparation automatique dun chemin racine incohérent ;
- la modification de `investigation.root_path` ;
- lintégration dans la fenêtre GTK ;
- une boîte de dialogue de sélection ;
- laffichage du nom de lenquête ;
- les DAO des preuves, sources ou entités ;
- la fermeture demandée par linterface utilisateur ;
- la sauvegarde automatique ;
- une migration de schéma ;
- un verrouillage multi-instance de la base.
## Fichiers principalement concernés
```text
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 :
```text
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 :
```text
le projet de fichiers
la connexion SQLite
les informations persistées de lenquête
```
Cette session deviendra le contexte principal utilisé ultérieurement par linterface et les futurs DAO.
## Commit attendu
Une fois tous les critères dacceptation validés :
```text
feat(core): add investigation session loader
```
Avant le commit :
```bash
make clean
make
make test
git diff --check
git status --short
```
Préparer les fichiers :
```bash
git add \
Makefile \
include/core/investigation_session.h \
src/core/investigation_session.c \
tests/test_investigation_session.c
```
Ajouter également les fichiers `InvestigationProject` uniquement sils ont réellement été modifiés :
```bash
git add \
include/core/investigation_project.h \
src/core/investigation_project.c
```
Contrôler le contenu préparé :
```bash
git diff --cached --stat
git diff --cached
```
Créer le commit :
```bash
git commit -m "feat(core): add investigation session loader"
```
Le push ne doit être effectué quaprès validation complète de la compilation, des tests et du contenu du commit.