diff --git a/Makefile b/Makefile index d24f13e..935df3f 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ EVIDENCE_INTEGRITY_VERIFIER_TEST_CFLAGS := \ ENTITY_RECORD_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic ENTITY_TYPE_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic ENTITY_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic +EVIDENCE_ENTITY_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic SRC := $(shell find src -name "*.c") @@ -88,6 +89,7 @@ TEST_EVIDENCE_INTEGRITY_TASK := tests/test_evidence_integrity_task TEST_ENTITY_RECORD := tests/test_entity_record TEST_ENTITY_TYPE_DAO := tests/test_entity_type_dao TEST_ENTITY_DAO := tests/test_entity_dao +TEST_EVIDENCE_ENTITY_DAO := tests/test_evidence_entity_dao all: $(TARGET) @@ -401,6 +403,19 @@ $(TEST_ENTITY_DAO): \ src/database/error.c $(CC) $(ENTITY_DAO_TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 +$(TEST_EVIDENCE_ENTITY_DAO): \ + tests/test_evidence_entity_dao.c \ + src/dao/evidence_entity_dao.c \ + src/dao/evidence_dao.c \ + src/dao/entity_dao.c \ + src/models/evidence_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) $(EVIDENCE_ENTITY_DAO_TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 test: \ $(TEST_NODE) \ @@ -439,7 +454,8 @@ test: \ $(TEST_EVIDENCE_INTEGRITY_TASK) \ $(TEST_ENTITY_RECORD) \ $(TEST_ENTITY_TYPE_DAO) \ - $(TEST_ENTITY_DAO) + $(TEST_ENTITY_DAO) \ + $(TEST_EVIDENCE_ENTITY_DAO) @echo "Exécution des tests..." @./$(TEST_NODE) @./$(TEST_TREE_MODEL) @@ -478,6 +494,7 @@ test: \ @$(TEST_ENTITY_RECORD) @$(TEST_ENTITY_TYPE_DAO) @$(TEST_ENTITY_DAO) + @$(TEST_EVIDENCE_ENTITY_DAO) @echo "Tous les tests sont valides." %.o: %.c @@ -522,7 +539,7 @@ clean: $(TEST_EVIDENCE_IMPORT_TASK) \ $(TEST_EVIDENCE_IMPORT_DIALOG) \ $(TEST_EVIDENCE_INTEGRITY_TASK) \ - $(TEST_ENTITY_DAO) - + $(TEST_ENTITY_DAO) \ + $(TEST_EVIDENCE_ENTITY_DAO) .PHONY: clean run test diff --git a/include/dao/evidence_entity_dao.h b/include/dao/evidence_entity_dao.h new file mode 100644 index 0000000..40ed05a --- /dev/null +++ b/include/dao/evidence_entity_dao.h @@ -0,0 +1,218 @@ +/****************************************************************************** + * @file evidence_entity_dao.h + * @brief Persistance des associations entre preuves et entités. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_EVIDENCE_ENTITY_DAO_H +#define LABFY_INVESTIGATION_EVIDENCE_ENTITY_DAO_H + +#include "database/database.h" + +#include + +G_BEGIN_DECLS + +/** + * @brief Catégories d'erreurs produites par EvidenceEntityDao. + */ +typedef enum +{ + /** + * Un argument public est invalide. + */ + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT, + + /** + * L'allocation d'une ressource a échoué. + */ + EVIDENCE_ENTITY_DAO_ERROR_MEMORY, + + /** + * La requête SQLite n'a pas pu être préparée. + */ + EVIDENCE_ENTITY_DAO_ERROR_PREPARE, + + /** + * Un paramètre n'a pas pu être lié à la requête. + */ + EVIDENCE_ENTITY_DAO_ERROR_BIND, + + /** + * La requête SQLite n'a pas pu être exécutée. + */ + EVIDENCE_ENTITY_DAO_ERROR_EXECUTE, + + /** + * Une contrainte SQLite a été violée. + */ + EVIDENCE_ENTITY_DAO_ERROR_CONSTRAINT, + + /** + * Un enregistrement ou une association demandée n'existe pas. + */ + EVIDENCE_ENTITY_DAO_ERROR_NOT_FOUND, + + /** + * Une ligne SQLite n'a pas pu être lue. + */ + EVIDENCE_ENTITY_DAO_ERROR_READ, + + /** + * La table ou une colonne attendue est absente. + */ + EVIDENCE_ENTITY_DAO_ERROR_SCHEMA +} EvidenceEntityDaoError; + +/** + * @brief Domaine d'erreur du DAO. + */ +#define EVIDENCE_ENTITY_DAO_ERROR \ + evidence_entity_dao_error_quark() + +/** + * @brief DAO opaque des associations entre preuves et entités. + * + * Le DAO emprunte la connexion Database reçue lors de sa création. + */ +typedef struct EvidenceEntityDao EvidenceEntityDao; + +/** + * @brief Retourne le domaine d'erreur du DAO. + */ +GQuark evidence_entity_dao_error_quark(void); + +/** + * @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. + */ +EvidenceEntityDao *evidence_entity_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 evidence_entity_dao DAO à libérer. + */ +void evidence_entity_dao_free( + EvidenceEntityDao *evidence_entity_dao +); + +/** + * @brief Associe une preuve à une entité. + * + * Les deux UUID doivent correspondre à des enregistrements existants. + * Une association déjà existante produit une erreur de contrainte. + * + * @param evidence_entity_dao DAO valide. + * @param evidence_identifier UUID de la preuve. + * @param entity_identifier UUID de l'entité. + * @param error Emplacement facultatif recevant une erreur. + * + * @return TRUE si l'association est créée. + */ +gboolean evidence_entity_dao_link( + EvidenceEntityDao *evidence_entity_dao, + const char *evidence_identifier, + const char *entity_identifier, + GError **error +); + +/** + * @brief Supprime une association entre une preuve et une entité. + * + * Une association absente produit EVIDENCE_ENTITY_DAO_ERROR_NOT_FOUND. + * + * Les enregistrements preuve et entité ne sont jamais supprimés. + * + * @param evidence_entity_dao DAO valide. + * @param evidence_identifier UUID de la preuve. + * @param entity_identifier UUID de l'entité. + * @param error Emplacement facultatif recevant une erreur. + * + * @return TRUE si l'association est supprimée. + */ +gboolean evidence_entity_dao_unlink( + EvidenceEntityDao *evidence_entity_dao, + const char *evidence_identifier, + const char *entity_identifier, + GError **error +); + +/** + * @brief Vérifie si une association existe. + * + * @param evidence_entity_dao DAO valide. + * @param evidence_identifier UUID de la preuve. + * @param entity_identifier UUID de l'entité. + * @param out_exists Destination du résultat. + * @param error Emplacement facultatif recevant une erreur. + * + * @return TRUE si la recherche a été exécutée correctement. + */ +gboolean evidence_entity_dao_exists( + EvidenceEntityDao *evidence_entity_dao, + const char *evidence_identifier, + const char *entity_identifier, + gboolean *out_exists, + GError **error +); + +/** + * @brief Liste les UUID des entités liées à une preuve. + * + * Les identifiants sont triés par ordre lexicographique croissant. + * + * Le tableau et les chaînes qu'il contient appartiennent à l'appelant. + * Le tableau utilise g_free() comme fonction de destruction. + * + * Une preuve sans association produit un tableau vide. + * + * @param evidence_entity_dao DAO valide. + * @param evidence_identifier UUID de la preuve. + * @param error Emplacement facultatif recevant une erreur. + * + * @return Nouveau GPtrArray de chaînes, ou NULL en cas d'échec. + */ +GPtrArray *evidence_entity_dao_list_entity_identifiers( + EvidenceEntityDao *evidence_entity_dao, + const char *evidence_identifier, + GError **error +); + +/** + * @brief Liste les UUID des preuves liées à une entité. + * + * Les identifiants sont triés par ordre lexicographique croissant. + * + * Le tableau et les chaînes qu'il contient appartiennent à l'appelant. + * Le tableau utilise g_free() comme fonction de destruction. + * + * Une entité sans association produit un tableau vide. + * + * @param evidence_entity_dao DAO valide. + * @param entity_identifier UUID de l'entité. + * @param error Emplacement facultatif recevant une erreur. + * + * @return Nouveau GPtrArray de chaînes, ou NULL en cas d'échec. + */ +GPtrArray *evidence_entity_dao_list_evidence_identifiers( + EvidenceEntityDao *evidence_entity_dao, + const char *entity_identifier, + GError **error +); + +G_END_DECLS + +#endif diff --git a/src/dao/evidence_entity_dao.c b/src/dao/evidence_entity_dao.c new file mode 100644 index 0000000..e2fb09b --- /dev/null +++ b/src/dao/evidence_entity_dao.c @@ -0,0 +1,1069 @@ +/****************************************************************************** + * @file evidence_entity_dao.c + * @brief Persistance des associations entre preuves et entités. + ******************************************************************************/ + +#include "dao/evidence_entity_dao.h" + +#include "database/error.h" +#include "database/statement.h" + +#include + +/** + * @brief Représentation privée du DAO. + * + * La connexion Database est empruntée. + */ +struct EvidenceEntityDao +{ + Database *database; +}; + +/** + * @brief Requête vérifiant l'existence d'une preuve. + */ +static const char *const evidence_entity_dao_evidence_exists_sql = + "SELECT 1 " + "FROM preuves " + "WHERE id = ? " + "LIMIT 1;"; + +/** + * @brief Requête vérifiant l'existence d'une entité. + */ +static const char *const evidence_entity_dao_entity_exists_sql = + "SELECT 1 " + "FROM entites " + "WHERE id = ? " + "LIMIT 1;"; + +/** + * @brief Requête vérifiant l'existence d'une association. + */ +static const char *const evidence_entity_dao_association_exists_sql = + "SELECT 1 " + "FROM preuve_entites " + "WHERE preuve_id = ? " + "AND entite_id = ? " + "LIMIT 1;"; + +/** + * @brief Requête créant une association. + */ +static const char *const evidence_entity_dao_link_sql = + "INSERT INTO preuve_entites" + "(" + " preuve_id," + " entite_id" + ")" + "VALUES" + "(" + " ?," + " ?" + ");"; + +/** + * @brief Requête supprimant une association. + */ +static const char *const evidence_entity_dao_unlink_sql = + "DELETE FROM preuve_entites " + "WHERE preuve_id = ? " + "AND entite_id = ?;"; + +/** + * @brief Requête listant les entités liées à une preuve. + */ +static const char *const +evidence_entity_dao_list_entity_identifiers_sql = + "SELECT entite_id " + "FROM preuve_entites " + "WHERE preuve_id = ? " + "ORDER BY entite_id ASC;"; + +/** + * @brief Requête listant les preuves liées à une entité. + */ +static const char *const +evidence_entity_dao_list_evidence_identifiers_sql = + "SELECT preuve_id " + "FROM preuve_entites " + "WHERE entite_id = ? " + "ORDER BY preuve_id ASC;"; + +/** + * @brief Enregistre une erreur littérale du DAO. + */ +static void evidence_entity_dao_set_error_literal( + GError **error, + EvidenceEntityDaoError error_code, + const char *message +) +{ + if (error == NULL || + *error != NULL) + { + return; + } + + g_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR, + error_code, + message + ); +} + +/** + * @brief Détermine si SQLite signale un schéma incompatible. + */ +static gboolean evidence_entity_dao_is_schema_error( + const char *database_message +) +{ + if (database_message == NULL) + { + return FALSE; + } + + return g_strstr_len( + database_message, + -1, + "no such table" + ) != NULL || + g_strstr_len( + database_message, + -1, + "no such column" + ) != NULL; +} + +/** + * @brief Détermine si SQLite signale une contrainte. + */ +static gboolean evidence_entity_dao_is_constraint_error( + const char *database_message +) +{ + if (database_message == NULL) + { + return FALSE; + } + + return g_strstr_len( + database_message, + -1, + "constraint" + ) != NULL || + g_strstr_len( + database_message, + -1, + "CONSTRAINT" + ) != NULL; +} + +/** + * @brief Transforme la dernière erreur Database en erreur du DAO. + */ +static void evidence_entity_dao_set_database_error( + EvidenceEntityDao *evidence_entity_dao, + GError **error, + EvidenceEntityDaoError error_code, + const char *context +) +{ + const char *database_message = + NULL; + + if (error == NULL || + *error != NULL) + { + return; + } + + if (evidence_entity_dao != NULL && + evidence_entity_dao->database != NULL) + { + database_message = + database_error_get_message( + evidence_entity_dao->database + ); + } + + if (error_code == + EVIDENCE_ENTITY_DAO_ERROR_PREPARE && + evidence_entity_dao_is_schema_error( + database_message + )) + { + error_code = + EVIDENCE_ENTITY_DAO_ERROR_SCHEMA; + } + + if (database_message != NULL && + database_message[0] != '\0') + { + g_set_error( + error, + EVIDENCE_ENTITY_DAO_ERROR, + error_code, + "%s : %s", + context, + database_message + ); + + return; + } + + g_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR, + error_code, + context + ); +} + +/** + * @brief Vérifie le DAO et les deux UUID d'une association. + */ +static gboolean evidence_entity_dao_validate_association( + EvidenceEntityDao *evidence_entity_dao, + const char *evidence_identifier, + const char *entity_identifier, + GError **error +) +{ + if (evidence_entity_dao == NULL || + evidence_entity_dao->database == NULL) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT, + "Le DAO des associations preuve-entité est invalide." + ); + + return FALSE; + } + + if (evidence_identifier == NULL || + !g_uuid_string_is_valid( + evidence_identifier + )) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT, + "L'identifiant de preuve est invalide." + ); + + return FALSE; + } + + if (entity_identifier == NULL || + !g_uuid_string_is_valid( + entity_identifier + )) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT, + "L'identifiant d'entité est invalide." + ); + + return FALSE; + } + + return TRUE; +} + +/** + * @brief Vérifie le DAO et un UUID utilisé pour une liste. + */ +static gboolean evidence_entity_dao_validate_list_request( + EvidenceEntityDao *evidence_entity_dao, + const char *identifier, + const char *invalid_identifier_message, + GError **error +) +{ + if (evidence_entity_dao == NULL || + evidence_entity_dao->database == NULL) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT, + "Le DAO des associations preuve-entité est invalide." + ); + + return FALSE; + } + + if (identifier == NULL || + !g_uuid_string_is_valid( + identifier + )) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT, + invalid_identifier_message + ); + + return FALSE; + } + + return TRUE; +} + +/** + * @brief Exécute une requête SELECT 1 avec un ou deux paramètres. + */ +static gboolean evidence_entity_dao_query_exists( + EvidenceEntityDao *evidence_entity_dao, + const char *sql, + const char *first_value, + const char *second_value, + gboolean *out_exists, + GError **error +) +{ + DatabaseStatement *statement = + NULL; + + DatabaseStatementStepResult step_result = + DATABASE_STATEMENT_STEP_ERROR; + + gboolean success = + FALSE; + + if (evidence_entity_dao == NULL || + evidence_entity_dao->database == NULL || + sql == NULL || + first_value == NULL || + out_exists == NULL) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT, + "Les paramètres de la recherche d'existence sont invalides." + ); + + return FALSE; + } + + *out_exists = + FALSE; + + statement = + database_statement_prepare( + evidence_entity_dao->database, + sql + ); + + if (statement == NULL) + { + evidence_entity_dao_set_database_error( + evidence_entity_dao, + error, + EVIDENCE_ENTITY_DAO_ERROR_PREPARE, + "Impossible de préparer la recherche d'existence" + ); + + goto cleanup; + } + + if (!database_statement_bind_text( + statement, + 1, + first_value + )) + { + evidence_entity_dao_set_database_error( + evidence_entity_dao, + error, + EVIDENCE_ENTITY_DAO_ERROR_BIND, + "Impossible de lier le premier identifiant" + ); + + goto cleanup; + } + + if (second_value != NULL && + !database_statement_bind_text( + statement, + 2, + second_value + )) + { + evidence_entity_dao_set_database_error( + evidence_entity_dao, + error, + EVIDENCE_ENTITY_DAO_ERROR_BIND, + "Impossible de lier le second identifiant" + ); + + goto cleanup; + } + + step_result = + database_statement_step( + statement + ); + + if (step_result == + DATABASE_STATEMENT_STEP_ERROR) + { + evidence_entity_dao_set_database_error( + evidence_entity_dao, + error, + EVIDENCE_ENTITY_DAO_ERROR_EXECUTE, + "Impossible d'exécuter la recherche d'existence" + ); + + goto cleanup; + } + + *out_exists = + step_result == + DATABASE_STATEMENT_STEP_ROW; + + success = + TRUE; + +cleanup: + + database_statement_finalize( + statement + ); + + return success; +} + +/** + * @brief Exécute une modification à deux UUID. + */ +static gboolean evidence_entity_dao_execute_pair_statement( + EvidenceEntityDao *evidence_entity_dao, + const char *sql, + const char *evidence_identifier, + const char *entity_identifier, + const char *prepare_context, + const char *bind_context, + const char *execute_context, + GError **error +) +{ + DatabaseStatement *statement = + NULL; + + DatabaseStatementStepResult step_result = + DATABASE_STATEMENT_STEP_ERROR; + + const char *database_message = + NULL; + + gboolean success = + FALSE; + + statement = + database_statement_prepare( + evidence_entity_dao->database, + sql + ); + + if (statement == NULL) + { + evidence_entity_dao_set_database_error( + evidence_entity_dao, + error, + EVIDENCE_ENTITY_DAO_ERROR_PREPARE, + prepare_context + ); + + goto cleanup; + } + + if (!database_statement_bind_text( + statement, + 1, + evidence_identifier + ) || + !database_statement_bind_text( + statement, + 2, + entity_identifier + )) + { + evidence_entity_dao_set_database_error( + evidence_entity_dao, + error, + EVIDENCE_ENTITY_DAO_ERROR_BIND, + bind_context + ); + + goto cleanup; + } + + step_result = + database_statement_step( + statement + ); + + if (step_result != + DATABASE_STATEMENT_STEP_DONE) + { + database_message = + database_error_get_message( + evidence_entity_dao->database + ); + + if (evidence_entity_dao_is_constraint_error( + database_message + )) + { + evidence_entity_dao_set_database_error( + evidence_entity_dao, + error, + EVIDENCE_ENTITY_DAO_ERROR_CONSTRAINT, + execute_context + ); + } + else + { + evidence_entity_dao_set_database_error( + evidence_entity_dao, + error, + EVIDENCE_ENTITY_DAO_ERROR_EXECUTE, + execute_context + ); + } + + goto cleanup; + } + + success = + TRUE; + +cleanup: + + database_statement_finalize( + statement + ); + + return success; +} + +/** + * @brief Charge une liste d'UUID depuis une requête à un paramètre. + */ +static GPtrArray *evidence_entity_dao_list_identifiers( + EvidenceEntityDao *evidence_entity_dao, + const char *sql, + const char *identifier, + const char *prepare_context, + const char *bind_context, + const char *execute_context, + GError **error +) +{ + DatabaseStatement *statement = + NULL; + + GPtrArray *identifiers = + NULL; + + DatabaseStatementStepResult step_result = + DATABASE_STATEMENT_STEP_ERROR; + + identifiers = + g_ptr_array_new_with_free_func( + g_free + ); + + if (identifiers == NULL) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_MEMORY, + "Impossible d'allouer la liste d'identifiants." + ); + + return NULL; + } + + statement = + database_statement_prepare( + evidence_entity_dao->database, + sql + ); + + if (statement == NULL) + { + evidence_entity_dao_set_database_error( + evidence_entity_dao, + error, + EVIDENCE_ENTITY_DAO_ERROR_PREPARE, + prepare_context + ); + + goto failure; + } + + if (!database_statement_bind_text( + statement, + 1, + identifier + )) + { + evidence_entity_dao_set_database_error( + evidence_entity_dao, + error, + EVIDENCE_ENTITY_DAO_ERROR_BIND, + bind_context + ); + + goto failure; + } + + while (TRUE) + { + char *loaded_identifier = + NULL; + + step_result = + database_statement_step( + statement + ); + + if (step_result == + DATABASE_STATEMENT_STEP_DONE) + { + break; + } + + if (step_result == + DATABASE_STATEMENT_STEP_ERROR) + { + evidence_entity_dao_set_database_error( + evidence_entity_dao, + error, + EVIDENCE_ENTITY_DAO_ERROR_EXECUTE, + execute_context + ); + + goto failure; + } + + if (!database_statement_column_text( + statement, + 0, + &loaded_identifier + )) + { + evidence_entity_dao_set_database_error( + evidence_entity_dao, + error, + EVIDENCE_ENTITY_DAO_ERROR_READ, + "Impossible de lire un identifiant associé" + ); + + g_free( + loaded_identifier + ); + + goto failure; + } + + if (loaded_identifier == NULL || + !g_uuid_string_is_valid( + loaded_identifier + )) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_READ, + "La base contient un identifiant associé invalide." + ); + + g_free( + loaded_identifier + ); + + goto failure; + } + + g_ptr_array_add( + identifiers, + loaded_identifier + ); + } + + database_statement_finalize( + statement + ); + + return identifiers; + +failure: + + database_statement_finalize( + statement + ); + + g_ptr_array_unref( + identifiers + ); + + return NULL; +} + +GQuark evidence_entity_dao_error_quark(void) +{ + return g_quark_from_static_string( + "evidence-entity-dao-error-quark" + ); +} + +EvidenceEntityDao *evidence_entity_dao_new( + Database *database, + GError **error +) +{ + EvidenceEntityDao *evidence_entity_dao = + NULL; + + g_return_val_if_fail( + error == NULL || *error == NULL, + NULL + ); + + if (database == NULL) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT, + "La connexion Database est obligatoire." + ); + + return NULL; + } + + evidence_entity_dao = + g_try_new0( + EvidenceEntityDao, + 1 + ); + + if (evidence_entity_dao == NULL) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_MEMORY, + "Impossible d'allouer le DAO des associations preuve-entité." + ); + + return NULL; + } + + evidence_entity_dao->database = + database; + + return evidence_entity_dao; +} + +void evidence_entity_dao_free( + EvidenceEntityDao *evidence_entity_dao +) +{ + if (evidence_entity_dao == NULL) + { + return; + } + + evidence_entity_dao->database = + NULL; + + g_free( + evidence_entity_dao + ); +} + +gboolean evidence_entity_dao_link( + EvidenceEntityDao *evidence_entity_dao, + const char *evidence_identifier, + const char *entity_identifier, + GError **error +) +{ + gboolean evidence_exists = + FALSE; + + gboolean entity_exists = + FALSE; + + gboolean association_exists = + FALSE; + + g_return_val_if_fail( + error == NULL || *error == NULL, + FALSE + ); + + if (!evidence_entity_dao_validate_association( + evidence_entity_dao, + evidence_identifier, + entity_identifier, + error + )) + { + return FALSE; + } + + if (!evidence_entity_dao_query_exists( + evidence_entity_dao, + evidence_entity_dao_evidence_exists_sql, + evidence_identifier, + NULL, + &evidence_exists, + error + )) + { + return FALSE; + } + + if (!evidence_exists) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_NOT_FOUND, + "La preuve à associer n'existe pas." + ); + + return FALSE; + } + + if (!evidence_entity_dao_query_exists( + evidence_entity_dao, + evidence_entity_dao_entity_exists_sql, + entity_identifier, + NULL, + &entity_exists, + error + )) + { + return FALSE; + } + + if (!entity_exists) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_NOT_FOUND, + "L'entité à associer n'existe pas." + ); + + return FALSE; + } + + if (!evidence_entity_dao_query_exists( + evidence_entity_dao, + evidence_entity_dao_association_exists_sql, + evidence_identifier, + entity_identifier, + &association_exists, + error + )) + { + return FALSE; + } + + if (association_exists) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_CONSTRAINT, + "Cette preuve est déjà associée à cette entité." + ); + + return FALSE; + } + + return evidence_entity_dao_execute_pair_statement( + evidence_entity_dao, + evidence_entity_dao_link_sql, + evidence_identifier, + entity_identifier, + "Impossible de préparer la création de l'association", + "Impossible de lier les identifiants de l'association", + "Impossible de créer l'association", + error + ); +} + +gboolean evidence_entity_dao_unlink( + EvidenceEntityDao *evidence_entity_dao, + const char *evidence_identifier, + const char *entity_identifier, + GError **error +) +{ + gboolean association_exists = + FALSE; + + g_return_val_if_fail( + error == NULL || *error == NULL, + FALSE + ); + + if (!evidence_entity_dao_validate_association( + evidence_entity_dao, + evidence_identifier, + entity_identifier, + error + )) + { + return FALSE; + } + + if (!evidence_entity_dao_query_exists( + evidence_entity_dao, + evidence_entity_dao_association_exists_sql, + evidence_identifier, + entity_identifier, + &association_exists, + error + )) + { + return FALSE; + } + + if (!association_exists) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_NOT_FOUND, + "L'association entre la preuve et l'entité n'existe pas." + ); + + return FALSE; + } + + return evidence_entity_dao_execute_pair_statement( + evidence_entity_dao, + evidence_entity_dao_unlink_sql, + evidence_identifier, + entity_identifier, + "Impossible de préparer la suppression de l'association", + "Impossible de lier les identifiants de l'association", + "Impossible de supprimer l'association", + error + ); +} + +gboolean evidence_entity_dao_exists( + EvidenceEntityDao *evidence_entity_dao, + const char *evidence_identifier, + const char *entity_identifier, + gboolean *out_exists, + GError **error +) +{ + g_return_val_if_fail( + error == NULL || *error == NULL, + FALSE + ); + + if (out_exists == NULL) + { + evidence_entity_dao_set_error_literal( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT, + "La destination du résultat d'existence est obligatoire." + ); + + return FALSE; + } + + if (!evidence_entity_dao_validate_association( + evidence_entity_dao, + evidence_identifier, + entity_identifier, + error + )) + { + return FALSE; + } + + return evidence_entity_dao_query_exists( + evidence_entity_dao, + evidence_entity_dao_association_exists_sql, + evidence_identifier, + entity_identifier, + out_exists, + error + ); +} + +GPtrArray *evidence_entity_dao_list_entity_identifiers( + EvidenceEntityDao *evidence_entity_dao, + const char *evidence_identifier, + GError **error +) +{ + g_return_val_if_fail( + error == NULL || *error == NULL, + NULL + ); + + if (!evidence_entity_dao_validate_list_request( + evidence_entity_dao, + evidence_identifier, + "L'identifiant de preuve est invalide.", + error + )) + { + return NULL; + } + + return evidence_entity_dao_list_identifiers( + evidence_entity_dao, + evidence_entity_dao_list_entity_identifiers_sql, + evidence_identifier, + "Impossible de préparer la liste des entités associées", + "Impossible de lier l'identifiant de preuve", + "Impossible de charger les entités associées", + error + ); +} + +GPtrArray *evidence_entity_dao_list_evidence_identifiers( + EvidenceEntityDao *evidence_entity_dao, + const char *entity_identifier, + GError **error +) +{ + g_return_val_if_fail( + error == NULL || *error == NULL, + NULL + ); + + if (!evidence_entity_dao_validate_list_request( + evidence_entity_dao, + entity_identifier, + "L'identifiant d'entité est invalide.", + error + )) + { + return NULL; + } + + return evidence_entity_dao_list_identifiers( + evidence_entity_dao, + evidence_entity_dao_list_evidence_identifiers_sql, + entity_identifier, + "Impossible de préparer la liste des preuves associées", + "Impossible de lier l'identifiant d'entité", + "Impossible de charger les preuves associées", + error + ); +} diff --git a/tests/test_evidence_entity_dao.c b/tests/test_evidence_entity_dao.c new file mode 100644 index 0000000..88c9b68 --- /dev/null +++ b/tests/test_evidence_entity_dao.c @@ -0,0 +1,1511 @@ +/****************************************************************************** + * @file test_evidence_entity_dao.c + * @brief Tests du DAO des associations entre preuves et entités. + ******************************************************************************/ + +#include "dao/entity_dao.h" +#include "dao/evidence_dao.h" +#include "dao/evidence_entity_dao.h" + +#include "database/database.h" +#include "database/statement.h" +#include "models/entity_record.h" +#include "models/evidence_record.h" + +#include +#include +#include +#include + +#include +#include + +/** + * @brief Environnement temporaire d'un test EvidenceEntityDao. + */ +typedef struct +{ + char *temporary_directory; + char *database_path; + + Database *database; + + EvidenceDao *evidence_dao; + EntityDao *entity_dao; + EvidenceEntityDao *evidence_entity_dao; +} TestEvidenceEntityDaoFixture; + +/** + * @brief Vérifie le domaine et le code d'une erreur. + */ +static void test_evidence_entity_dao_assert_error( + const GError *error, + EvidenceEntityDaoError expected_code +) +{ + assert(error != NULL); + + assert( + error->domain == + EVIDENCE_ENTITY_DAO_ERROR + ); + + assert( + error->code == + (gint) expected_code + ); + + assert(error->message != NULL); + assert(error->message[0] != '\0'); +} + +/** + * @brief Crée une base temporaire et les DAO nécessaires. + */ +static TestEvidenceEntityDaoFixture +test_evidence_entity_dao_fixture_create(void) +{ + TestEvidenceEntityDaoFixture fixture = + {0}; + + GError *error = + NULL; + + fixture.temporary_directory = + g_dir_make_tmp( + "labfy-evidence-entity-dao-test-XXXXXX", + &error + ); + + assert(fixture.temporary_directory != NULL); + assert(error == NULL); + + fixture.database_path = + g_build_filename( + fixture.temporary_directory, + "Enquete.sqlite", + NULL + ); + + assert(fixture.database_path != NULL); + + assert( + database_initialize( + fixture.database_path, + "Enquete_EvidenceEntityDao", + fixture.temporary_directory + ) + ); + + fixture.database = + database_open( + fixture.database_path + ); + + assert(fixture.database != NULL); + + fixture.evidence_dao = + evidence_dao_new( + fixture.database, + &error + ); + + assert(fixture.evidence_dao != NULL); + assert(error == NULL); + + fixture.entity_dao = + entity_dao_new( + fixture.database, + &error + ); + + assert(fixture.entity_dao != NULL); + assert(error == NULL); + + fixture.evidence_entity_dao = + evidence_entity_dao_new( + fixture.database, + &error + ); + + assert(fixture.evidence_entity_dao != NULL); + assert(error == NULL); + + return fixture; +} + +/** + * @brief Libère la fixture et supprime sa base temporaire. + */ +static void test_evidence_entity_dao_fixture_clear( + TestEvidenceEntityDaoFixture *fixture +) +{ + assert(fixture != NULL); + + evidence_entity_dao_free( + fixture->evidence_entity_dao + ); + + entity_dao_free( + fixture->entity_dao + ); + + evidence_dao_free( + fixture->evidence_dao + ); + + database_close( + fixture->database + ); + + assert( + g_remove( + fixture->database_path + ) == 0 + ); + + assert( + g_rmdir( + fixture->temporary_directory + ) == 0 + ); + + g_free( + fixture->database_path + ); + + g_free( + fixture->temporary_directory + ); + + fixture->evidence_entity_dao = + NULL; + + fixture->entity_dao = + NULL; + + fixture->evidence_dao = + NULL; + + fixture->database = + NULL; + + fixture->database_path = + NULL; + + fixture->temporary_directory = + NULL; +} + +/** + * @brief Crée une preuve valide destinée aux tests. + */ +static EvidenceRecord *test_evidence_entity_dao_create_evidence( + const char *identifier +) +{ + EvidenceRecord *evidence_record = + NULL; + + char *internal_name = + NULL; + + char *relative_path = + NULL; + + GError *error = + NULL; + + assert(identifier != NULL); + + internal_name = + g_strdup_printf( + "%s.bin", + identifier + ); + + relative_path = + g_strdup_printf( + "01_Preuves_Originales/Documents/%s.bin", + identifier + ); + + assert(internal_name != NULL); + assert(relative_path != NULL); + + evidence_record = + evidence_record_new( + identifier, + "preuve.bin", + internal_name, + relative_path, + "document", + 128, + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "2026-07-20T20:00:00Z", + NULL, + NULL, + NULL, + EVIDENCE_INTEGRITY_STATUS_UNKNOWN, + &error + ); + + assert(evidence_record != NULL); + assert(error == NULL); + + g_free( + relative_path + ); + + g_free( + internal_name + ); + + return evidence_record; +} + +/** + * @brief Crée une entité valide destinée aux tests. + */ +static EntityRecord *test_evidence_entity_dao_create_entity( + const char *identifier, + const char *type_identifier, + const char *value +) +{ + EntityRecord *entity_record = + NULL; + + GError *error = + NULL; + + assert(identifier != NULL); + assert(type_identifier != NULL); + assert(value != NULL); + + entity_record = + entity_record_new( + identifier, + type_identifier, + value, + NULL, + NULL, + 80, + "2026-07-20T20:00:00Z", + "2026-07-20T20:00:00Z", + ENTITY_STATUS_ACTIVE, + &error + ); + + assert(entity_record != NULL); + assert(error == NULL); + + return entity_record; +} + +/** + * @brief Insère une preuve de test. + */ +static void test_evidence_entity_dao_insert_evidence( + TestEvidenceEntityDaoFixture *fixture, + const char *identifier +) +{ + EvidenceRecord *evidence_record = + NULL; + + GError *error = + NULL; + + assert(fixture != NULL); + + evidence_record = + test_evidence_entity_dao_create_evidence( + identifier + ); + + assert( + evidence_dao_insert( + fixture->evidence_dao, + evidence_record, + &error + ) + ); + + assert(error == NULL); + + evidence_record_free( + evidence_record + ); +} + +/** + * @brief Insère une entité de test. + */ +static void test_evidence_entity_dao_insert_entity( + TestEvidenceEntityDaoFixture *fixture, + const char *identifier, + const char *type_identifier, + const char *value +) +{ + EntityRecord *entity_record = + NULL; + + GError *error = + NULL; + + assert(fixture != NULL); + + entity_record = + test_evidence_entity_dao_create_entity( + identifier, + type_identifier, + value + ); + + assert( + entity_dao_insert( + fixture->entity_dao, + entity_record, + &error + ) + ); + + assert(error == NULL); + + entity_record_free( + entity_record + ); +} + +/** + * @brief Retourne le nombre d'associations persistées. + */ +static int64_t test_evidence_entity_dao_count_rows( + Database *database +) +{ + DatabaseStatement *statement = + NULL; + + int64_t association_count = + -1; + + assert(database != NULL); + + statement = + database_statement_prepare( + database, + "SELECT COUNT(*) " + "FROM preuve_entites;" + ); + + assert(statement != NULL); + + assert( + database_statement_step( + statement + ) == DATABASE_STATEMENT_STEP_ROW + ); + + assert( + database_statement_column_int64( + statement, + 0, + &association_count + ) + ); + + assert( + database_statement_step( + statement + ) == DATABASE_STATEMENT_STEP_DONE + ); + + database_statement_finalize( + statement + ); + + return association_count; +} + +/** + * @brief Vérifie le refus d'une connexion absente. + */ +static void test_evidence_entity_dao_new_null_database(void) +{ + EvidenceEntityDao *evidence_entity_dao = + NULL; + + GError *error = + NULL; + + evidence_entity_dao = + evidence_entity_dao_new( + NULL, + &error + ); + + assert(evidence_entity_dao == NULL); + + test_evidence_entity_dao_assert_error( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); +} + +/** + * @brief Vérifie la création et la destruction du DAO. + */ +static void test_evidence_entity_dao_lifecycle(void) +{ + Database *database = + NULL; + + EvidenceEntityDao *evidence_entity_dao = + NULL; + + GError *error = + NULL; + + database = + database_open( + ":memory:" + ); + + assert(database != NULL); + + evidence_entity_dao = + evidence_entity_dao_new( + database, + &error + ); + + assert(evidence_entity_dao != NULL); + assert(error == NULL); + + evidence_entity_dao_free( + evidence_entity_dao + ); + + evidence_entity_dao_free( + NULL + ); + + database_close( + database + ); +} + +/** + * @brief Vérifie la création d'une association valide. + */ +static void test_evidence_entity_dao_link_valid(void) +{ + static const char *const evidence_identifier = + "10000000-0000-4000-8000-000000000001"; + + static const char *const entity_identifier = + "20000000-0000-4000-8000-000000000001"; + + TestEvidenceEntityDaoFixture fixture = + test_evidence_entity_dao_fixture_create(); + + gboolean association_exists = + FALSE; + + GError *error = + NULL; + + test_evidence_entity_dao_insert_evidence( + &fixture, + evidence_identifier + ); + + test_evidence_entity_dao_insert_entity( + &fixture, + entity_identifier, + "email_address", + "contact@example.org" + ); + + assert( + evidence_entity_dao_link( + fixture.evidence_entity_dao, + evidence_identifier, + entity_identifier, + &error + ) + ); + + assert(error == NULL); + + assert( + test_evidence_entity_dao_count_rows( + fixture.database + ) == 1 + ); + + assert( + evidence_entity_dao_exists( + fixture.evidence_entity_dao, + evidence_identifier, + entity_identifier, + &association_exists, + &error + ) + ); + + assert(error == NULL); + assert(association_exists); + + test_evidence_entity_dao_fixture_clear( + &fixture + ); +} + +/** + * @brief Vérifie le refus d'une association dupliquée. + */ +static void test_evidence_entity_dao_link_duplicate(void) +{ + static const char *const evidence_identifier = + "10000000-0000-4000-8000-000000000002"; + + static const char *const entity_identifier = + "20000000-0000-4000-8000-000000000002"; + + TestEvidenceEntityDaoFixture fixture = + test_evidence_entity_dao_fixture_create(); + + GError *error = + NULL; + + test_evidence_entity_dao_insert_evidence( + &fixture, + evidence_identifier + ); + + test_evidence_entity_dao_insert_entity( + &fixture, + entity_identifier, + "iban", + "FR7612345678901234567890123" + ); + + assert( + evidence_entity_dao_link( + fixture.evidence_entity_dao, + evidence_identifier, + entity_identifier, + &error + ) + ); + + assert(error == NULL); + + assert( + !evidence_entity_dao_link( + fixture.evidence_entity_dao, + evidence_identifier, + entity_identifier, + &error + ) + ); + + test_evidence_entity_dao_assert_error( + error, + EVIDENCE_ENTITY_DAO_ERROR_CONSTRAINT + ); + + assert( + test_evidence_entity_dao_count_rows( + fixture.database + ) == 1 + ); + + g_clear_error( + &error + ); + + test_evidence_entity_dao_fixture_clear( + &fixture + ); +} + +/** + * @brief Vérifie le refus d'une preuve inexistante. + */ +static void test_evidence_entity_dao_link_missing_evidence(void) +{ + static const char *const evidence_identifier = + "10000000-0000-4000-8000-000000000003"; + + static const char *const entity_identifier = + "20000000-0000-4000-8000-000000000003"; + + TestEvidenceEntityDaoFixture fixture = + test_evidence_entity_dao_fixture_create(); + + GError *error = + NULL; + + test_evidence_entity_dao_insert_entity( + &fixture, + entity_identifier, + "person", + "Personne inconnue" + ); + + assert( + !evidence_entity_dao_link( + fixture.evidence_entity_dao, + evidence_identifier, + entity_identifier, + &error + ) + ); + + test_evidence_entity_dao_assert_error( + error, + EVIDENCE_ENTITY_DAO_ERROR_NOT_FOUND + ); + + assert( + test_evidence_entity_dao_count_rows( + fixture.database + ) == 0 + ); + + g_clear_error( + &error + ); + + test_evidence_entity_dao_fixture_clear( + &fixture + ); +} + +/** + * @brief Vérifie le refus d'une entité inexistante. + */ +static void test_evidence_entity_dao_link_missing_entity(void) +{ + static const char *const evidence_identifier = + "10000000-0000-4000-8000-000000000004"; + + static const char *const entity_identifier = + "20000000-0000-4000-8000-000000000004"; + + TestEvidenceEntityDaoFixture fixture = + test_evidence_entity_dao_fixture_create(); + + GError *error = + NULL; + + test_evidence_entity_dao_insert_evidence( + &fixture, + evidence_identifier + ); + + assert( + !evidence_entity_dao_link( + fixture.evidence_entity_dao, + evidence_identifier, + entity_identifier, + &error + ) + ); + + test_evidence_entity_dao_assert_error( + error, + EVIDENCE_ENTITY_DAO_ERROR_NOT_FOUND + ); + + assert( + test_evidence_entity_dao_count_rows( + fixture.database + ) == 0 + ); + + g_clear_error( + &error + ); + + test_evidence_entity_dao_fixture_clear( + &fixture + ); +} + +/** + * @brief Vérifie les résultats vrai et faux de exists(). + */ +static void test_evidence_entity_dao_exists(void) +{ + static const char *const evidence_identifier = + "10000000-0000-4000-8000-000000000005"; + + static const char *const entity_identifier = + "20000000-0000-4000-8000-000000000005"; + + TestEvidenceEntityDaoFixture fixture = + test_evidence_entity_dao_fixture_create(); + + gboolean association_exists = + TRUE; + + GError *error = + NULL; + + test_evidence_entity_dao_insert_evidence( + &fixture, + evidence_identifier + ); + + test_evidence_entity_dao_insert_entity( + &fixture, + entity_identifier, + "domain_name", + "example.org" + ); + + assert( + evidence_entity_dao_exists( + fixture.evidence_entity_dao, + evidence_identifier, + entity_identifier, + &association_exists, + &error + ) + ); + + assert(error == NULL); + assert(!association_exists); + + assert( + evidence_entity_dao_link( + fixture.evidence_entity_dao, + evidence_identifier, + entity_identifier, + &error + ) + ); + + assert(error == NULL); + + assert( + evidence_entity_dao_exists( + fixture.evidence_entity_dao, + evidence_identifier, + entity_identifier, + &association_exists, + &error + ) + ); + + assert(error == NULL); + assert(association_exists); + + test_evidence_entity_dao_fixture_clear( + &fixture + ); +} + +/** + * @brief Vérifie la suppression d'une association. + */ +static void test_evidence_entity_dao_unlink_valid(void) +{ + static const char *const evidence_identifier = + "10000000-0000-4000-8000-000000000006"; + + static const char *const entity_identifier = + "20000000-0000-4000-8000-000000000006"; + + TestEvidenceEntityDaoFixture fixture = + test_evidence_entity_dao_fixture_create(); + + gboolean association_exists = + TRUE; + + GError *error = + NULL; + + test_evidence_entity_dao_insert_evidence( + &fixture, + evidence_identifier + ); + + test_evidence_entity_dao_insert_entity( + &fixture, + entity_identifier, + "phone_number", + "+33601020304" + ); + + assert( + evidence_entity_dao_link( + fixture.evidence_entity_dao, + evidence_identifier, + entity_identifier, + &error + ) + ); + + assert(error == NULL); + + assert( + evidence_entity_dao_unlink( + fixture.evidence_entity_dao, + evidence_identifier, + entity_identifier, + &error + ) + ); + + assert(error == NULL); + + assert( + evidence_entity_dao_exists( + fixture.evidence_entity_dao, + evidence_identifier, + entity_identifier, + &association_exists, + &error + ) + ); + + assert(error == NULL); + assert(!association_exists); + + assert( + test_evidence_entity_dao_count_rows( + fixture.database + ) == 0 + ); + + test_evidence_entity_dao_fixture_clear( + &fixture + ); +} + +/** + * @brief Vérifie le signalement d'une association absente. + */ +static void test_evidence_entity_dao_unlink_missing(void) +{ + TestEvidenceEntityDaoFixture fixture = + test_evidence_entity_dao_fixture_create(); + + GError *error = + NULL; + + assert( + !evidence_entity_dao_unlink( + fixture.evidence_entity_dao, + "10000000-0000-4000-8000-000000000007", + "20000000-0000-4000-8000-000000000007", + &error + ) + ); + + test_evidence_entity_dao_assert_error( + error, + EVIDENCE_ENTITY_DAO_ERROR_NOT_FOUND + ); + + g_clear_error( + &error + ); + + test_evidence_entity_dao_fixture_clear( + &fixture + ); +} + +/** + * @brief Vérifie la liste vide et l'ordre des entités d'une preuve. + */ +static void test_evidence_entity_dao_list_entities(void) +{ + static const char *const evidence_identifier = + "10000000-0000-4000-8000-000000000008"; + + static const char *const first_entity_identifier = + "20000000-0000-4000-8000-000000000001"; + + static const char *const second_entity_identifier = + "20000000-0000-4000-8000-000000000002"; + + static const char *const third_entity_identifier = + "20000000-0000-4000-8000-000000000003"; + + TestEvidenceEntityDaoFixture fixture = + test_evidence_entity_dao_fixture_create(); + + GPtrArray *entity_identifiers = + NULL; + + GError *error = + NULL; + + test_evidence_entity_dao_insert_evidence( + &fixture, + evidence_identifier + ); + + entity_identifiers = + evidence_entity_dao_list_entity_identifiers( + fixture.evidence_entity_dao, + evidence_identifier, + &error + ); + + assert(entity_identifiers != NULL); + assert(error == NULL); + assert(entity_identifiers->len == 0); + + g_ptr_array_unref( + entity_identifiers + ); + + test_evidence_entity_dao_insert_entity( + &fixture, + third_entity_identifier, + "pseudonym", + "pseudo-trois" + ); + + test_evidence_entity_dao_insert_entity( + &fixture, + first_entity_identifier, + "pseudonym", + "pseudo-un" + ); + + test_evidence_entity_dao_insert_entity( + &fixture, + second_entity_identifier, + "pseudonym", + "pseudo-deux" + ); + + assert( + evidence_entity_dao_link( + fixture.evidence_entity_dao, + evidence_identifier, + third_entity_identifier, + &error + ) + ); + + assert(error == NULL); + + assert( + evidence_entity_dao_link( + fixture.evidence_entity_dao, + evidence_identifier, + first_entity_identifier, + &error + ) + ); + + assert(error == NULL); + + assert( + evidence_entity_dao_link( + fixture.evidence_entity_dao, + evidence_identifier, + second_entity_identifier, + &error + ) + ); + + assert(error == NULL); + + entity_identifiers = + evidence_entity_dao_list_entity_identifiers( + fixture.evidence_entity_dao, + evidence_identifier, + &error + ); + + assert(entity_identifiers != NULL); + assert(error == NULL); + assert(entity_identifiers->len == 3); + + assert( + strcmp( + g_ptr_array_index( + entity_identifiers, + 0 + ), + first_entity_identifier + ) == 0 + ); + + assert( + strcmp( + g_ptr_array_index( + entity_identifiers, + 1 + ), + second_entity_identifier + ) == 0 + ); + + assert( + strcmp( + g_ptr_array_index( + entity_identifiers, + 2 + ), + third_entity_identifier + ) == 0 + ); + + g_ptr_array_unref( + entity_identifiers + ); + + test_evidence_entity_dao_fixture_clear( + &fixture + ); +} + +/** + * @brief Vérifie l'ordre des preuves liées à une entité. + */ +static void test_evidence_entity_dao_list_evidence(void) +{ + static const char *const entity_identifier = + "20000000-0000-4000-8000-000000000009"; + + static const char *const first_evidence_identifier = + "10000000-0000-4000-8000-000000000001"; + + static const char *const second_evidence_identifier = + "10000000-0000-4000-8000-000000000002"; + + static const char *const third_evidence_identifier = + "10000000-0000-4000-8000-000000000003"; + + TestEvidenceEntityDaoFixture fixture = + test_evidence_entity_dao_fixture_create(); + + GPtrArray *evidence_identifiers = + NULL; + + GError *error = + NULL; + + test_evidence_entity_dao_insert_entity( + &fixture, + entity_identifier, + "organization", + "Organisation de test" + ); + + test_evidence_entity_dao_insert_evidence( + &fixture, + third_evidence_identifier + ); + + test_evidence_entity_dao_insert_evidence( + &fixture, + first_evidence_identifier + ); + + test_evidence_entity_dao_insert_evidence( + &fixture, + second_evidence_identifier + ); + + assert( + evidence_entity_dao_link( + fixture.evidence_entity_dao, + third_evidence_identifier, + entity_identifier, + &error + ) + ); + + assert(error == NULL); + + assert( + evidence_entity_dao_link( + fixture.evidence_entity_dao, + first_evidence_identifier, + entity_identifier, + &error + ) + ); + + assert(error == NULL); + + assert( + evidence_entity_dao_link( + fixture.evidence_entity_dao, + second_evidence_identifier, + entity_identifier, + &error + ) + ); + + assert(error == NULL); + + evidence_identifiers = + evidence_entity_dao_list_evidence_identifiers( + fixture.evidence_entity_dao, + entity_identifier, + &error + ); + + assert(evidence_identifiers != NULL); + assert(error == NULL); + assert(evidence_identifiers->len == 3); + + assert( + strcmp( + g_ptr_array_index( + evidence_identifiers, + 0 + ), + first_evidence_identifier + ) == 0 + ); + + assert( + strcmp( + g_ptr_array_index( + evidence_identifiers, + 1 + ), + second_evidence_identifier + ) == 0 + ); + + assert( + strcmp( + g_ptr_array_index( + evidence_identifiers, + 2 + ), + third_evidence_identifier + ) == 0 + ); + + g_ptr_array_unref( + evidence_identifiers + ); + + test_evidence_entity_dao_fixture_clear( + &fixture + ); +} + +/** + * @brief Vérifie les arguments invalides des opérations publiques. + */ +static void test_evidence_entity_dao_invalid_arguments(void) +{ + TestEvidenceEntityDaoFixture fixture = + test_evidence_entity_dao_fixture_create(); + + GPtrArray *identifiers = + NULL; + + gboolean association_exists = + FALSE; + + GError *error = + NULL; + + assert( + !evidence_entity_dao_link( + NULL, + "10000000-0000-4000-8000-000000000010", + "20000000-0000-4000-8000-000000000010", + &error + ) + ); + + test_evidence_entity_dao_assert_error( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + assert( + !evidence_entity_dao_link( + fixture.evidence_entity_dao, + "preuve-invalide", + "20000000-0000-4000-8000-000000000010", + &error + ) + ); + + test_evidence_entity_dao_assert_error( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + assert( + !evidence_entity_dao_unlink( + fixture.evidence_entity_dao, + "10000000-0000-4000-8000-000000000010", + "entite-invalide", + &error + ) + ); + + test_evidence_entity_dao_assert_error( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + assert( + !evidence_entity_dao_exists( + fixture.evidence_entity_dao, + "10000000-0000-4000-8000-000000000010", + "20000000-0000-4000-8000-000000000010", + NULL, + &error + ) + ); + + test_evidence_entity_dao_assert_error( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + identifiers = + evidence_entity_dao_list_entity_identifiers( + fixture.evidence_entity_dao, + "preuve-invalide", + &error + ); + + assert(identifiers == NULL); + + test_evidence_entity_dao_assert_error( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + identifiers = + evidence_entity_dao_list_evidence_identifiers( + NULL, + "20000000-0000-4000-8000-000000000010", + &error + ); + + assert(identifiers == NULL); + + test_evidence_entity_dao_assert_error( + error, + EVIDENCE_ENTITY_DAO_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + /* + * Un résultat faux est parfaitement valide lorsque l'association + * n'existe pas. + */ + assert( + evidence_entity_dao_exists( + fixture.evidence_entity_dao, + "10000000-0000-4000-8000-000000000010", + "20000000-0000-4000-8000-000000000010", + &association_exists, + &error + ) + ); + + assert(error == NULL); + assert(!association_exists); + + test_evidence_entity_dao_fixture_clear( + &fixture + ); +} + +/** + * @brief Vérifie les contraintes ON DELETE RESTRICT. + */ +static void test_evidence_entity_dao_foreign_key_restrict(void) +{ + static const char *const evidence_identifier = + "10000000-0000-4000-8000-000000000011"; + + static const char *const entity_identifier = + "20000000-0000-4000-8000-000000000011"; + + TestEvidenceEntityDaoFixture fixture = + test_evidence_entity_dao_fixture_create(); + + DatabaseStatement *statement = + NULL; + + GError *error = + NULL; + + test_evidence_entity_dao_insert_evidence( + &fixture, + evidence_identifier + ); + + test_evidence_entity_dao_insert_entity( + &fixture, + entity_identifier, + "website", + "https://example.org/" + ); + + assert( + evidence_entity_dao_link( + fixture.evidence_entity_dao, + evidence_identifier, + entity_identifier, + &error + ) + ); + + assert(error == NULL); + + statement = + database_statement_prepare( + fixture.database, + "DELETE FROM preuves " + "WHERE id = ?;" + ); + + assert(statement != NULL); + + assert( + database_statement_bind_text( + statement, + 1, + evidence_identifier + ) + ); + + g_test_expect_message( + NULL, + G_LOG_LEVEL_WARNING, + "*FOREIGN KEY constraint failed*" + ); + + assert( + database_statement_step( + statement + ) == DATABASE_STATEMENT_STEP_ERROR + ); + + database_statement_finalize( + statement + ); + + statement = + database_statement_prepare( + fixture.database, + "DELETE FROM entites " + "WHERE id = ?;" + ); + + assert(statement != NULL); + + assert( + database_statement_bind_text( + statement, + 1, + entity_identifier + ) + ); + + g_test_expect_message( + NULL, + G_LOG_LEVEL_WARNING, + "*FOREIGN KEY constraint failed*" + ); + + assert( + database_statement_step( + statement + ) == DATABASE_STATEMENT_STEP_ERROR + ); + + database_statement_finalize( + statement + ); + + assert( + test_evidence_entity_dao_count_rows( + fixture.database + ) == 1 + ); + + test_evidence_entity_dao_fixture_clear( + &fixture + ); +} + +int main( + int argc, + char **argv +) +{ + g_test_init( + &argc, + &argv, + NULL + ); + test_evidence_entity_dao_new_null_database(); + test_evidence_entity_dao_lifecycle(); + test_evidence_entity_dao_link_valid(); + test_evidence_entity_dao_link_duplicate(); + test_evidence_entity_dao_link_missing_evidence(); + test_evidence_entity_dao_link_missing_entity(); + test_evidence_entity_dao_exists(); + test_evidence_entity_dao_unlink_valid(); + test_evidence_entity_dao_unlink_missing(); + test_evidence_entity_dao_list_entities(); + test_evidence_entity_dao_list_evidence(); + test_evidence_entity_dao_invalid_arguments(); + test_evidence_entity_dao_foreign_key_restrict(); + + printf( + "EvidenceEntityDao : tous les tests sont valides.\n" + ); + + return 0; +}