feat: add relation DAO
This commit is contained in:
parent
c8860cf9f2
commit
b493ba6cd4
7 changed files with 5691 additions and 2 deletions
32
Makefile
32
Makefile
|
|
@ -45,6 +45,8 @@ ENTITY_RECORD_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
||||||
ENTITY_TYPE_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
ENTITY_TYPE_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
||||||
ENTITY_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
ENTITY_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
||||||
EVIDENCE_ENTITY_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
EVIDENCE_ENTITY_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
||||||
|
RELATION_RECORD_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
||||||
|
RELATION_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
||||||
|
|
||||||
SRC := $(shell find src -name "*.c")
|
SRC := $(shell find src -name "*.c")
|
||||||
|
|
||||||
|
|
@ -90,6 +92,8 @@ TEST_ENTITY_RECORD := tests/test_entity_record
|
||||||
TEST_ENTITY_TYPE_DAO := tests/test_entity_type_dao
|
TEST_ENTITY_TYPE_DAO := tests/test_entity_type_dao
|
||||||
TEST_ENTITY_DAO := tests/test_entity_dao
|
TEST_ENTITY_DAO := tests/test_entity_dao
|
||||||
TEST_EVIDENCE_ENTITY_DAO := tests/test_evidence_entity_dao
|
TEST_EVIDENCE_ENTITY_DAO := tests/test_evidence_entity_dao
|
||||||
|
TEST_RELATION_RECORD := tests/test_relation_record
|
||||||
|
TEST_RELATION_DAO := tests/test_relation_dao
|
||||||
|
|
||||||
all: $(TARGET)
|
all: $(TARGET)
|
||||||
|
|
||||||
|
|
@ -417,6 +421,24 @@ $(TEST_EVIDENCE_ENTITY_DAO): \
|
||||||
src/database/error.c
|
src/database/error.c
|
||||||
$(CC) $(EVIDENCE_ENTITY_DAO_TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
$(CC) $(EVIDENCE_ENTITY_DAO_TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||||
|
|
||||||
|
$(TEST_RELATION_RECORD): \
|
||||||
|
tests/test_relation_record.c \
|
||||||
|
src/models/relation_record.c
|
||||||
|
$(CC) $(RELATION_RECORD_TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||||||
|
|
||||||
|
$(TEST_RELATION_DAO): \
|
||||||
|
tests/test_relation_dao.c \
|
||||||
|
src/dao/relation_dao.c \
|
||||||
|
src/dao/entity_dao.c \
|
||||||
|
src/models/relation_record.c \
|
||||||
|
src/models/entity_record.c \
|
||||||
|
src/database/database.c \
|
||||||
|
src/database/schema.c \
|
||||||
|
src/database/statement.c \
|
||||||
|
src/database/transaction.c \
|
||||||
|
src/database/error.c
|
||||||
|
$(CC) $(RELATION_DAO_TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||||
|
|
||||||
test: \
|
test: \
|
||||||
$(TEST_NODE) \
|
$(TEST_NODE) \
|
||||||
$(TEST_TREE_MODEL) \
|
$(TEST_TREE_MODEL) \
|
||||||
|
|
@ -455,7 +477,9 @@ test: \
|
||||||
$(TEST_ENTITY_RECORD) \
|
$(TEST_ENTITY_RECORD) \
|
||||||
$(TEST_ENTITY_TYPE_DAO) \
|
$(TEST_ENTITY_TYPE_DAO) \
|
||||||
$(TEST_ENTITY_DAO) \
|
$(TEST_ENTITY_DAO) \
|
||||||
$(TEST_EVIDENCE_ENTITY_DAO)
|
$(TEST_EVIDENCE_ENTITY_DAO) \
|
||||||
|
$(TEST_RELATION_RECORD) \
|
||||||
|
$(TEST_RELATION_DAO)
|
||||||
@echo "Exécution des tests..."
|
@echo "Exécution des tests..."
|
||||||
@./$(TEST_NODE)
|
@./$(TEST_NODE)
|
||||||
@./$(TEST_TREE_MODEL)
|
@./$(TEST_TREE_MODEL)
|
||||||
|
|
@ -495,6 +519,8 @@ test: \
|
||||||
@$(TEST_ENTITY_TYPE_DAO)
|
@$(TEST_ENTITY_TYPE_DAO)
|
||||||
@$(TEST_ENTITY_DAO)
|
@$(TEST_ENTITY_DAO)
|
||||||
@$(TEST_EVIDENCE_ENTITY_DAO)
|
@$(TEST_EVIDENCE_ENTITY_DAO)
|
||||||
|
@$(TEST_RELATION_RECORD)
|
||||||
|
@$(TEST_RELATION_DAO)
|
||||||
@echo "Tous les tests sont valides."
|
@echo "Tous les tests sont valides."
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
|
|
@ -540,6 +566,8 @@ clean:
|
||||||
$(TEST_EVIDENCE_IMPORT_DIALOG) \
|
$(TEST_EVIDENCE_IMPORT_DIALOG) \
|
||||||
$(TEST_EVIDENCE_INTEGRITY_TASK) \
|
$(TEST_EVIDENCE_INTEGRITY_TASK) \
|
||||||
$(TEST_ENTITY_DAO) \
|
$(TEST_ENTITY_DAO) \
|
||||||
$(TEST_EVIDENCE_ENTITY_DAO)
|
$(TEST_EVIDENCE_ENTITY_DAO) \
|
||||||
|
$(TEST_RELATION_RECORD) \
|
||||||
|
$(TEST_RELATION_DAO)
|
||||||
|
|
||||||
.PHONY: clean run test
|
.PHONY: clean run test
|
||||||
|
|
|
||||||
151
include/dao/relation_dao.h
Normal file
151
include/dao/relation_dao.h
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file relation_dao.h
|
||||||
|
* @brief Persistance des relations entre entités dans SQLite.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_RELATION_DAO_H
|
||||||
|
#define LABFY_INVESTIGATION_RELATION_DAO_H
|
||||||
|
|
||||||
|
#include "database/database.h"
|
||||||
|
#include "models/relation_record.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Catégories d'erreurs produites par RelationDao.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RELATION_DAO_ERROR_INVALID_ARGUMENT,
|
||||||
|
RELATION_DAO_ERROR_MEMORY,
|
||||||
|
RELATION_DAO_ERROR_PREPARE,
|
||||||
|
RELATION_DAO_ERROR_BIND,
|
||||||
|
RELATION_DAO_ERROR_EXECUTE,
|
||||||
|
RELATION_DAO_ERROR_CONSTRAINT,
|
||||||
|
RELATION_DAO_ERROR_READ,
|
||||||
|
RELATION_DAO_ERROR_MODEL,
|
||||||
|
RELATION_DAO_ERROR_SCHEMA,
|
||||||
|
RELATION_DAO_ERROR_RANGE
|
||||||
|
} RelationDaoError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Domaine d'erreur du DAO des relations.
|
||||||
|
*/
|
||||||
|
#define RELATION_DAO_ERROR \
|
||||||
|
relation_dao_error_quark()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le domaine d'erreur du DAO.
|
||||||
|
*/
|
||||||
|
GQuark relation_dao_error_quark(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief DAO opaque donnant accès aux relations persistées.
|
||||||
|
*
|
||||||
|
* Le DAO emprunte la connexion Database reçue lors de sa création.
|
||||||
|
*/
|
||||||
|
typedef struct RelationDao RelationDao;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Crée un DAO utilisant une connexion Database existante.
|
||||||
|
*
|
||||||
|
* La connexion est empruntée et doit rester valide pendant toute
|
||||||
|
* la durée de vie du DAO.
|
||||||
|
*
|
||||||
|
* @param database Connexion SQLite ouverte.
|
||||||
|
* @param error Emplacement facultatif recevant une erreur.
|
||||||
|
*
|
||||||
|
* @return Nouveau DAO, ou NULL en cas d'échec.
|
||||||
|
*/
|
||||||
|
RelationDao *relation_dao_new(
|
||||||
|
Database *database,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Libère le DAO.
|
||||||
|
*
|
||||||
|
* La connexion Database empruntée n'est pas fermée.
|
||||||
|
* Cette fonction accepte NULL.
|
||||||
|
*
|
||||||
|
* @param relation_dao DAO à libérer.
|
||||||
|
*/
|
||||||
|
void relation_dao_free(
|
||||||
|
RelationDao *relation_dao
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Insère une nouvelle relation.
|
||||||
|
*
|
||||||
|
* Aucun enregistrement existant n'est remplacé.
|
||||||
|
*
|
||||||
|
* @param relation_dao DAO valide.
|
||||||
|
* @param relation_record Relation empruntée.
|
||||||
|
* @param error Emplacement facultatif recevant une erreur.
|
||||||
|
*
|
||||||
|
* @return TRUE si l'insertion réussit.
|
||||||
|
*/
|
||||||
|
gboolean relation_dao_insert(
|
||||||
|
RelationDao *relation_dao,
|
||||||
|
const RelationRecord *relation_record,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Recherche une relation par son UUID.
|
||||||
|
*
|
||||||
|
* Une relation absente retourne NULL sans produire d'erreur.
|
||||||
|
*
|
||||||
|
* @param relation_dao DAO valide.
|
||||||
|
* @param identifier UUID recherché.
|
||||||
|
* @param error Emplacement facultatif recevant une erreur.
|
||||||
|
*
|
||||||
|
* @return Nouveau modèle possédé par l'appelant, ou NULL.
|
||||||
|
*/
|
||||||
|
RelationRecord *relation_dao_find_by_identifier(
|
||||||
|
RelationDao *relation_dao,
|
||||||
|
const char *identifier,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Charge toutes les relations dans un ordre déterministe.
|
||||||
|
*
|
||||||
|
* L'ordre utilisé est :
|
||||||
|
*
|
||||||
|
* - date de création croissante ;
|
||||||
|
* - UUID croissant en cas d'égalité.
|
||||||
|
*
|
||||||
|
* Le tableau retourné utilise relation_record_free() comme fonction
|
||||||
|
* de destruction.
|
||||||
|
*
|
||||||
|
* @param relation_dao DAO valide.
|
||||||
|
* @param error Emplacement facultatif recevant une erreur.
|
||||||
|
*
|
||||||
|
* @return Nouveau GPtrArray, ou NULL en cas d'échec.
|
||||||
|
*/
|
||||||
|
GPtrArray *relation_dao_list_all(
|
||||||
|
RelationDao *relation_dao,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compte les relations persistées.
|
||||||
|
*
|
||||||
|
* @param relation_dao DAO valide.
|
||||||
|
* @param out_count Destination du nombre de relations.
|
||||||
|
* @param error Emplacement facultatif recevant une erreur.
|
||||||
|
*
|
||||||
|
* @return TRUE si le comptage réussit.
|
||||||
|
*/
|
||||||
|
gboolean relation_dao_count(
|
||||||
|
RelationDao *relation_dao,
|
||||||
|
guint64 *out_count,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
||||||
187
include/models/relation_record.h
Normal file
187
include/models/relation_record.h
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file relation_record.h
|
||||||
|
* @brief Modèle représentant une relation persistée entre deux entités.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_RELATION_RECORD_H
|
||||||
|
#define LABFY_INVESTIGATION_RELATION_RECORD_H
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Modèle opaque représentant une relation entre deux entités.
|
||||||
|
*/
|
||||||
|
typedef struct RelationRecord RelationRecord;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Statut métier d'une relation.
|
||||||
|
*
|
||||||
|
* UNKNOWN sert uniquement de valeur défensive et ne doit jamais être
|
||||||
|
* persisté dans SQLite.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RELATION_STATUS_UNKNOWN,
|
||||||
|
RELATION_STATUS_ACTIVE,
|
||||||
|
RELATION_STATUS_ARCHIVED,
|
||||||
|
RELATION_STATUS_DELETED,
|
||||||
|
RELATION_STATUS_DISPUTED
|
||||||
|
} RelationStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Codes d'erreur produits par RelationRecord.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RELATION_RECORD_ERROR_INVALID_ARGUMENT,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_IDENTIFIER,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_SOURCE_IDENTIFIER,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_TARGET_IDENTIFIER,
|
||||||
|
RELATION_RECORD_ERROR_IDENTICAL_ENTITIES,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_TYPE,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_CONFIDENCE,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_DATE,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_STATUS
|
||||||
|
} RelationRecordError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Domaine d'erreur du modèle RelationRecord.
|
||||||
|
*/
|
||||||
|
#define RELATION_RECORD_ERROR \
|
||||||
|
relation_record_error_quark()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le domaine d'erreur du modèle.
|
||||||
|
*/
|
||||||
|
GQuark relation_record_error_quark(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Crée une relation métier.
|
||||||
|
*
|
||||||
|
* Toutes les chaînes sont copiées.
|
||||||
|
*
|
||||||
|
* identifier, source_entity_identifier, target_entity_identifier,
|
||||||
|
* relation_type, created_at et updated_at sont obligatoires.
|
||||||
|
*
|
||||||
|
* label et justification peuvent être NULL ou vides.
|
||||||
|
*
|
||||||
|
* confidence doit être compris entre 0 et 100.
|
||||||
|
*
|
||||||
|
* La source et la cible doivent être différentes.
|
||||||
|
*
|
||||||
|
* @param identifier UUID de la relation.
|
||||||
|
* @param source_entity_identifier UUID de l'entité source.
|
||||||
|
* @param target_entity_identifier UUID de l'entité cible.
|
||||||
|
* @param relation_type Type métier de la relation.
|
||||||
|
* @param label Libellé facultatif.
|
||||||
|
* @param justification Justification facultative.
|
||||||
|
* @param confidence Niveau de confiance compris entre 0 et 100.
|
||||||
|
* @param created_at Date UTC de création.
|
||||||
|
* @param updated_at Date UTC de dernière modification.
|
||||||
|
* @param status Statut métier.
|
||||||
|
* @param error Emplacement facultatif recevant une erreur.
|
||||||
|
*
|
||||||
|
* @return Nouvelle relation, ou NULL lorsque les données sont invalides.
|
||||||
|
*/
|
||||||
|
RelationRecord *relation_record_new(
|
||||||
|
const char *identifier,
|
||||||
|
const char *source_entity_identifier,
|
||||||
|
const char *target_entity_identifier,
|
||||||
|
const char *relation_type,
|
||||||
|
const char *label,
|
||||||
|
const char *justification,
|
||||||
|
gint confidence,
|
||||||
|
const char *created_at,
|
||||||
|
const char *updated_at,
|
||||||
|
RelationStatus status,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Libère une relation.
|
||||||
|
*
|
||||||
|
* Cette fonction accepte NULL.
|
||||||
|
*/
|
||||||
|
void relation_record_free(
|
||||||
|
RelationRecord *relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne l'UUID de la relation.
|
||||||
|
*/
|
||||||
|
const char *relation_record_get_identifier(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne l'UUID de l'entité source.
|
||||||
|
*/
|
||||||
|
const char *relation_record_get_source_entity_identifier(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne l'UUID de l'entité cible.
|
||||||
|
*/
|
||||||
|
const char *relation_record_get_target_entity_identifier(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le type métier de la relation.
|
||||||
|
*/
|
||||||
|
const char *relation_record_get_relation_type(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le libellé facultatif.
|
||||||
|
*/
|
||||||
|
const char *relation_record_get_label(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne la justification facultative.
|
||||||
|
*/
|
||||||
|
const char *relation_record_get_justification(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le niveau de confiance.
|
||||||
|
*
|
||||||
|
* @return Valeur comprise entre 0 et 100, ou 0 si relation_record est NULL.
|
||||||
|
*/
|
||||||
|
gint relation_record_get_confidence(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne la date UTC de création.
|
||||||
|
*/
|
||||||
|
const char *relation_record_get_created_at(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne la date UTC de dernière modification.
|
||||||
|
*/
|
||||||
|
const char *relation_record_get_updated_at(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le statut métier.
|
||||||
|
*
|
||||||
|
* @return Statut ou RELATION_STATUS_UNKNOWN si relation_record est NULL.
|
||||||
|
*/
|
||||||
|
RelationStatus relation_record_get_status(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
||||||
1745
src/dao/relation_dao.c
Normal file
1745
src/dao/relation_dao.c
Normal file
File diff suppressed because it is too large
Load diff
648
src/models/relation_record.c
Normal file
648
src/models/relation_record.c
Normal file
|
|
@ -0,0 +1,648 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file relation_record.c
|
||||||
|
* @brief Implémentation du modèle représentant une relation persistée.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "models/relation_record.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Représentation privée d'une relation.
|
||||||
|
*/
|
||||||
|
struct RelationRecord
|
||||||
|
{
|
||||||
|
char *identifier;
|
||||||
|
char *source_entity_identifier;
|
||||||
|
char *target_entity_identifier;
|
||||||
|
char *relation_type;
|
||||||
|
char *label;
|
||||||
|
char *justification;
|
||||||
|
|
||||||
|
gint confidence;
|
||||||
|
|
||||||
|
char *created_at;
|
||||||
|
char *updated_at;
|
||||||
|
|
||||||
|
RelationStatus status;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copie et nettoie une chaîne.
|
||||||
|
*/
|
||||||
|
static char *relation_record_duplicate_trimmed(
|
||||||
|
const char *value
|
||||||
|
)
|
||||||
|
{
|
||||||
|
char *copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
if (value == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
copy =
|
||||||
|
g_strdup(
|
||||||
|
value
|
||||||
|
);
|
||||||
|
|
||||||
|
g_strstrip(
|
||||||
|
copy
|
||||||
|
);
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Normalise un champ facultatif.
|
||||||
|
*
|
||||||
|
* Une chaîne vide après nettoyage devient NULL.
|
||||||
|
*/
|
||||||
|
static char *relation_record_duplicate_optional(
|
||||||
|
const char *value
|
||||||
|
)
|
||||||
|
{
|
||||||
|
char *copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
copy =
|
||||||
|
relation_record_duplicate_trimmed(
|
||||||
|
value
|
||||||
|
);
|
||||||
|
|
||||||
|
if (copy != NULL &&
|
||||||
|
copy[0] == '\0')
|
||||||
|
{
|
||||||
|
g_free(
|
||||||
|
copy
|
||||||
|
);
|
||||||
|
|
||||||
|
copy =
|
||||||
|
NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie qu'une chaîne obligatoire est présente.
|
||||||
|
*/
|
||||||
|
static gboolean relation_record_required_value_is_valid(
|
||||||
|
const char *value
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return value != NULL &&
|
||||||
|
value[0] != '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie une date UTC stricte au format YYYY-MM-DDTHH:MM:SSZ.
|
||||||
|
*/
|
||||||
|
static gboolean relation_record_timestamp_is_valid(
|
||||||
|
const char *timestamp
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GDateTime *date_time =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *normalized_timestamp =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
gboolean valid =
|
||||||
|
FALSE;
|
||||||
|
|
||||||
|
gsize character_index =
|
||||||
|
0;
|
||||||
|
|
||||||
|
if (timestamp == NULL ||
|
||||||
|
strlen(timestamp) != 20U)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timestamp[4] != '-' ||
|
||||||
|
timestamp[7] != '-' ||
|
||||||
|
timestamp[10] != 'T' ||
|
||||||
|
timestamp[13] != ':' ||
|
||||||
|
timestamp[16] != ':' ||
|
||||||
|
timestamp[19] != 'Z')
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (character_index = 0;
|
||||||
|
character_index < 20U;
|
||||||
|
character_index++)
|
||||||
|
{
|
||||||
|
if (character_index == 4U ||
|
||||||
|
character_index == 7U ||
|
||||||
|
character_index == 10U ||
|
||||||
|
character_index == 13U ||
|
||||||
|
character_index == 16U ||
|
||||||
|
character_index == 19U)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_ascii_isdigit(
|
||||||
|
timestamp[character_index]
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
date_time =
|
||||||
|
g_date_time_new_from_iso8601(
|
||||||
|
timestamp,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (date_time == NULL)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized_timestamp =
|
||||||
|
g_date_time_format(
|
||||||
|
date_time,
|
||||||
|
"%Y-%m-%dT%H:%M:%SZ"
|
||||||
|
);
|
||||||
|
|
||||||
|
valid =
|
||||||
|
normalized_timestamp != NULL &&
|
||||||
|
g_strcmp0(
|
||||||
|
normalized_timestamp,
|
||||||
|
timestamp
|
||||||
|
) == 0;
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
normalized_timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
g_date_time_unref(
|
||||||
|
date_time
|
||||||
|
);
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie qu'un statut peut être persisté.
|
||||||
|
*/
|
||||||
|
static gboolean relation_record_status_is_valid(
|
||||||
|
RelationStatus status
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return status == RELATION_STATUS_ACTIVE ||
|
||||||
|
status == RELATION_STATUS_ARCHIVED ||
|
||||||
|
status == RELATION_STATUS_DELETED ||
|
||||||
|
status == RELATION_STATUS_DISPUTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
GQuark relation_record_error_quark(void)
|
||||||
|
{
|
||||||
|
return g_quark_from_static_string(
|
||||||
|
"relation-record-error-quark"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RelationRecord *relation_record_new(
|
||||||
|
const char *identifier,
|
||||||
|
const char *source_entity_identifier,
|
||||||
|
const char *target_entity_identifier,
|
||||||
|
const char *relation_type,
|
||||||
|
const char *label,
|
||||||
|
const char *justification,
|
||||||
|
gint confidence,
|
||||||
|
const char *created_at,
|
||||||
|
const char *updated_at,
|
||||||
|
RelationStatus status,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RelationRecord *relation_record =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *identifier_copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *source_entity_identifier_copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *target_entity_identifier_copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *relation_type_copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *label_copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *justification_copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *created_at_copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *updated_at_copy =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
g_return_val_if_fail(
|
||||||
|
error == NULL || *error == NULL,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
identifier_copy =
|
||||||
|
relation_record_duplicate_trimmed(
|
||||||
|
identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!relation_record_required_value_is_valid(
|
||||||
|
identifier_copy
|
||||||
|
) ||
|
||||||
|
!g_uuid_string_is_valid(
|
||||||
|
identifier_copy
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_RECORD_ERROR,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_IDENTIFIER,
|
||||||
|
"L'identifiant de la relation n'est pas un UUID valide."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
source_entity_identifier_copy =
|
||||||
|
relation_record_duplicate_trimmed(
|
||||||
|
source_entity_identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!relation_record_required_value_is_valid(
|
||||||
|
source_entity_identifier_copy
|
||||||
|
) ||
|
||||||
|
!g_uuid_string_is_valid(
|
||||||
|
source_entity_identifier_copy
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_RECORD_ERROR,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_SOURCE_IDENTIFIER,
|
||||||
|
"L'identifiant de l'entité source n'est pas un UUID valide."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
target_entity_identifier_copy =
|
||||||
|
relation_record_duplicate_trimmed(
|
||||||
|
target_entity_identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!relation_record_required_value_is_valid(
|
||||||
|
target_entity_identifier_copy
|
||||||
|
) ||
|
||||||
|
!g_uuid_string_is_valid(
|
||||||
|
target_entity_identifier_copy
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_RECORD_ERROR,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_TARGET_IDENTIFIER,
|
||||||
|
"L'identifiant de l'entité cible n'est pas un UUID valide."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_strcmp0(
|
||||||
|
source_entity_identifier_copy,
|
||||||
|
target_entity_identifier_copy
|
||||||
|
) == 0)
|
||||||
|
{
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_RECORD_ERROR,
|
||||||
|
RELATION_RECORD_ERROR_IDENTICAL_ENTITIES,
|
||||||
|
"Une relation ne peut pas relier une entité à elle-même."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
relation_type_copy =
|
||||||
|
relation_record_duplicate_trimmed(
|
||||||
|
relation_type
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!relation_record_required_value_is_valid(
|
||||||
|
relation_type_copy
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_RECORD_ERROR,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_TYPE,
|
||||||
|
"Le type de relation est obligatoire."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (confidence < 0 ||
|
||||||
|
confidence > 100)
|
||||||
|
{
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_RECORD_ERROR,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_CONFIDENCE,
|
||||||
|
"Le niveau de confiance doit être compris entre 0 et 100."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
created_at_copy =
|
||||||
|
relation_record_duplicate_trimmed(
|
||||||
|
created_at
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!relation_record_timestamp_is_valid(
|
||||||
|
created_at_copy
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_RECORD_ERROR,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_DATE,
|
||||||
|
"La date de création de la relation est invalide."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
updated_at_copy =
|
||||||
|
relation_record_duplicate_trimmed(
|
||||||
|
updated_at
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!relation_record_timestamp_is_valid(
|
||||||
|
updated_at_copy
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_RECORD_ERROR,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_DATE,
|
||||||
|
"La date de modification de la relation est invalide."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!relation_record_status_is_valid(
|
||||||
|
status
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_RECORD_ERROR,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_STATUS,
|
||||||
|
"Le statut de la relation est invalide."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
label_copy =
|
||||||
|
relation_record_duplicate_optional(
|
||||||
|
label
|
||||||
|
);
|
||||||
|
|
||||||
|
justification_copy =
|
||||||
|
relation_record_duplicate_optional(
|
||||||
|
justification
|
||||||
|
);
|
||||||
|
|
||||||
|
relation_record =
|
||||||
|
g_new0(
|
||||||
|
RelationRecord,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
relation_record->identifier =
|
||||||
|
identifier_copy;
|
||||||
|
|
||||||
|
relation_record->source_entity_identifier =
|
||||||
|
source_entity_identifier_copy;
|
||||||
|
|
||||||
|
relation_record->target_entity_identifier =
|
||||||
|
target_entity_identifier_copy;
|
||||||
|
|
||||||
|
relation_record->relation_type =
|
||||||
|
relation_type_copy;
|
||||||
|
|
||||||
|
relation_record->label =
|
||||||
|
label_copy;
|
||||||
|
|
||||||
|
relation_record->justification =
|
||||||
|
justification_copy;
|
||||||
|
|
||||||
|
relation_record->confidence =
|
||||||
|
confidence;
|
||||||
|
|
||||||
|
relation_record->created_at =
|
||||||
|
created_at_copy;
|
||||||
|
|
||||||
|
relation_record->updated_at =
|
||||||
|
updated_at_copy;
|
||||||
|
|
||||||
|
relation_record->status =
|
||||||
|
status;
|
||||||
|
|
||||||
|
identifier_copy = NULL;
|
||||||
|
source_entity_identifier_copy = NULL;
|
||||||
|
target_entity_identifier_copy = NULL;
|
||||||
|
relation_type_copy = NULL;
|
||||||
|
label_copy = NULL;
|
||||||
|
justification_copy = NULL;
|
||||||
|
created_at_copy = NULL;
|
||||||
|
updated_at_copy = NULL;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
updated_at_copy
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
created_at_copy
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
justification_copy
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
label_copy
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
relation_type_copy
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
target_entity_identifier_copy
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
source_entity_identifier_copy
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
identifier_copy
|
||||||
|
);
|
||||||
|
|
||||||
|
return relation_record;
|
||||||
|
}
|
||||||
|
|
||||||
|
void relation_record_free(
|
||||||
|
RelationRecord *relation_record
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (relation_record == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
relation_record->updated_at
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
relation_record->created_at
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
relation_record->justification
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
relation_record->label
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
relation_record->relation_type
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
relation_record->target_entity_identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
relation_record->source_entity_identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
relation_record->identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
relation_record
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *relation_record_get_identifier(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return relation_record != NULL
|
||||||
|
? relation_record->identifier
|
||||||
|
: NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *relation_record_get_source_entity_identifier(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return relation_record != NULL
|
||||||
|
? relation_record->source_entity_identifier
|
||||||
|
: NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *relation_record_get_target_entity_identifier(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return relation_record != NULL
|
||||||
|
? relation_record->target_entity_identifier
|
||||||
|
: NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *relation_record_get_relation_type(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return relation_record != NULL
|
||||||
|
? relation_record->relation_type
|
||||||
|
: NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *relation_record_get_label(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return relation_record != NULL
|
||||||
|
? relation_record->label
|
||||||
|
: NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *relation_record_get_justification(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return relation_record != NULL
|
||||||
|
? relation_record->justification
|
||||||
|
: NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
gint relation_record_get_confidence(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return relation_record != NULL
|
||||||
|
? relation_record->confidence
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *relation_record_get_created_at(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return relation_record != NULL
|
||||||
|
? relation_record->created_at
|
||||||
|
: NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *relation_record_get_updated_at(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return relation_record != NULL
|
||||||
|
? relation_record->updated_at
|
||||||
|
: NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
RelationStatus relation_record_get_status(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return relation_record != NULL
|
||||||
|
? relation_record->status
|
||||||
|
: RELATION_STATUS_UNKNOWN;
|
||||||
|
}
|
||||||
2265
tests/test_relation_dao.c
Normal file
2265
tests/test_relation_dao.c
Normal file
File diff suppressed because it is too large
Load diff
665
tests/test_relation_record.c
Normal file
665
tests/test_relation_record.c
Normal file
|
|
@ -0,0 +1,665 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file test_relation_record.c
|
||||||
|
* @brief Tests unitaires du modèle RelationRecord.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "models/relation_record.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#define TEST_RELATION_IDENTIFIER \
|
||||||
|
"30000000-0000-4000-8000-000000000001"
|
||||||
|
|
||||||
|
#define TEST_SOURCE_IDENTIFIER \
|
||||||
|
"10000000-0000-4000-8000-000000000001"
|
||||||
|
|
||||||
|
#define TEST_TARGET_IDENTIFIER \
|
||||||
|
"20000000-0000-4000-8000-000000000001"
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
const char *identifier;
|
||||||
|
const char *source_entity_identifier;
|
||||||
|
const char *target_entity_identifier;
|
||||||
|
const char *relation_type;
|
||||||
|
const char *label;
|
||||||
|
const char *justification;
|
||||||
|
|
||||||
|
gint confidence;
|
||||||
|
|
||||||
|
const char *created_at;
|
||||||
|
const char *updated_at;
|
||||||
|
|
||||||
|
RelationStatus status;
|
||||||
|
} TestRelationRecordInput;
|
||||||
|
|
||||||
|
static TestRelationRecordInput test_relation_record_valid_input(void)
|
||||||
|
{
|
||||||
|
return (TestRelationRecordInput)
|
||||||
|
{
|
||||||
|
.identifier = TEST_RELATION_IDENTIFIER,
|
||||||
|
.source_entity_identifier = TEST_SOURCE_IDENTIFIER,
|
||||||
|
.target_entity_identifier = TEST_TARGET_IDENTIFIER,
|
||||||
|
.relation_type = "uses",
|
||||||
|
.label = "Utilise cette adresse",
|
||||||
|
.justification = "Association observée dans la conversation.",
|
||||||
|
.confidence = 80,
|
||||||
|
.created_at = "2026-07-20T20:00:00Z",
|
||||||
|
.updated_at = "2026-07-20T20:00:00Z",
|
||||||
|
.status = RELATION_STATUS_ACTIVE
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static RelationRecord *test_relation_record_create(
|
||||||
|
const TestRelationRecordInput *input,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
g_assert_nonnull(
|
||||||
|
input
|
||||||
|
);
|
||||||
|
|
||||||
|
return relation_record_new(
|
||||||
|
input->identifier,
|
||||||
|
input->source_entity_identifier,
|
||||||
|
input->target_entity_identifier,
|
||||||
|
input->relation_type,
|
||||||
|
input->label,
|
||||||
|
input->justification,
|
||||||
|
input->confidence,
|
||||||
|
input->created_at,
|
||||||
|
input->updated_at,
|
||||||
|
input->status,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_assert_invalid(
|
||||||
|
const TestRelationRecordInput *input,
|
||||||
|
RelationRecordError expected_error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RelationRecord *relation_record =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
GError *error =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
relation_record =
|
||||||
|
test_relation_record_create(
|
||||||
|
input,
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_null(
|
||||||
|
relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_error(
|
||||||
|
error,
|
||||||
|
RELATION_RECORD_ERROR,
|
||||||
|
(gint) expected_error
|
||||||
|
);
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_valid_full(void)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
RelationRecord *relation_record =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
GError *error =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
relation_record =
|
||||||
|
test_relation_record_create(
|
||||||
|
&input,
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_no_error(
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_nonnull(
|
||||||
|
relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpstr(
|
||||||
|
relation_record_get_identifier(relation_record),
|
||||||
|
==,
|
||||||
|
TEST_RELATION_IDENTIFIER
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpstr(
|
||||||
|
relation_record_get_source_entity_identifier(relation_record),
|
||||||
|
==,
|
||||||
|
TEST_SOURCE_IDENTIFIER
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpstr(
|
||||||
|
relation_record_get_target_entity_identifier(relation_record),
|
||||||
|
==,
|
||||||
|
TEST_TARGET_IDENTIFIER
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpstr(
|
||||||
|
relation_record_get_relation_type(relation_record),
|
||||||
|
==,
|
||||||
|
"uses"
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpstr(
|
||||||
|
relation_record_get_label(relation_record),
|
||||||
|
==,
|
||||||
|
"Utilise cette adresse"
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpstr(
|
||||||
|
relation_record_get_justification(relation_record),
|
||||||
|
==,
|
||||||
|
"Association observée dans la conversation."
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpint(
|
||||||
|
relation_record_get_confidence(relation_record),
|
||||||
|
==,
|
||||||
|
80
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpstr(
|
||||||
|
relation_record_get_created_at(relation_record),
|
||||||
|
==,
|
||||||
|
"2026-07-20T20:00:00Z"
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpstr(
|
||||||
|
relation_record_get_updated_at(relation_record),
|
||||||
|
==,
|
||||||
|
"2026-07-20T20:00:00Z"
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpint(
|
||||||
|
relation_record_get_status(relation_record),
|
||||||
|
==,
|
||||||
|
RELATION_STATUS_ACTIVE
|
||||||
|
);
|
||||||
|
|
||||||
|
relation_record_free(
|
||||||
|
relation_record
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_optional_fields(void)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
RelationRecord *relation_record =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
GError *error =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
input.label = " ";
|
||||||
|
input.justification = NULL;
|
||||||
|
|
||||||
|
relation_record =
|
||||||
|
test_relation_record_create(
|
||||||
|
&input,
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_no_error(
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_null(
|
||||||
|
relation_record_get_label(
|
||||||
|
relation_record
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_null(
|
||||||
|
relation_record_get_justification(
|
||||||
|
relation_record
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
relation_record_free(
|
||||||
|
relation_record
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_values_are_trimmed(void)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
RelationRecord *relation_record =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
GError *error =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
input.identifier =
|
||||||
|
" 30000000-0000-4000-8000-000000000001 ";
|
||||||
|
|
||||||
|
input.source_entity_identifier =
|
||||||
|
" 10000000-0000-4000-8000-000000000001 ";
|
||||||
|
|
||||||
|
input.target_entity_identifier =
|
||||||
|
"\t20000000-0000-4000-8000-000000000001\t";
|
||||||
|
|
||||||
|
input.relation_type =
|
||||||
|
" controls ";
|
||||||
|
|
||||||
|
input.label =
|
||||||
|
" Contrôle ";
|
||||||
|
|
||||||
|
input.justification =
|
||||||
|
" Source documentaire. ";
|
||||||
|
|
||||||
|
input.created_at =
|
||||||
|
" 2026-07-20T20:00:00Z ";
|
||||||
|
|
||||||
|
input.updated_at =
|
||||||
|
"\t2026-07-20T20:00:00Z\t";
|
||||||
|
|
||||||
|
relation_record =
|
||||||
|
test_relation_record_create(
|
||||||
|
&input,
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_no_error(
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpstr(
|
||||||
|
relation_record_get_relation_type(relation_record),
|
||||||
|
==,
|
||||||
|
"controls"
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpstr(
|
||||||
|
relation_record_get_label(relation_record),
|
||||||
|
==,
|
||||||
|
"Contrôle"
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpstr(
|
||||||
|
relation_record_get_justification(relation_record),
|
||||||
|
==,
|
||||||
|
"Source documentaire."
|
||||||
|
);
|
||||||
|
|
||||||
|
relation_record_free(
|
||||||
|
relation_record
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_invalid_identifier(void)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
input.identifier = "relation-invalide";
|
||||||
|
|
||||||
|
test_relation_record_assert_invalid(
|
||||||
|
&input,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_IDENTIFIER
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_invalid_source_identifier(void)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
input.source_entity_identifier = "source-invalide";
|
||||||
|
|
||||||
|
test_relation_record_assert_invalid(
|
||||||
|
&input,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_SOURCE_IDENTIFIER
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_invalid_target_identifier(void)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
input.target_entity_identifier = "cible-invalide";
|
||||||
|
|
||||||
|
test_relation_record_assert_invalid(
|
||||||
|
&input,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_TARGET_IDENTIFIER
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_identical_entities(void)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
input.target_entity_identifier =
|
||||||
|
TEST_SOURCE_IDENTIFIER;
|
||||||
|
|
||||||
|
test_relation_record_assert_invalid(
|
||||||
|
&input,
|
||||||
|
RELATION_RECORD_ERROR_IDENTICAL_ENTITIES
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_empty_type(void)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
input.relation_type = " \t ";
|
||||||
|
|
||||||
|
test_relation_record_assert_invalid(
|
||||||
|
&input,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_TYPE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_confidence_below_range(void)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
input.confidence = -1;
|
||||||
|
|
||||||
|
test_relation_record_assert_invalid(
|
||||||
|
&input,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_CONFIDENCE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_confidence_above_range(void)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
input.confidence = 101;
|
||||||
|
|
||||||
|
test_relation_record_assert_invalid(
|
||||||
|
&input,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_CONFIDENCE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_invalid_created_at(void)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
input.created_at = "2026-02-30T20:00:00Z";
|
||||||
|
|
||||||
|
test_relation_record_assert_invalid(
|
||||||
|
&input,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_DATE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_invalid_updated_at(void)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
input.updated_at = "2026-07-20 20:00:00Z";
|
||||||
|
|
||||||
|
test_relation_record_assert_invalid(
|
||||||
|
&input,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_DATE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_valid_statuses(void)
|
||||||
|
{
|
||||||
|
RelationStatus statuses[] =
|
||||||
|
{
|
||||||
|
RELATION_STATUS_ACTIVE,
|
||||||
|
RELATION_STATUS_ARCHIVED,
|
||||||
|
RELATION_STATUS_DELETED,
|
||||||
|
RELATION_STATUS_DISPUTED
|
||||||
|
};
|
||||||
|
|
||||||
|
guint status_index =
|
||||||
|
0;
|
||||||
|
|
||||||
|
for (status_index = 0;
|
||||||
|
status_index < G_N_ELEMENTS(statuses);
|
||||||
|
status_index++)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
RelationRecord *relation_record =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
GError *error =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
input.status =
|
||||||
|
statuses[status_index];
|
||||||
|
|
||||||
|
relation_record =
|
||||||
|
test_relation_record_create(
|
||||||
|
&input,
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_no_error(
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_nonnull(
|
||||||
|
relation_record
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpint(
|
||||||
|
relation_record_get_status(relation_record),
|
||||||
|
==,
|
||||||
|
statuses[status_index]
|
||||||
|
);
|
||||||
|
|
||||||
|
relation_record_free(
|
||||||
|
relation_record
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_invalid_status(void)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
input.status = RELATION_STATUS_UNKNOWN;
|
||||||
|
|
||||||
|
test_relation_record_assert_invalid(
|
||||||
|
&input,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_STATUS
|
||||||
|
);
|
||||||
|
|
||||||
|
input.status = (RelationStatus) 999;
|
||||||
|
|
||||||
|
test_relation_record_assert_invalid(
|
||||||
|
&input,
|
||||||
|
RELATION_RECORD_ERROR_INVALID_STATUS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_null_accessors(void)
|
||||||
|
{
|
||||||
|
g_assert_null(
|
||||||
|
relation_record_get_identifier(NULL)
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_null(
|
||||||
|
relation_record_get_source_entity_identifier(NULL)
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_null(
|
||||||
|
relation_record_get_target_entity_identifier(NULL)
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_null(
|
||||||
|
relation_record_get_relation_type(NULL)
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_null(
|
||||||
|
relation_record_get_label(NULL)
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_null(
|
||||||
|
relation_record_get_justification(NULL)
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpint(
|
||||||
|
relation_record_get_confidence(NULL),
|
||||||
|
==,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_null(
|
||||||
|
relation_record_get_created_at(NULL)
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_null(
|
||||||
|
relation_record_get_updated_at(NULL)
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_cmpint(
|
||||||
|
relation_record_get_status(NULL),
|
||||||
|
==,
|
||||||
|
RELATION_STATUS_UNKNOWN
|
||||||
|
);
|
||||||
|
|
||||||
|
relation_record_free(
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_relation_record_optional_error(void)
|
||||||
|
{
|
||||||
|
TestRelationRecordInput input =
|
||||||
|
test_relation_record_valid_input();
|
||||||
|
|
||||||
|
RelationRecord *relation_record =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
input.identifier = "invalide";
|
||||||
|
|
||||||
|
relation_record =
|
||||||
|
test_relation_record_create(
|
||||||
|
&input,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
g_assert_null(
|
||||||
|
relation_record
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(
|
||||||
|
int argc,
|
||||||
|
char **argv
|
||||||
|
)
|
||||||
|
{
|
||||||
|
g_test_init(
|
||||||
|
&argc,
|
||||||
|
&argv,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/valid-full",
|
||||||
|
test_relation_record_valid_full
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/optional-fields",
|
||||||
|
test_relation_record_optional_fields
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/values-are-trimmed",
|
||||||
|
test_relation_record_values_are_trimmed
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/invalid-identifier",
|
||||||
|
test_relation_record_invalid_identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/invalid-source-identifier",
|
||||||
|
test_relation_record_invalid_source_identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/invalid-target-identifier",
|
||||||
|
test_relation_record_invalid_target_identifier
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/identical-entities",
|
||||||
|
test_relation_record_identical_entities
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/empty-type",
|
||||||
|
test_relation_record_empty_type
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/confidence-below-range",
|
||||||
|
test_relation_record_confidence_below_range
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/confidence-above-range",
|
||||||
|
test_relation_record_confidence_above_range
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/invalid-created-at",
|
||||||
|
test_relation_record_invalid_created_at
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/invalid-updated-at",
|
||||||
|
test_relation_record_invalid_updated_at
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/valid-statuses",
|
||||||
|
test_relation_record_valid_statuses
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/invalid-status",
|
||||||
|
test_relation_record_invalid_status
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/null-accessors",
|
||||||
|
test_relation_record_null_accessors
|
||||||
|
);
|
||||||
|
|
||||||
|
g_test_add_func(
|
||||||
|
"/relation-record/optional-error",
|
||||||
|
test_relation_record_optional_error
|
||||||
|
);
|
||||||
|
|
||||||
|
return g_test_run();
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue