feat(dao): add investigation record loader
This commit is contained in:
parent
564d67a65b
commit
137e75722d
21 changed files with 1767 additions and 19 deletions
28
Makefile
28
Makefile
|
|
@ -37,6 +37,8 @@ TEST_DATABASE = tests/test_database
|
|||
TEST_STATEMENT = tests/test_statement
|
||||
TEST_TRANSACTION = tests/test_transaction
|
||||
TEST_ERROR = tests/test_error
|
||||
TEST_INVESTIGATION_RECORD = tests/test_investigation_record
|
||||
TEST_INVESTIGATION_DAO := tests/test_investigation_dao
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
|
|
@ -105,6 +107,22 @@ $(TEST_ERROR): \
|
|||
src/database/error.c
|
||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||
|
||||
$(TEST_INVESTIGATION_RECORD): \
|
||||
tests/test_investigation_record.c \
|
||||
src/models/investigation_record.c
|
||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||||
|
||||
$(TEST_INVESTIGATION_DAO): \
|
||||
tests/test_investigation_dao.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) \
|
||||
|
|
@ -113,7 +131,9 @@ test: \
|
|||
$(TEST_DATABASE) \
|
||||
$(TEST_STATEMENT) \
|
||||
$(TEST_TRANSACTION) \
|
||||
$(TEST_ERROR)
|
||||
$(TEST_ERROR) \
|
||||
$(TEST_INVESTIGATION_RECORD) \
|
||||
$(TEST_INVESTIGATION_DAO)
|
||||
@echo "Exécution des tests..."
|
||||
@./$(TEST_NODE)
|
||||
@./$(TEST_TREE_MODEL)
|
||||
|
|
@ -123,6 +143,8 @@ test: \
|
|||
@$(TEST_STATEMENT)
|
||||
@$(TEST_TRANSACTION)
|
||||
@$(TEST_ERROR)
|
||||
@$(TEST_INVESTIGATION_RECORD)
|
||||
@$(TEST_INVESTIGATION_DAO)
|
||||
@echo "Tous les tests sont valides."
|
||||
|
||||
%.o: %.c
|
||||
|
|
@ -140,6 +162,8 @@ clean:
|
|||
$(TEST_DATABASE) \
|
||||
$(TEST_STATEMENT) \
|
||||
$(TEST_TRANSACTION) \
|
||||
$(TEST_ERROR)
|
||||
$(TEST_ERROR) \
|
||||
$(TEST_INVESTIGATION_RECORD) \
|
||||
$(TEST_INVESTIGATION_DAO)
|
||||
|
||||
.PHONY: clean run test
|
||||
|
|
|
|||
535
docs/tickets/open/TICKET-027.md
Normal file
535
docs/tickets/open/TICKET-027.md
Normal file
|
|
@ -0,0 +1,535 @@
|
|||
# Ticket #027 — Ajouter le modèle de lecture et le DAO de l’enquête
|
||||
|
||||
## Contexte
|
||||
|
||||
Les tickets précédents ont permis de mettre en place :
|
||||
|
||||
- le schéma SQLite V1 ;
|
||||
- l’infrastructure `Database` ;
|
||||
- les requêtes préparées `DatabaseStatement` ;
|
||||
- les transactions ;
|
||||
- la gestion centralisée des erreurs ;
|
||||
- l’initialisation transactionnelle d’une nouvelle enquête.
|
||||
|
||||
La table SQLite :
|
||||
|
||||
```text
|
||||
investigation
|
||||
```
|
||||
contient les informations principales de l’enquête courante :
|
||||
|
||||
```text
|
||||
id
|
||||
name
|
||||
root_path
|
||||
created_at
|
||||
updated_at
|
||||
```
|
||||
|
||||
Le projet possède déjà un type :
|
||||
|
||||
```C
|
||||
Investigation
|
||||
```
|
||||
|
||||
dans le module `core`.
|
||||
|
||||
Cet objet représente actuellement le contexte de travail sur le système de
|
||||
fichiers :
|
||||
|
||||
- chemin racine de l’enquête ;
|
||||
- chemin du fichier SQLite.
|
||||
|
||||
Il ne doit pas être transformé directement en représentation d’une ligne SQL.
|
||||
|
||||
L’architecture du projet prévoit une séparation entre :
|
||||
|
||||
```text
|
||||
Core
|
||||
Models
|
||||
DAO
|
||||
Database
|
||||
SQLite
|
||||
```
|
||||
|
||||
Le DAO doit être responsable des requêtes SQL métier.
|
||||
|
||||
## Objectif
|
||||
|
||||
Créer un modèle de données en lecture seule représentant la ligne de la table
|
||||
investigation, ainsi qu’un DAO permettant de charger cette ligne depuis une
|
||||
connexion Database.
|
||||
|
||||
Le ticket doit permettre de lire les informations persistées d’une enquête
|
||||
sans utiliser directement l’API SQLite hors de la couche Database.
|
||||
|
||||
## Architecture attendue
|
||||
|
||||
```
|
||||
InvestigationRecord
|
||||
↑
|
||||
│ construit par
|
||||
│
|
||||
InvestigationDao
|
||||
│
|
||||
▼
|
||||
DatabaseStatement
|
||||
│
|
||||
▼
|
||||
Database
|
||||
│
|
||||
▼
|
||||
SQLite
|
||||
```
|
||||
|
||||
## Travail à réaliser
|
||||
|
||||
### Créer le modèle InvestigationRecord
|
||||
|
||||
Créer les fichiers :
|
||||
```text
|
||||
include/models/investigation_record.h
|
||||
src/models/investigation_record.c
|
||||
```
|
||||
|
||||
Le modèle doit être opaque :
|
||||
|
||||
```C
|
||||
typedef struct InvestigationRecord InvestigationRecord;
|
||||
```
|
||||
|
||||
Sa représentation privée doit contenir :
|
||||
|
||||
```C
|
||||
struct InvestigationRecord
|
||||
{
|
||||
char *id;
|
||||
char *name;
|
||||
char *root_path;
|
||||
char *created_at;
|
||||
char *updated_at;
|
||||
};
|
||||
```
|
||||
|
||||
Le modèle ne doit dépendre ni de SQLite, ni de GTK.
|
||||
|
||||
### Ajouter le constructeur
|
||||
|
||||
Créer une fonction :
|
||||
|
||||
```C
|
||||
InvestigationRecord *investigation_record_new(
|
||||
const char *id,
|
||||
const char *name,
|
||||
const char *root_path,
|
||||
const char *created_at,
|
||||
const char *updated_at
|
||||
);
|
||||
```
|
||||
|
||||
Le constructeur doit :
|
||||
|
||||
- refuser les pointeurs `NULL` ;
|
||||
- refuser les chaînes vides ;
|
||||
- allouer une copie de chaque chaîne ;
|
||||
- nettoyer correctement toutes les allocations en cas d’échec ;
|
||||
- retourner `NULL` si les données sont invalides.
|
||||
|
||||
### Ajouter la fonction de libération
|
||||
|
||||
Créer :
|
||||
|
||||
```C
|
||||
void investigation_record_free(
|
||||
InvestigationRecord *record
|
||||
);
|
||||
```
|
||||
|
||||
Cette fonction doit :
|
||||
|
||||
- accepter `NULL` ;
|
||||
- libérer toutes les chaînes ;
|
||||
- libérer la structure.
|
||||
|
||||
### Ajouter les accesseurs
|
||||
|
||||
Créer les accesseurs en lecture seule :
|
||||
|
||||
```C
|
||||
const char *investigation_record_get_id(
|
||||
const InvestigationRecord *record
|
||||
);
|
||||
|
||||
const char *investigation_record_get_name(
|
||||
const InvestigationRecord *record
|
||||
);
|
||||
|
||||
const char *investigation_record_get_root_path(
|
||||
const InvestigationRecord *record
|
||||
);
|
||||
|
||||
const char *investigation_record_get_created_at(
|
||||
const InvestigationRecord *record
|
||||
);
|
||||
|
||||
const char *investigation_record_get_updated_at(
|
||||
const InvestigationRecord *record
|
||||
);
|
||||
```
|
||||
|
||||
Les pointeurs retournés appartiennent au modèle et ne doivent pas être libérés.
|
||||
|
||||
Les accesseurs doivent retourner `NULL` si le modèle reçu est `NULL`.
|
||||
|
||||
### Créer le DAO de l’enquête
|
||||
|
||||
Créer les fichiers :
|
||||
|
||||
```text
|
||||
include/dao/investigation_dao.h
|
||||
src/dao/investigation_dao.c
|
||||
```
|
||||
|
||||
Déclarer la fonction :
|
||||
|
||||
```C
|
||||
InvestigationRecord *investigation_dao_load(
|
||||
Database *database
|
||||
);
|
||||
```
|
||||
|
||||
Cette fonction doit charger l’unique ligne de la table :
|
||||
|
||||
```text
|
||||
investigation
|
||||
```
|
||||
|
||||
La requête doit lire :
|
||||
|
||||
```SQL
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
root_path,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM investigation;
|
||||
```
|
||||
|
||||
### Utiliser exclusivement DatabaseStatement
|
||||
|
||||
Le DAO doit utiliser :
|
||||
|
||||
```C
|
||||
database_statement_prepare()
|
||||
database_statement_step()
|
||||
database_statement_column_text()
|
||||
database_statement_finalize()
|
||||
```
|
||||
|
||||
Le DAO ne doit pas utiliser directement :
|
||||
|
||||
```C
|
||||
sqlite3_prepare_v2()
|
||||
sqlite3_step()
|
||||
sqlite3_column_text()
|
||||
sqlite3_finalize()
|
||||
```
|
||||
|
||||
Aucun type SQLite ne doit apparaître dans l’interface publique du DAO.
|
||||
|
||||
### Vérifier le nombre de lignes
|
||||
|
||||
La base d’une enquête doit contenir exactement une ligne dans la table
|
||||
`investigation`.
|
||||
|
||||
Le DAO doit gérer les cas suivants :
|
||||
|
||||
Une ligne
|
||||
```
|
||||
ROW
|
||||
DONE
|
||||
```
|
||||
Résultat :
|
||||
|
||||
```
|
||||
succès
|
||||
```
|
||||
|
||||
Le DAO construit et retourne un `InvestigationRecord`.
|
||||
|
||||
#### Aucune ligne
|
||||
|
||||
`DONE` dès le premier step
|
||||
|
||||
Résultat :
|
||||
|
||||
```
|
||||
échec
|
||||
```
|
||||
|
||||
Le DAO retourne `NULL` et enregistre :
|
||||
|
||||
```
|
||||
DATABASE_ERROR_INVALID_STATE
|
||||
```
|
||||
|
||||
#### Plusieurs lignes
|
||||
|
||||
```
|
||||
ROW
|
||||
ROW
|
||||
```
|
||||
|
||||
Résultat :
|
||||
|
||||
`échec`
|
||||
|
||||
Le DAO libère le modèle temporaire, retourne `NULL` et enregistre :
|
||||
```C
|
||||
DATABASE_ERROR_INVALID_STATE
|
||||
```
|
||||
|
||||
### Propager les erreurs
|
||||
|
||||
Les erreurs de préparation ou d’exécution provenant de `DatabaseStatement`
|
||||
doivent rester disponibles dans le contexte `Database`.
|
||||
|
||||
Le DAO doit enregistrer une erreur explicite pour :
|
||||
|
||||
- une table vide ;
|
||||
- plusieurs lignes dans investigation ;
|
||||
- une colonne obligatoire invalide ;
|
||||
- une allocation impossible ;
|
||||
- un état incohérent.
|
||||
|
||||
Les codes prévus sont :
|
||||
|
||||
```C
|
||||
DATABASE_ERROR_INVALID_ARGUMENT
|
||||
DATABASE_ERROR_INVALID_STATE
|
||||
DATABASE_ERROR_MEMORY
|
||||
DATABASE_ERROR_SQLITE
|
||||
```
|
||||
|
||||
Une lecture réussie doit laisser :
|
||||
|
||||
```C
|
||||
DATABASE_ERROR_NONE
|
||||
```
|
||||
|
||||
### Gérer proprement la mémoire
|
||||
|
||||
Le DAO doit libérer toutes les chaînes temporaires retournées par :
|
||||
|
||||
```C
|
||||
database_statement_column_text()
|
||||
```
|
||||
|
||||
après la construction du modèle.
|
||||
|
||||
Toutes les sorties d’échec doivent :
|
||||
|
||||
- finaliser la requête préparée ;
|
||||
- libérer les chaînes déjà lues ;
|
||||
- libérer le modèle partiellement construit ;
|
||||
- ne provoquer aucune fuite mémoire.
|
||||
- Tests à ajouter
|
||||
|
||||
#### Test du modèle
|
||||
|
||||
Créer :
|
||||
|
||||
```
|
||||
tests/test_investigation_record.c
|
||||
```
|
||||
|
||||
Vérifier :
|
||||
|
||||
- la création avec des données valides ;
|
||||
- la copie des chaînes ;
|
||||
- les cinq accesseurs ;
|
||||
- le refus des paramètres `NULL` ;
|
||||
- le refus des chaînes vides ;
|
||||
- `investigation_record_free(NULL)`.
|
||||
|
||||
#### Test du DAO
|
||||
|
||||
Créer :
|
||||
|
||||
```
|
||||
tests/test_investigation_dao.c
|
||||
Chargement valide
|
||||
```
|
||||
|
||||
Créer une base temporaire avec :
|
||||
|
||||
```C
|
||||
database_initialize()
|
||||
```
|
||||
|
||||
Puis :
|
||||
|
||||
- ouvrir la base avec `database_open()` ;
|
||||
- charger l’enquête avec `investigation_dao_load()` ;
|
||||
- vérifier que le modèle n’est pas `NULL` ;
|
||||
- vérifier l’UUID ;
|
||||
- vérifier le nom ;
|
||||
- vérifier le chemin racine ;
|
||||
- vérifier `created_at` ;
|
||||
- vérifier `updated_at` ;
|
||||
- vérifier que l’erreur Database vaut `DATABASE_ERROR_NONE`.
|
||||
|
||||
#### Paramètre invalide
|
||||
|
||||
Vérifier :
|
||||
|
||||
```C
|
||||
investigation_dao_load(NULL) == NULL
|
||||
```
|
||||
|
||||
#### Table vide
|
||||
|
||||
Créer une base contenant la table investigation sans ligne.
|
||||
|
||||
Vérifier :
|
||||
|
||||
```
|
||||
résultat == NULL
|
||||
DATABASE_ERROR_INVALID_STATE
|
||||
message non vide
|
||||
```
|
||||
|
||||
#### Plusieurs lignes
|
||||
|
||||
Créer une base contenant deux lignes dans la table investigation.
|
||||
|
||||
Vérifier :
|
||||
|
||||
```
|
||||
résultat == NULL
|
||||
DATABASE_ERROR_INVALID_STATE
|
||||
message non vide
|
||||
```
|
||||
|
||||
#### Données invalides
|
||||
|
||||
Créer une situation dans laquelle une colonne obligatoire ne peut pas être
|
||||
correctement lue.
|
||||
|
||||
Vérifier :
|
||||
|
||||
```
|
||||
résultat == NULL
|
||||
erreur enregistrée
|
||||
aucune fuite de mémoire
|
||||
```
|
||||
|
||||
## Makefile
|
||||
|
||||
Ajouter les exécutables :
|
||||
|
||||
```
|
||||
tests/test_investigation_record
|
||||
tests/test_investigation_dao
|
||||
```
|
||||
|
||||
Ajouter leurs règles de compilation.
|
||||
|
||||
Les tests du DAO devront notamment compiler avec :
|
||||
|
||||
```
|
||||
src/models/investigation_record.c
|
||||
src/dao/investigation_dao.c
|
||||
src/database/database.c
|
||||
src/database/schema.c
|
||||
src/database/statement.c
|
||||
src/database/transaction.c
|
||||
src/database/error.c
|
||||
```
|
||||
|
||||
Ajouter les nouveaux tests aux cibles :
|
||||
|
||||
```
|
||||
test
|
||||
clean
|
||||
```
|
||||
### Critères d’acceptation
|
||||
|
||||
- [ ] Le type `InvestigationRecord` est opaque.
|
||||
- [ ] Le modèle ne dépend ni de SQLite ni de GTK.
|
||||
- [ ] Le constructeur valide tous les champs obligatoires.
|
||||
- [ ] Toutes les chaînes sont copiées.
|
||||
- [ ] Une fonction de libération complète est disponible.
|
||||
- [ ] Les cinq accesseurs en lecture seule sont disponibles.
|
||||
- [ ] `investigation_dao_load()` charge l’enquête courante.
|
||||
- [ ] Le DAO utilise uniquement l’API `DatabaseStatement`.
|
||||
- [ ] Aucun appel direct à SQLite n’apparaît dans le DAO.
|
||||
- [ ] Une base contenant exactement une enquête est acceptée.
|
||||
- [ ] Une table vide est refusée.
|
||||
- [ ] Plusieurs lignes sont refusées.
|
||||
- [ ] Les erreurs sont enregistrées dans `Database`.
|
||||
- [ ] Toutes les ressources temporaires sont libérées.
|
||||
- [ ] Les tests du modèle sont valides.
|
||||
- [ ] Les tests du DAO sont valides.
|
||||
- [ ] Tous les anciens tests restent valides.
|
||||
- [ ] `make` réussit sans erreur.
|
||||
- [ ] `make test` réussit.
|
||||
- [ ] `git diff --check` ne retourne aucune erreur.
|
||||
|
||||
### Hors périmètre
|
||||
|
||||
Ce ticket ne doit pas ajouter :
|
||||
|
||||
- la création d’une enquête par le DAO ;
|
||||
- la modification du nom de l’enquête ;
|
||||
- la modification du chemin racine ;
|
||||
- la suppression d’une enquête ;
|
||||
- un CRUD complet ;
|
||||
- l’ouverture automatique dans l’interface GTK ;
|
||||
- l’affichage des informations dans la fenêtre ;
|
||||
- les DAO des preuves, sources ou entités ;
|
||||
- les migrations de schéma ;
|
||||
- une nouvelle version du schéma SQLite.
|
||||
|
||||
### Fichiers principalement concernés
|
||||
|
||||
```
|
||||
include/models/investigation_record.h
|
||||
include/dao/investigation_dao.h
|
||||
|
||||
src/models/investigation_record.c
|
||||
src/dao/investigation_dao.c
|
||||
|
||||
tests/test_investigation_record.c
|
||||
tests/test_investigation_dao.c
|
||||
|
||||
Makefile
|
||||
```
|
||||
## Résultat attendu
|
||||
|
||||
À la fin du ticket, le projet doit être capable de charger les informations
|
||||
persistées de l’enquête courante dans un modèle C indépendant de SQLite.
|
||||
|
||||
Le DAO devient le premier point d’accès métier structuré à la base de données.
|
||||
|
||||
### Commit attendu
|
||||
|
||||
Une fois tous les critères validés :
|
||||
|
||||
`feat(dao): add investigation record loader`
|
||||
|
||||
Avant le commit :
|
||||
|
||||
```bash
|
||||
make clean
|
||||
make
|
||||
make test
|
||||
git diff --check
|
||||
git status --short
|
||||
git add .
|
||||
git diff --cached --stat
|
||||
git diff --cached
|
||||
git commit -m "feat(dao): add investigation record loader"
|
||||
git push
|
||||
```
|
||||
25
include/dao/investigation_dao.h
Normal file
25
include/dao/investigation_dao.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/******************************************************************************
|
||||
* @file investigation_dao.h
|
||||
* @brief Accès aux informations persistées de l'enquête.
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef LABFY_INVESTIGATION_INVESTIGATION_DAO_H
|
||||
#define LABFY_INVESTIGATION_INVESTIGATION_DAO_H
|
||||
|
||||
#include "database/database.h"
|
||||
#include "models/investigation_record.h"
|
||||
|
||||
/**
|
||||
* @brief Charge l'unique enquête enregistrée dans la base.
|
||||
*
|
||||
* La fonction attend exactement une ligne dans la table investigation.
|
||||
*
|
||||
* @param database Connexion ouverte.
|
||||
*
|
||||
* @return Un nouveau modèle InvestigationRecord, ou NULL en cas d'échec.
|
||||
*/
|
||||
InvestigationRecord *investigation_dao_load(
|
||||
Database *database
|
||||
);
|
||||
|
||||
#endif
|
||||
|
|
@ -44,6 +44,21 @@ const char *database_error_get_message(
|
|||
const Database *database
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Enregistre une erreur dans une connexion Database.
|
||||
*
|
||||
* Le message est copié par la connexion.
|
||||
*
|
||||
* @param database Connexion Database.
|
||||
* @param error_code Code de l'erreur.
|
||||
* @param error_message Message descriptif.
|
||||
*/
|
||||
void database_error_set(
|
||||
Database *database,
|
||||
DatabaseErrorCode error_code,
|
||||
const char *error_message
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Efface la dernière erreur enregistrée.
|
||||
*
|
||||
|
|
|
|||
92
include/models/investigation_record.h
Normal file
92
include/models/investigation_record.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/******************************************************************************
|
||||
* @file investigation_record.h
|
||||
* @brief Modèle représentant les informations persistées d'une enquête.
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef LABFY_INVESTIGATION_INVESTIGATION_RECORD_H
|
||||
#define LABFY_INVESTIGATION_INVESTIGATION_RECORD_H
|
||||
|
||||
/**
|
||||
* @brief Modèle opaque représentant une ligne de la table investigation.
|
||||
*/
|
||||
typedef struct InvestigationRecord InvestigationRecord;
|
||||
|
||||
/**
|
||||
* @brief Crée un modèle représentant une enquête persistée.
|
||||
*
|
||||
* Toutes les chaînes sont copiées par le modèle.
|
||||
*
|
||||
* @param id Identifiant UUID de l'enquête.
|
||||
* @param name Nom de l'enquête.
|
||||
* @param root_path Chemin racine de l'enquête.
|
||||
* @param created_at Date de création.
|
||||
* @param updated_at Date de dernière modification.
|
||||
*
|
||||
* @return Un nouveau modèle, ou NULL si les données sont invalides
|
||||
* ou si une allocation échoue.
|
||||
*/
|
||||
InvestigationRecord *investigation_record_new(
|
||||
const char *id,
|
||||
const char *name,
|
||||
const char *root_path,
|
||||
const char *created_at,
|
||||
const char *updated_at
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Libère un modèle InvestigationRecord.
|
||||
*
|
||||
* Cette fonction accepte NULL.
|
||||
*
|
||||
* @param record Modèle à libérer.
|
||||
*/
|
||||
void investigation_record_free(
|
||||
InvestigationRecord *record
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne l'identifiant de l'enquête.
|
||||
*
|
||||
* @return Identifiant appartenant au modèle, ou NULL.
|
||||
*/
|
||||
const char *investigation_record_get_id(
|
||||
const InvestigationRecord *record
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne le nom de l'enquête.
|
||||
*
|
||||
* @return Nom appartenant au modèle, ou NULL.
|
||||
*/
|
||||
const char *investigation_record_get_name(
|
||||
const InvestigationRecord *record
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne le chemin racine de l'enquête.
|
||||
*
|
||||
* @return Chemin appartenant au modèle, ou NULL.
|
||||
*/
|
||||
const char *investigation_record_get_root_path(
|
||||
const InvestigationRecord *record
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne la date de création.
|
||||
*
|
||||
* @return Date appartenant au modèle, ou NULL.
|
||||
*/
|
||||
const char *investigation_record_get_created_at(
|
||||
const InvestigationRecord *record
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne la date de dernière modification.
|
||||
*
|
||||
* @return Date appartenant au modèle, ou NULL.
|
||||
*/
|
||||
const char *investigation_record_get_updated_at(
|
||||
const InvestigationRecord *record
|
||||
);
|
||||
|
||||
#endif
|
||||
Binary file not shown.
197
src/dao/investigation_dao.c
Normal file
197
src/dao/investigation_dao.c
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
/******************************************************************************
|
||||
* @file investigation_dao.c
|
||||
* @brief Chargement de l'enquête persistée.
|
||||
******************************************************************************/
|
||||
|
||||
#include "dao/investigation_dao.h"
|
||||
|
||||
#include "database/error.h"
|
||||
#include "database/statement.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
/**
|
||||
* @brief Requête de chargement de l'enquête.
|
||||
*/
|
||||
static const char *const investigation_dao_load_sql =
|
||||
"SELECT "
|
||||
" id, "
|
||||
" name, "
|
||||
" root_path, "
|
||||
" created_at, "
|
||||
" updated_at "
|
||||
"FROM investigation;";
|
||||
|
||||
/**
|
||||
* @brief Vérifie qu'une valeur obligatoire est présente.
|
||||
*/
|
||||
static bool investigation_dao_text_is_valid(
|
||||
const char *text
|
||||
)
|
||||
{
|
||||
return text != NULL && text[0] != '\0';
|
||||
}
|
||||
|
||||
InvestigationRecord *investigation_dao_load(
|
||||
Database *database
|
||||
)
|
||||
{
|
||||
DatabaseStatement *statement = NULL;
|
||||
InvestigationRecord *record = NULL;
|
||||
|
||||
char *id = NULL;
|
||||
char *name = NULL;
|
||||
char *root_path = NULL;
|
||||
char *created_at = NULL;
|
||||
char *updated_at = NULL;
|
||||
|
||||
DatabaseStatementStepResult step_result =
|
||||
DATABASE_STATEMENT_STEP_ERROR;
|
||||
|
||||
if (database == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
statement = database_statement_prepare(
|
||||
database,
|
||||
investigation_dao_load_sql
|
||||
);
|
||||
|
||||
if (statement == NULL)
|
||||
{
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
step_result = database_statement_step(
|
||||
statement
|
||||
);
|
||||
|
||||
if (step_result == DATABASE_STATEMENT_STEP_ERROR)
|
||||
{
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (step_result == DATABASE_STATEMENT_STEP_DONE)
|
||||
{
|
||||
database_error_set(
|
||||
database,
|
||||
DATABASE_ERROR_INVALID_STATE,
|
||||
"La table investigation ne contient aucune ligne."
|
||||
);
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!database_statement_column_text(
|
||||
statement,
|
||||
0,
|
||||
&id
|
||||
) ||
|
||||
!database_statement_column_text(
|
||||
statement,
|
||||
1,
|
||||
&name
|
||||
) ||
|
||||
!database_statement_column_text(
|
||||
statement,
|
||||
2,
|
||||
&root_path
|
||||
) ||
|
||||
!database_statement_column_text(
|
||||
statement,
|
||||
3,
|
||||
&created_at
|
||||
) ||
|
||||
!database_statement_column_text(
|
||||
statement,
|
||||
4,
|
||||
&updated_at
|
||||
))
|
||||
{
|
||||
database_error_set(
|
||||
database,
|
||||
DATABASE_ERROR_INVALID_STATE,
|
||||
"Impossible de lire les colonnes de l'enquête."
|
||||
);
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!investigation_dao_text_is_valid(id) ||
|
||||
!investigation_dao_text_is_valid(name) ||
|
||||
!investigation_dao_text_is_valid(root_path) ||
|
||||
!investigation_dao_text_is_valid(created_at) ||
|
||||
!investigation_dao_text_is_valid(updated_at))
|
||||
{
|
||||
database_error_set(
|
||||
database,
|
||||
DATABASE_ERROR_INVALID_STATE,
|
||||
"L'enquête contient une colonne obligatoire invalide."
|
||||
);
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
record = investigation_record_new(
|
||||
id,
|
||||
name,
|
||||
root_path,
|
||||
created_at,
|
||||
updated_at
|
||||
);
|
||||
|
||||
if (record == NULL)
|
||||
{
|
||||
database_error_set(
|
||||
database,
|
||||
DATABASE_ERROR_MEMORY,
|
||||
"Impossible d'allouer le modèle de l'enquête."
|
||||
);
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/*
|
||||
* Une seconde ligne indiquerait une base incohérente.
|
||||
*/
|
||||
step_result = database_statement_step(
|
||||
statement
|
||||
);
|
||||
|
||||
if (step_result == DATABASE_STATEMENT_STEP_ERROR)
|
||||
{
|
||||
investigation_record_free(record);
|
||||
record = NULL;
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (step_result == DATABASE_STATEMENT_STEP_ROW)
|
||||
{
|
||||
database_error_set(
|
||||
database,
|
||||
DATABASE_ERROR_INVALID_STATE,
|
||||
"La table investigation contient plusieurs lignes."
|
||||
);
|
||||
|
||||
investigation_record_free(record);
|
||||
record = NULL;
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
database_error_clear(database);
|
||||
|
||||
cleanup:
|
||||
|
||||
g_free(updated_at);
|
||||
g_free(created_at);
|
||||
g_free(root_path);
|
||||
g_free(name);
|
||||
g_free(id);
|
||||
|
||||
database_statement_finalize(statement);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
|
@ -22,6 +22,19 @@ const char *database_error_get_message(
|
|||
return database_get_error_message_internal(database);
|
||||
}
|
||||
|
||||
void database_error_set(
|
||||
Database *database,
|
||||
DatabaseErrorCode error_code,
|
||||
const char *error_message
|
||||
)
|
||||
{
|
||||
database_set_error(
|
||||
database,
|
||||
error_code,
|
||||
error_message
|
||||
);
|
||||
}
|
||||
|
||||
void database_error_clear(
|
||||
Database *database
|
||||
)
|
||||
|
|
|
|||
|
|
@ -590,33 +590,24 @@ void database_statement_finalize(
|
|||
DatabaseStatement *statement
|
||||
)
|
||||
{
|
||||
sqlite3 *database_handle = NULL;
|
||||
int result = SQLITE_OK;
|
||||
|
||||
if (statement == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
database_handle = database_get_handle(
|
||||
statement->database
|
||||
);
|
||||
|
||||
if (statement->handle != NULL)
|
||||
{
|
||||
result = sqlite3_finalize(
|
||||
/*
|
||||
* sqlite3_finalize() peut retourner l'erreur produite par le dernier
|
||||
* sqlite3_step(), même si la requête est correctement détruite.
|
||||
*
|
||||
* Cette erreur est déjà enregistrée par database_statement_step().
|
||||
*/
|
||||
sqlite3_finalize(
|
||||
statement->handle
|
||||
);
|
||||
|
||||
if (result != SQLITE_OK)
|
||||
{
|
||||
g_warning(
|
||||
"Impossible de finaliser la requête SQL : %s",
|
||||
database_handle != NULL
|
||||
? sqlite3_errmsg(database_handle)
|
||||
: sqlite3_errstr(result)
|
||||
);
|
||||
}
|
||||
statement->handle = NULL;
|
||||
}
|
||||
|
||||
g_free(statement);
|
||||
|
|
|
|||
199
src/models/investigation_record.c
Normal file
199
src/models/investigation_record.c
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
/******************************************************************************
|
||||
* @file investigation_record.c
|
||||
* @brief Implémentation du modèle InvestigationRecord.
|
||||
******************************************************************************/
|
||||
|
||||
#include "models/investigation_record.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* @brief Représentation privée d'une enquête persistée.
|
||||
*/
|
||||
struct InvestigationRecord
|
||||
{
|
||||
char *id;
|
||||
char *name;
|
||||
char *root_path;
|
||||
char *created_at;
|
||||
char *updated_at;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Vérifie qu'une chaîne obligatoire est présente et non vide.
|
||||
*/
|
||||
static gboolean investigation_record_text_is_valid(
|
||||
const char *text
|
||||
)
|
||||
{
|
||||
return text != NULL && text[0] != '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copie une chaîne en utilisant une allocation non fatale.
|
||||
*
|
||||
* Contrairement à g_strdup(), cette fonction retourne NULL si
|
||||
* l'allocation échoue au lieu d'interrompre le programme.
|
||||
*/
|
||||
static char *investigation_record_copy_text(
|
||||
const char *text
|
||||
)
|
||||
{
|
||||
char *text_copy = NULL;
|
||||
gsize text_length = 0;
|
||||
|
||||
if (text == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
text_length = strlen(text);
|
||||
|
||||
if (text_length == G_MAXSIZE)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
text_copy = g_try_malloc(
|
||||
text_length + 1
|
||||
);
|
||||
|
||||
if (text_copy == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(
|
||||
text_copy,
|
||||
text,
|
||||
text_length + 1
|
||||
);
|
||||
|
||||
return text_copy;
|
||||
}
|
||||
|
||||
InvestigationRecord *investigation_record_new(
|
||||
const char *id,
|
||||
const char *name,
|
||||
const char *root_path,
|
||||
const char *created_at,
|
||||
const char *updated_at
|
||||
)
|
||||
{
|
||||
InvestigationRecord *record = NULL;
|
||||
|
||||
if (!investigation_record_text_is_valid(id) ||
|
||||
!investigation_record_text_is_valid(name) ||
|
||||
!investigation_record_text_is_valid(root_path) ||
|
||||
!investigation_record_text_is_valid(created_at) ||
|
||||
!investigation_record_text_is_valid(updated_at))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
record = g_try_new0(
|
||||
InvestigationRecord,
|
||||
1
|
||||
);
|
||||
|
||||
if (record == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
record->id = investigation_record_copy_text(id);
|
||||
record->name = investigation_record_copy_text(name);
|
||||
record->root_path = investigation_record_copy_text(root_path);
|
||||
record->created_at = investigation_record_copy_text(created_at);
|
||||
record->updated_at = investigation_record_copy_text(updated_at);
|
||||
|
||||
if (record->id == NULL ||
|
||||
record->name == NULL ||
|
||||
record->root_path == NULL ||
|
||||
record->created_at == NULL ||
|
||||
record->updated_at == NULL)
|
||||
{
|
||||
investigation_record_free(record);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
void investigation_record_free(
|
||||
InvestigationRecord *record
|
||||
)
|
||||
{
|
||||
if (record == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
g_free(record->updated_at);
|
||||
g_free(record->created_at);
|
||||
g_free(record->root_path);
|
||||
g_free(record->name);
|
||||
g_free(record->id);
|
||||
g_free(record);
|
||||
}
|
||||
|
||||
const char *investigation_record_get_id(
|
||||
const InvestigationRecord *record
|
||||
)
|
||||
{
|
||||
if (record == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return record->id;
|
||||
}
|
||||
|
||||
const char *investigation_record_get_name(
|
||||
const InvestigationRecord *record
|
||||
)
|
||||
{
|
||||
if (record == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return record->name;
|
||||
}
|
||||
|
||||
const char *investigation_record_get_root_path(
|
||||
const InvestigationRecord *record
|
||||
)
|
||||
{
|
||||
if (record == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return record->root_path;
|
||||
}
|
||||
|
||||
const char *investigation_record_get_created_at(
|
||||
const InvestigationRecord *record
|
||||
)
|
||||
{
|
||||
if (record == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return record->created_at;
|
||||
}
|
||||
|
||||
const char *investigation_record_get_updated_at(
|
||||
const InvestigationRecord *record
|
||||
)
|
||||
{
|
||||
if (record == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return record->updated_at;
|
||||
}
|
||||
Binary file not shown.
|
|
@ -133,6 +133,7 @@ static void test_database_initialize_valid_database(void)
|
|||
char *investigation_created_at = NULL;
|
||||
char *investigation_id = NULL;
|
||||
char *investigation_count = NULL;
|
||||
char *investigation_updated_at = NULL;
|
||||
|
||||
sqlite3 *database = NULL;
|
||||
GError *error = NULL;
|
||||
|
|
@ -231,6 +232,12 @@ static void test_database_initialize_valid_database(void)
|
|||
"LIMIT 1;"
|
||||
);
|
||||
|
||||
investigation_updated_at = test_database_read_single_text(
|
||||
database,
|
||||
"SELECT updated_at FROM investigation "
|
||||
"LIMIT 1;"
|
||||
);
|
||||
|
||||
investigation_id = test_database_read_single_text(
|
||||
database,
|
||||
"SELECT id FROM investigation "
|
||||
|
|
@ -253,6 +260,13 @@ static void test_database_initialize_valid_database(void)
|
|||
assert(strcmp(investigation_root_path, temporary_directory) == 0);
|
||||
assert(strcmp(investigation_created_at, created_at) == 0);
|
||||
|
||||
assert(investigation_updated_at[0] != '\0');
|
||||
|
||||
assert(strcmp(investigation_updated_at,created_at) == 0);
|
||||
|
||||
assert(strcmp(investigation_id,investigation_uuid) == 0
|
||||
);
|
||||
|
||||
result = sqlite3_close(database);
|
||||
|
||||
assert(result == SQLITE_OK);
|
||||
|
|
|
|||
BIN
tests/test_error
BIN
tests/test_error
Binary file not shown.
BIN
tests/test_investigation_dao
Executable file
BIN
tests/test_investigation_dao
Executable file
Binary file not shown.
374
tests/test_investigation_dao.c
Normal file
374
tests/test_investigation_dao.c
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
/******************************************************************************
|
||||
* @file test_investigation_dao.c
|
||||
* @brief Tests du DAO de l'enquête.
|
||||
******************************************************************************/
|
||||
|
||||
#include "dao/investigation_dao.h"
|
||||
|
||||
#include "database/database.h"
|
||||
#include "database/error.h"
|
||||
#include "database/statement.h"
|
||||
#include "models/investigation_record.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
/**
|
||||
* @brief Crée une table investigation minimale pour les tests du DAO.
|
||||
*/
|
||||
static void test_investigation_dao_create_table(
|
||||
Database *database
|
||||
)
|
||||
{
|
||||
DatabaseStatement *statement = NULL;
|
||||
|
||||
assert(database != NULL);
|
||||
|
||||
statement = database_statement_prepare(
|
||||
database,
|
||||
"CREATE TABLE investigation"
|
||||
"("
|
||||
" id TEXT NOT NULL,"
|
||||
" name TEXT NOT NULL,"
|
||||
" root_path TEXT NOT NULL,"
|
||||
" created_at TEXT NOT NULL,"
|
||||
" updated_at TEXT NOT NULL"
|
||||
");"
|
||||
);
|
||||
|
||||
assert(statement != NULL);
|
||||
|
||||
assert(
|
||||
database_statement_step(statement) ==
|
||||
DATABASE_STATEMENT_STEP_DONE
|
||||
);
|
||||
|
||||
database_statement_finalize(statement);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Insère une ligne dans la table investigation de test.
|
||||
*/
|
||||
static void test_investigation_dao_insert_row(
|
||||
Database *database,
|
||||
const char *id,
|
||||
const char *name
|
||||
)
|
||||
{
|
||||
DatabaseStatement *statement = NULL;
|
||||
|
||||
assert(database != NULL);
|
||||
assert(id != NULL);
|
||||
assert(name != NULL);
|
||||
|
||||
statement = database_statement_prepare(
|
||||
database,
|
||||
"INSERT INTO investigation"
|
||||
"("
|
||||
" id,"
|
||||
" name,"
|
||||
" root_path,"
|
||||
" created_at,"
|
||||
" updated_at"
|
||||
")"
|
||||
"VALUES (?, ?, ?, ?, ?);"
|
||||
);
|
||||
|
||||
assert(statement != NULL);
|
||||
|
||||
assert(
|
||||
database_statement_bind_text(
|
||||
statement,
|
||||
1,
|
||||
id
|
||||
)
|
||||
);
|
||||
|
||||
assert(
|
||||
database_statement_bind_text(
|
||||
statement,
|
||||
2,
|
||||
name
|
||||
)
|
||||
);
|
||||
|
||||
assert(
|
||||
database_statement_bind_text(
|
||||
statement,
|
||||
3,
|
||||
"/tmp/enquete-dao"
|
||||
)
|
||||
);
|
||||
|
||||
assert(
|
||||
database_statement_bind_text(
|
||||
statement,
|
||||
4,
|
||||
"2026-07-16T12:00:00Z"
|
||||
)
|
||||
);
|
||||
|
||||
assert(
|
||||
database_statement_bind_text(
|
||||
statement,
|
||||
5,
|
||||
"2026-07-16T12:00:00Z"
|
||||
)
|
||||
);
|
||||
|
||||
assert(
|
||||
database_statement_step(statement) ==
|
||||
DATABASE_STATEMENT_STEP_DONE
|
||||
);
|
||||
|
||||
database_statement_finalize(statement);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Vérifie le refus d'une table investigation vide.
|
||||
*/
|
||||
static void test_investigation_dao_load_empty_table(void)
|
||||
{
|
||||
Database *database = NULL;
|
||||
InvestigationRecord *record = NULL;
|
||||
const char *error_message = NULL;
|
||||
|
||||
database = database_open(":memory:");
|
||||
|
||||
assert(database != NULL);
|
||||
|
||||
test_investigation_dao_create_table(database);
|
||||
|
||||
record = investigation_dao_load(database);
|
||||
|
||||
assert(record == NULL);
|
||||
|
||||
assert(
|
||||
database_error_get_code(database) ==
|
||||
DATABASE_ERROR_INVALID_STATE
|
||||
);
|
||||
|
||||
error_message = database_error_get_message(database);
|
||||
|
||||
assert(error_message != NULL);
|
||||
assert(error_message[0] != '\0');
|
||||
|
||||
database_close(database);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Vérifie le refus de plusieurs enquêtes dans la même base.
|
||||
*/
|
||||
static void test_investigation_dao_load_multiple_rows(void)
|
||||
{
|
||||
Database *database = NULL;
|
||||
InvestigationRecord *record = NULL;
|
||||
const char *error_message = NULL;
|
||||
|
||||
database = database_open(":memory:");
|
||||
|
||||
assert(database != NULL);
|
||||
|
||||
test_investigation_dao_create_table(database);
|
||||
|
||||
test_investigation_dao_insert_row(
|
||||
database,
|
||||
"11111111-1111-4111-8111-111111111111",
|
||||
"Premiere_Enquete"
|
||||
);
|
||||
|
||||
test_investigation_dao_insert_row(
|
||||
database,
|
||||
"22222222-2222-4222-8222-222222222222",
|
||||
"Seconde_Enquete"
|
||||
);
|
||||
|
||||
record = investigation_dao_load(database);
|
||||
|
||||
assert(record == NULL);
|
||||
|
||||
assert(
|
||||
database_error_get_code(database) ==
|
||||
DATABASE_ERROR_INVALID_STATE
|
||||
);
|
||||
|
||||
error_message = database_error_get_message(database);
|
||||
|
||||
assert(error_message != NULL);
|
||||
assert(error_message[0] != '\0');
|
||||
|
||||
database_close(database);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Vérifie le chargement d'une enquête valide.
|
||||
*/
|
||||
static void test_investigation_dao_load_valid(void)
|
||||
{
|
||||
char *temporary_directory = NULL;
|
||||
char *database_path = NULL;
|
||||
|
||||
Database *database = NULL;
|
||||
InvestigationRecord *record = NULL;
|
||||
|
||||
const char *id = NULL;
|
||||
const char *created_at = NULL;
|
||||
const char *updated_at = NULL;
|
||||
|
||||
GError *error = NULL;
|
||||
|
||||
temporary_directory = g_dir_make_tmp(
|
||||
"labfy-investigation-dao-test-XXXXXX",
|
||||
&error
|
||||
);
|
||||
|
||||
assert(temporary_directory != NULL);
|
||||
assert(error == NULL);
|
||||
|
||||
database_path = g_build_filename(
|
||||
temporary_directory,
|
||||
"Enquete.sqlite",
|
||||
NULL
|
||||
);
|
||||
|
||||
assert(database_path != NULL);
|
||||
|
||||
assert(
|
||||
database_initialize(
|
||||
database_path,
|
||||
"Enquete_DAO",
|
||||
temporary_directory
|
||||
)
|
||||
);
|
||||
|
||||
database = database_open(
|
||||
database_path
|
||||
);
|
||||
|
||||
assert(database != NULL);
|
||||
|
||||
record = investigation_dao_load(
|
||||
database
|
||||
);
|
||||
|
||||
assert(record != NULL);
|
||||
|
||||
id = investigation_record_get_id(record);
|
||||
created_at = investigation_record_get_created_at(record);
|
||||
updated_at = investigation_record_get_updated_at(record);
|
||||
|
||||
assert(id != NULL);
|
||||
assert(g_uuid_string_is_valid(id));
|
||||
|
||||
assert(
|
||||
strcmp(
|
||||
investigation_record_get_name(record),
|
||||
"Enquete_DAO"
|
||||
) == 0
|
||||
);
|
||||
|
||||
assert(
|
||||
strcmp(
|
||||
investigation_record_get_root_path(record),
|
||||
temporary_directory
|
||||
) == 0
|
||||
);
|
||||
|
||||
assert(created_at != NULL);
|
||||
assert(created_at[0] != '\0');
|
||||
|
||||
assert(updated_at != NULL);
|
||||
assert(updated_at[0] != '\0');
|
||||
|
||||
assert(
|
||||
strcmp(
|
||||
created_at,
|
||||
updated_at
|
||||
) == 0
|
||||
);
|
||||
|
||||
assert(
|
||||
database_error_get_code(database) ==
|
||||
DATABASE_ERROR_NONE
|
||||
);
|
||||
|
||||
investigation_record_free(record);
|
||||
database_close(database);
|
||||
|
||||
assert(g_remove(database_path) == 0);
|
||||
assert(g_rmdir(temporary_directory) == 0);
|
||||
|
||||
g_free(database_path);
|
||||
g_free(temporary_directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Vérifie le refus d'une connexion absente.
|
||||
*/
|
||||
static void test_investigation_dao_load_null_database(void)
|
||||
{
|
||||
assert(
|
||||
investigation_dao_load(NULL) == NULL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Vérifie le refus d'une enquête contenant une valeur obligatoire vide.
|
||||
*/
|
||||
static void test_investigation_dao_load_invalid_row(void)
|
||||
{
|
||||
Database *database = NULL;
|
||||
InvestigationRecord *record = NULL;
|
||||
const char *error_message = NULL;
|
||||
|
||||
database = database_open(":memory:");
|
||||
|
||||
assert(database != NULL);
|
||||
|
||||
test_investigation_dao_create_table(database);
|
||||
|
||||
/*
|
||||
* La colonne name est NOT NULL, mais une chaîne vide reste autorisée
|
||||
* par SQLite. Le DAO doit la considérer comme invalide.
|
||||
*/
|
||||
test_investigation_dao_insert_row(
|
||||
database,
|
||||
"33333333-3333-4333-8333-333333333333",
|
||||
""
|
||||
);
|
||||
|
||||
record = investigation_dao_load(database);
|
||||
|
||||
assert(record == NULL);
|
||||
|
||||
assert(
|
||||
database_error_get_code(database) ==
|
||||
DATABASE_ERROR_INVALID_STATE
|
||||
);
|
||||
|
||||
error_message = database_error_get_message(database);
|
||||
|
||||
assert(error_message != NULL);
|
||||
assert(error_message[0] != '\0');
|
||||
|
||||
database_close(database);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test_investigation_dao_load_valid();
|
||||
test_investigation_dao_load_null_database();
|
||||
test_investigation_dao_load_empty_table();
|
||||
test_investigation_dao_load_multiple_rows();
|
||||
test_investigation_dao_load_invalid_row();
|
||||
|
||||
printf(
|
||||
"InvestigationDao : tous les tests sont valides.\n"
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
BIN
tests/test_investigation_record
Executable file
BIN
tests/test_investigation_record
Executable file
Binary file not shown.
269
tests/test_investigation_record.c
Normal file
269
tests/test_investigation_record.c
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
/******************************************************************************
|
||||
* @file test_investigation_record.c
|
||||
* @brief Tests du modèle InvestigationRecord.
|
||||
******************************************************************************/
|
||||
|
||||
#include "models/investigation_record.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* @brief Vérifie la création et les accesseurs du modèle.
|
||||
*/
|
||||
static void test_investigation_record_valid(void)
|
||||
{
|
||||
InvestigationRecord *record = NULL;
|
||||
|
||||
char id[] = "6e62b9af-2046-4efd-b3b9-29869f816951";
|
||||
char name[] = "Enquete_Test";
|
||||
char root_path[] = "/tmp/enquete-test";
|
||||
char created_at[] = "2026-07-16T10:00:00Z";
|
||||
char updated_at[] = "2026-07-16T10:00:00Z";
|
||||
|
||||
record = investigation_record_new(
|
||||
id,
|
||||
name,
|
||||
root_path,
|
||||
created_at,
|
||||
updated_at
|
||||
);
|
||||
|
||||
assert(record != NULL);
|
||||
|
||||
assert(
|
||||
strcmp(
|
||||
investigation_record_get_id(record),
|
||||
id
|
||||
) == 0
|
||||
);
|
||||
|
||||
assert(
|
||||
strcmp(
|
||||
investigation_record_get_name(record),
|
||||
name
|
||||
) == 0
|
||||
);
|
||||
|
||||
assert(
|
||||
strcmp(
|
||||
investigation_record_get_root_path(record),
|
||||
root_path
|
||||
) == 0
|
||||
);
|
||||
|
||||
assert(
|
||||
strcmp(
|
||||
investigation_record_get_created_at(record),
|
||||
created_at
|
||||
) == 0
|
||||
);
|
||||
|
||||
assert(
|
||||
strcmp(
|
||||
investigation_record_get_updated_at(record),
|
||||
updated_at
|
||||
) == 0
|
||||
);
|
||||
|
||||
/*
|
||||
* Le modèle doit posséder ses propres copies.
|
||||
*/
|
||||
id[0] = 'X';
|
||||
name[0] = 'X';
|
||||
root_path[0] = 'X';
|
||||
created_at[0] = 'X';
|
||||
updated_at[0] = 'X';
|
||||
|
||||
assert(
|
||||
strcmp(
|
||||
investigation_record_get_id(record),
|
||||
"6e62b9af-2046-4efd-b3b9-29869f816951"
|
||||
) == 0
|
||||
);
|
||||
|
||||
assert(
|
||||
strcmp(
|
||||
investigation_record_get_name(record),
|
||||
"Enquete_Test"
|
||||
) == 0
|
||||
);
|
||||
|
||||
assert(
|
||||
strcmp(
|
||||
investigation_record_get_root_path(record),
|
||||
"/tmp/enquete-test"
|
||||
) == 0
|
||||
);
|
||||
|
||||
assert(
|
||||
strcmp(
|
||||
investigation_record_get_created_at(record),
|
||||
"2026-07-16T10:00:00Z"
|
||||
) == 0
|
||||
);
|
||||
|
||||
assert(
|
||||
strcmp(
|
||||
investigation_record_get_updated_at(record),
|
||||
"2026-07-16T10:00:00Z"
|
||||
) == 0
|
||||
);
|
||||
|
||||
investigation_record_free(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Vérifie le refus des pointeurs NULL.
|
||||
*/
|
||||
static void test_investigation_record_null_parameters(void)
|
||||
{
|
||||
assert(
|
||||
investigation_record_new(
|
||||
NULL,
|
||||
"Enquete",
|
||||
"/tmp/enquete",
|
||||
"2026-07-16T10:00:00Z",
|
||||
"2026-07-16T10:00:00Z"
|
||||
) == NULL
|
||||
);
|
||||
|
||||
assert(
|
||||
investigation_record_new(
|
||||
"id",
|
||||
NULL,
|
||||
"/tmp/enquete",
|
||||
"2026-07-16T10:00:00Z",
|
||||
"2026-07-16T10:00:00Z"
|
||||
) == NULL
|
||||
);
|
||||
|
||||
assert(
|
||||
investigation_record_new(
|
||||
"id",
|
||||
"Enquete",
|
||||
NULL,
|
||||
"2026-07-16T10:00:00Z",
|
||||
"2026-07-16T10:00:00Z"
|
||||
) == NULL
|
||||
);
|
||||
|
||||
assert(
|
||||
investigation_record_new(
|
||||
"id",
|
||||
"Enquete",
|
||||
"/tmp/enquete",
|
||||
NULL,
|
||||
"2026-07-16T10:00:00Z"
|
||||
) == NULL
|
||||
);
|
||||
|
||||
assert(
|
||||
investigation_record_new(
|
||||
"id",
|
||||
"Enquete",
|
||||
"/tmp/enquete",
|
||||
"2026-07-16T10:00:00Z",
|
||||
NULL
|
||||
) == NULL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Vérifie le refus des chaînes vides.
|
||||
*/
|
||||
static void test_investigation_record_empty_parameters(void)
|
||||
{
|
||||
assert(
|
||||
investigation_record_new(
|
||||
"",
|
||||
"Enquete",
|
||||
"/tmp/enquete",
|
||||
"2026-07-16T10:00:00Z",
|
||||
"2026-07-16T10:00:00Z"
|
||||
) == NULL
|
||||
);
|
||||
|
||||
assert(
|
||||
investigation_record_new(
|
||||
"id",
|
||||
"",
|
||||
"/tmp/enquete",
|
||||
"2026-07-16T10:00:00Z",
|
||||
"2026-07-16T10:00:00Z"
|
||||
) == NULL
|
||||
);
|
||||
|
||||
assert(
|
||||
investigation_record_new(
|
||||
"id",
|
||||
"Enquete",
|
||||
"",
|
||||
"2026-07-16T10:00:00Z",
|
||||
"2026-07-16T10:00:00Z"
|
||||
) == NULL
|
||||
);
|
||||
|
||||
assert(
|
||||
investigation_record_new(
|
||||
"id",
|
||||
"Enquete",
|
||||
"/tmp/enquete",
|
||||
"",
|
||||
"2026-07-16T10:00:00Z"
|
||||
) == NULL
|
||||
);
|
||||
|
||||
assert(
|
||||
investigation_record_new(
|
||||
"id",
|
||||
"Enquete",
|
||||
"/tmp/enquete",
|
||||
"2026-07-16T10:00:00Z",
|
||||
""
|
||||
) == NULL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Vérifie les opérations acceptant un modèle NULL.
|
||||
*/
|
||||
static void test_investigation_record_null_instance(void)
|
||||
{
|
||||
assert(
|
||||
investigation_record_get_id(NULL) == NULL
|
||||
);
|
||||
|
||||
assert(
|
||||
investigation_record_get_name(NULL) == NULL
|
||||
);
|
||||
|
||||
assert(
|
||||
investigation_record_get_root_path(NULL) == NULL
|
||||
);
|
||||
|
||||
assert(
|
||||
investigation_record_get_created_at(NULL) == NULL
|
||||
);
|
||||
|
||||
assert(
|
||||
investigation_record_get_updated_at(NULL) == NULL
|
||||
);
|
||||
|
||||
investigation_record_free(NULL);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test_investigation_record_valid();
|
||||
test_investigation_record_null_parameters();
|
||||
test_investigation_record_empty_parameters();
|
||||
test_investigation_record_null_instance();
|
||||
|
||||
printf(
|
||||
"InvestigationRecord : tous les tests sont valides.\n"
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue