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

17 KiB
Raw Blame History

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 :

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

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 douverture

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 derreur 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

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 :

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 derreur attendu est :

INVESTIGATION_SESSION_ERROR_INVALID_ARGUMENT

Le paramètre error peut être NULL.

Si error nest pas NULL, il doit respecter les conventions GLib :

*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 :

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 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 :

<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 :

G_FILE_TEST_IS_REGULAR

Si le fichier nexiste pas, retourner :

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 :

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 lenquê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 derreur 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, louverture doit échouer avec :

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 :

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 :

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 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 :

#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 douverture valide

Créer un dossier temporaire.

Initialiser une base avec :

database_initialize()

Ouvrir ensuite lenquête avec :

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 :

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 dun dossier inexistant

Utiliser un chemin inexistant.

Vérifier :

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 :

résultat == NULL
erreur == INVESTIGATION_SESSION_ERROR_ROOT_NOT_FOUND

Test dune 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 dune 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 dun chemin racine incohérent

Créer une base avec un chemin racine enregistré différent du dossier utilisé pour louverture.

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 louverture valide dune 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 lallocation 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 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 :

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 :

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

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 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 :

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 sils 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é quaprès validation complète de la compilation, des tests et du contenu du commit.