/****************************************************************************** * @file test_investigation_graph_model.c * @brief Tests unitaires du graphe métier d'une enquête. ******************************************************************************/ #include "models/investigation_graph_model.h" #include "models/entity_record.h" #include "models/relation_record.h" #include #include #include #include #define TEST_ENTITY_A_IDENTIFIER \ "10000000-0000-4000-8000-000000000001" #define TEST_ENTITY_B_IDENTIFIER \ "20000000-0000-4000-8000-000000000001" #define TEST_ENTITY_C_IDENTIFIER \ "30000000-0000-4000-8000-000000000001" #define TEST_RELATION_AB_IDENTIFIER \ "40000000-0000-4000-8000-000000000001" #define TEST_RELATION_AC_IDENTIFIER \ "40000000-0000-4000-8000-000000000002" #define TEST_RELATION_CA_IDENTIFIER \ "40000000-0000-4000-8000-000000000003" /** * @brief Vérifie le domaine et le code d'une erreur du graphe. */ static void test_graph_assert_error( const GError *error, InvestigationGraphModelError expected_code ) { assert(error != NULL); assert( error->domain == INVESTIGATION_GRAPH_MODEL_ERROR ); assert( error->code == (gint) expected_code ); assert(error->message != NULL); assert(error->message[0] != '\0'); } /** * @brief Crée une entité valide. */ static EntityRecord *test_graph_create_entity( const char *identifier, const char *value ) { EntityRecord *entity_record = NULL; GError *error = NULL; entity_record = entity_record_new( identifier, "person", value, NULL, NULL, 80, "2026-07-21T20:00:00Z", "2026-07-21T20:00:00Z", ENTITY_STATUS_ACTIVE, &error ); assert(entity_record != NULL); assert(error == NULL); return entity_record; } /** * @brief Crée une relation valide. */ static RelationRecord *test_graph_create_relation( const char *identifier, const char *source_identifier, const char *target_identifier, const char *relation_type ) { RelationRecord *relation_record = NULL; GError *error = NULL; relation_record = relation_record_new( identifier, source_identifier, target_identifier, relation_type, NULL, NULL, 75, "2026-07-21T20:10:00Z", "2026-07-21T20:10:00Z", RELATION_STATUS_ACTIVE, &error ); assert(relation_record != NULL); assert(error == NULL); return relation_record; } /** * @brief Ajoute une entité et vérifie le succès. */ static void test_graph_add_entity_or_abort( InvestigationGraphModel *graph_model, EntityRecord *entity_record ) { GError *error = NULL; assert( investigation_graph_model_add_entity( graph_model, entity_record, &error ) ); assert(error == NULL); } /** * @brief Ajoute une relation et vérifie le succès. */ static void test_graph_add_relation_or_abort( InvestigationGraphModel *graph_model, RelationRecord *relation_record ) { GError *error = NULL; assert( investigation_graph_model_add_relation( graph_model, relation_record, &error ) ); assert(error == NULL); } /** * @brief Ajoute les trois entités courantes. */ static void test_graph_add_standard_entities( InvestigationGraphModel *graph_model ) { test_graph_add_entity_or_abort( graph_model, test_graph_create_entity( TEST_ENTITY_A_IDENTIFIER, "Personne A" ) ); test_graph_add_entity_or_abort( graph_model, test_graph_create_entity( TEST_ENTITY_B_IDENTIFIER, "Personne B" ) ); test_graph_add_entity_or_abort( graph_model, test_graph_create_entity( TEST_ENTITY_C_IDENTIFIER, "Personne C" ) ); } /** * @brief Vérifie la création, le comptage et la destruction d'un graphe vide. */ static void test_graph_empty_lifecycle(void) { InvestigationGraphModel *graph_model = NULL; GPtrArray *entities = NULL; GPtrArray *relations = NULL; GError *error = NULL; graph_model = investigation_graph_model_new( &error ); assert(graph_model != NULL); assert(error == NULL); assert( investigation_graph_model_get_entity_count( graph_model ) == 0 ); assert( investigation_graph_model_get_relation_count( graph_model ) == 0 ); entities = investigation_graph_model_list_entities( graph_model, &error ); assert(entities != NULL); assert(error == NULL); assert(entities->len == 0); relations = investigation_graph_model_list_relations( graph_model, &error ); assert(relations != NULL); assert(error == NULL); assert(relations->len == 0); g_ptr_array_unref( relations ); g_ptr_array_unref( entities ); investigation_graph_model_free( graph_model ); investigation_graph_model_free( NULL ); } /** * @brief Vérifie l'ajout et la recherche d'une entité. */ static void test_graph_add_and_find_entity(void) { InvestigationGraphModel *graph_model = NULL; EntityRecord *entity_record = NULL; const EntityRecord *loaded_entity = NULL; GError *error = NULL; graph_model = investigation_graph_model_new( &error ); assert(graph_model != NULL); assert(error == NULL); entity_record = test_graph_create_entity( TEST_ENTITY_A_IDENTIFIER, "Personne A" ); test_graph_add_entity_or_abort( graph_model, entity_record ); /* * Le pointeur retourné doit être le même objet possédé par le graphe. */ loaded_entity = investigation_graph_model_find_entity( graph_model, TEST_ENTITY_A_IDENTIFIER ); assert(loaded_entity == entity_record); assert( strcmp( entity_record_get_identifier( loaded_entity ), TEST_ENTITY_A_IDENTIFIER ) == 0 ); assert( investigation_graph_model_get_entity_count( graph_model ) == 1 ); assert( investigation_graph_model_find_entity( graph_model, TEST_ENTITY_B_IDENTIFIER ) == NULL ); assert( investigation_graph_model_find_entity( graph_model, "identifiant-invalide" ) == NULL ); investigation_graph_model_free( graph_model ); } /** * @brief Vérifie le refus d'une entité NULL. */ static void test_graph_reject_null_entity(void) { InvestigationGraphModel *graph_model = NULL; GError *error = NULL; graph_model = investigation_graph_model_new( &error ); assert(graph_model != NULL); assert(error == NULL); assert( !investigation_graph_model_add_entity( graph_model, NULL, &error ) ); test_graph_assert_error( error, INVESTIGATION_GRAPH_MODEL_ERROR_INVALID_ARGUMENT ); assert( investigation_graph_model_get_entity_count( graph_model ) == 0 ); g_clear_error( &error ); investigation_graph_model_free( graph_model ); } /** * @brief Vérifie le refus d'un UUID d'entité dupliqué. */ static void test_graph_reject_duplicate_entity(void) { InvestigationGraphModel *graph_model = NULL; EntityRecord *first_entity = NULL; EntityRecord *duplicate_entity = NULL; GError *error = NULL; graph_model = investigation_graph_model_new( &error ); assert(graph_model != NULL); assert(error == NULL); first_entity = test_graph_create_entity( TEST_ENTITY_A_IDENTIFIER, "Personne A" ); duplicate_entity = test_graph_create_entity( TEST_ENTITY_A_IDENTIFIER, "Autre valeur" ); test_graph_add_entity_or_abort( graph_model, first_entity ); assert( !investigation_graph_model_add_entity( graph_model, duplicate_entity, &error ) ); test_graph_assert_error( error, INVESTIGATION_GRAPH_MODEL_ERROR_DUPLICATE_ENTITY ); assert( investigation_graph_model_get_entity_count( graph_model ) == 1 ); /* * Après l'échec, le modèle appartient encore à l'appelant. */ entity_record_free( duplicate_entity ); g_clear_error( &error ); investigation_graph_model_free( graph_model ); } /** * @brief Vérifie l'ordre stable de la liste globale des entités. */ static void test_graph_list_entities_sorted(void) { InvestigationGraphModel *graph_model = NULL; GPtrArray *entities = NULL; const EntityRecord *first_entity = NULL; const EntityRecord *second_entity = NULL; const EntityRecord *third_entity = NULL; GError *error = NULL; graph_model = investigation_graph_model_new( &error ); assert(graph_model != NULL); assert(error == NULL); test_graph_add_entity_or_abort( graph_model, test_graph_create_entity( TEST_ENTITY_C_IDENTIFIER, "Personne C" ) ); test_graph_add_entity_or_abort( graph_model, test_graph_create_entity( TEST_ENTITY_A_IDENTIFIER, "Personne A" ) ); test_graph_add_entity_or_abort( graph_model, test_graph_create_entity( TEST_ENTITY_B_IDENTIFIER, "Personne B" ) ); entities = investigation_graph_model_list_entities( graph_model, &error ); assert(entities != NULL); assert(error == NULL); assert(entities->len == 3); first_entity = g_ptr_array_index( entities, 0 ); second_entity = g_ptr_array_index( entities, 1 ); third_entity = g_ptr_array_index( entities, 2 ); assert( strcmp( entity_record_get_identifier( first_entity ), TEST_ENTITY_A_IDENTIFIER ) == 0 ); assert( strcmp( entity_record_get_identifier( second_entity ), TEST_ENTITY_B_IDENTIFIER ) == 0 ); assert( strcmp( entity_record_get_identifier( third_entity ), TEST_ENTITY_C_IDENTIFIER ) == 0 ); /* * Le conteneur ne doit pas posséder les modèles. */ g_ptr_array_unref( entities ); assert( investigation_graph_model_find_entity( graph_model, TEST_ENTITY_A_IDENTIFIER ) != NULL ); investigation_graph_model_free( graph_model ); } /** * @brief Vérifie l'ajout, la recherche et l'orientation d'une relation. */ static void test_graph_add_and_find_relation(void) { InvestigationGraphModel *graph_model = NULL; RelationRecord *relation_record = NULL; const RelationRecord *loaded_relation = NULL; GPtrArray *outgoing_relations = NULL; GPtrArray *incoming_relations = NULL; GError *error = NULL; graph_model = investigation_graph_model_new( &error ); assert(graph_model != NULL); assert(error == NULL); test_graph_add_standard_entities( graph_model ); relation_record = test_graph_create_relation( TEST_RELATION_AB_IDENTIFIER, TEST_ENTITY_A_IDENTIFIER, TEST_ENTITY_B_IDENTIFIER, "uses" ); test_graph_add_relation_or_abort( graph_model, relation_record ); loaded_relation = investigation_graph_model_find_relation( graph_model, TEST_RELATION_AB_IDENTIFIER ); assert(loaded_relation == relation_record); assert( investigation_graph_model_get_relation_count( graph_model ) == 1 ); outgoing_relations = investigation_graph_model_list_outgoing_relations( graph_model, TEST_ENTITY_A_IDENTIFIER, &error ); assert(outgoing_relations != NULL); assert(error == NULL); assert(outgoing_relations->len == 1); assert( g_ptr_array_index( outgoing_relations, 0 ) == relation_record ); incoming_relations = investigation_graph_model_list_incoming_relations( graph_model, TEST_ENTITY_B_IDENTIFIER, &error ); assert(incoming_relations != NULL); assert(error == NULL); assert(incoming_relations->len == 1); assert( g_ptr_array_index( incoming_relations, 0 ) == relation_record ); g_ptr_array_unref( incoming_relations ); g_ptr_array_unref( outgoing_relations ); investigation_graph_model_free( graph_model ); } /** * @brief Vérifie le refus d'une relation NULL. */ static void test_graph_reject_null_relation(void) { InvestigationGraphModel *graph_model = NULL; GError *error = NULL; graph_model = investigation_graph_model_new( &error ); assert(graph_model != NULL); assert(error == NULL); assert( !investigation_graph_model_add_relation( graph_model, NULL, &error ) ); test_graph_assert_error( error, INVESTIGATION_GRAPH_MODEL_ERROR_INVALID_ARGUMENT ); assert( investigation_graph_model_get_relation_count( graph_model ) == 0 ); g_clear_error( &error ); investigation_graph_model_free( graph_model ); } /** * @brief Vérifie le refus d'un UUID de relation dupliqué. */ static void test_graph_reject_duplicate_relation(void) { InvestigationGraphModel *graph_model = NULL; RelationRecord *first_relation = NULL; RelationRecord *duplicate_relation = NULL; GError *error = NULL; graph_model = investigation_graph_model_new( &error ); assert(graph_model != NULL); assert(error == NULL); test_graph_add_standard_entities( graph_model ); first_relation = test_graph_create_relation( TEST_RELATION_AB_IDENTIFIER, TEST_ENTITY_A_IDENTIFIER, TEST_ENTITY_B_IDENTIFIER, "uses" ); duplicate_relation = test_graph_create_relation( TEST_RELATION_AB_IDENTIFIER, TEST_ENTITY_A_IDENTIFIER, TEST_ENTITY_C_IDENTIFIER, "controls" ); test_graph_add_relation_or_abort( graph_model, first_relation ); assert( !investigation_graph_model_add_relation( graph_model, duplicate_relation, &error ) ); test_graph_assert_error( error, INVESTIGATION_GRAPH_MODEL_ERROR_DUPLICATE_RELATION ); assert( investigation_graph_model_get_relation_count( graph_model ) == 1 ); /* * Après l'échec, la relation appartient encore à l'appelant. */ relation_record_free( duplicate_relation ); g_clear_error( &error ); investigation_graph_model_free( graph_model ); } /** * @brief Vérifie le refus d'une relation dont la source est absente. */ static void test_graph_reject_missing_source(void) { InvestigationGraphModel *graph_model = NULL; RelationRecord *relation_record = NULL; GPtrArray *incoming_relations = NULL; GError *error = NULL; graph_model = investigation_graph_model_new( &error ); assert(graph_model != NULL); assert(error == NULL); test_graph_add_entity_or_abort( graph_model, test_graph_create_entity( TEST_ENTITY_B_IDENTIFIER, "Personne B" ) ); relation_record = test_graph_create_relation( TEST_RELATION_AB_IDENTIFIER, TEST_ENTITY_A_IDENTIFIER, TEST_ENTITY_B_IDENTIFIER, "uses" ); assert( !investigation_graph_model_add_relation( graph_model, relation_record, &error ) ); test_graph_assert_error( error, INVESTIGATION_GRAPH_MODEL_ERROR_SOURCE_NOT_FOUND ); assert( investigation_graph_model_get_relation_count( graph_model ) == 0 ); g_clear_error( &error ); incoming_relations = investigation_graph_model_list_incoming_relations( graph_model, TEST_ENTITY_B_IDENTIFIER, &error ); assert(incoming_relations != NULL); assert(error == NULL); assert(incoming_relations->len == 0); g_ptr_array_unref( incoming_relations ); relation_record_free( relation_record ); investigation_graph_model_free( graph_model ); } /** * @brief Vérifie le refus d'une relation dont la cible est absente. */ static void test_graph_reject_missing_target(void) { InvestigationGraphModel *graph_model = NULL; RelationRecord *relation_record = NULL; GPtrArray *outgoing_relations = NULL; GError *error = NULL; graph_model = investigation_graph_model_new( &error ); assert(graph_model != NULL); assert(error == NULL); test_graph_add_entity_or_abort( graph_model, test_graph_create_entity( TEST_ENTITY_A_IDENTIFIER, "Personne A" ) ); relation_record = test_graph_create_relation( TEST_RELATION_AB_IDENTIFIER, TEST_ENTITY_A_IDENTIFIER, TEST_ENTITY_B_IDENTIFIER, "uses" ); assert( !investigation_graph_model_add_relation( graph_model, relation_record, &error ) ); test_graph_assert_error( error, INVESTIGATION_GRAPH_MODEL_ERROR_TARGET_NOT_FOUND ); assert( investigation_graph_model_get_relation_count( graph_model ) == 0 ); g_clear_error( &error ); outgoing_relations = investigation_graph_model_list_outgoing_relations( graph_model, TEST_ENTITY_A_IDENTIFIER, &error ); assert(outgoing_relations != NULL); assert(error == NULL); assert(outgoing_relations->len == 0); g_ptr_array_unref( outgoing_relations ); relation_record_free( relation_record ); investigation_graph_model_free( graph_model ); } /** * @brief Vérifie l'ordre global des relations. */ static void test_graph_list_relations_sorted(void) { InvestigationGraphModel *graph_model = NULL; GPtrArray *relations = NULL; const RelationRecord *first_relation = NULL; const RelationRecord *second_relation = NULL; const RelationRecord *third_relation = NULL; GError *error = NULL; graph_model = investigation_graph_model_new( &error ); assert(graph_model != NULL); assert(error == NULL); test_graph_add_standard_entities( graph_model ); test_graph_add_relation_or_abort( graph_model, test_graph_create_relation( TEST_RELATION_CA_IDENTIFIER, TEST_ENTITY_C_IDENTIFIER, TEST_ENTITY_A_IDENTIFIER, "knows" ) ); test_graph_add_relation_or_abort( graph_model, test_graph_create_relation( TEST_RELATION_AB_IDENTIFIER, TEST_ENTITY_A_IDENTIFIER, TEST_ENTITY_B_IDENTIFIER, "uses" ) ); test_graph_add_relation_or_abort( graph_model, test_graph_create_relation( TEST_RELATION_AC_IDENTIFIER, TEST_ENTITY_A_IDENTIFIER, TEST_ENTITY_C_IDENTIFIER, "controls" ) ); relations = investigation_graph_model_list_relations( graph_model, &error ); assert(relations != NULL); assert(error == NULL); assert(relations->len == 3); first_relation = g_ptr_array_index( relations, 0 ); second_relation = g_ptr_array_index( relations, 1 ); third_relation = g_ptr_array_index( relations, 2 ); assert( strcmp( relation_record_get_identifier( first_relation ), TEST_RELATION_AB_IDENTIFIER ) == 0 ); assert( strcmp( relation_record_get_identifier( second_relation ), TEST_RELATION_AC_IDENTIFIER ) == 0 ); assert( strcmp( relation_record_get_identifier( third_relation ), TEST_RELATION_CA_IDENTIFIER ) == 0 ); g_ptr_array_unref( relations ); assert( investigation_graph_model_find_relation( graph_model, TEST_RELATION_AB_IDENTIFIER ) != NULL ); investigation_graph_model_free( graph_model ); } /** * @brief Vérifie les listes entrantes, sortantes et incidentes. */ static void test_graph_navigation_indexes(void) { InvestigationGraphModel *graph_model = NULL; RelationRecord *relation_ab = NULL; RelationRecord *relation_ac = NULL; RelationRecord *relation_ca = NULL; GPtrArray *outgoing_a = NULL; GPtrArray *incoming_a = NULL; GPtrArray *incident_a = NULL; GPtrArray *outgoing_b = NULL; GError *error = NULL; graph_model = investigation_graph_model_new( &error ); assert(graph_model != NULL); assert(error == NULL); test_graph_add_standard_entities( graph_model ); relation_ab = test_graph_create_relation( TEST_RELATION_AB_IDENTIFIER, TEST_ENTITY_A_IDENTIFIER, TEST_ENTITY_B_IDENTIFIER, "uses" ); relation_ac = test_graph_create_relation( TEST_RELATION_AC_IDENTIFIER, TEST_ENTITY_A_IDENTIFIER, TEST_ENTITY_C_IDENTIFIER, "controls" ); relation_ca = test_graph_create_relation( TEST_RELATION_CA_IDENTIFIER, TEST_ENTITY_C_IDENTIFIER, TEST_ENTITY_A_IDENTIFIER, "knows" ); test_graph_add_relation_or_abort( graph_model, relation_ca ); test_graph_add_relation_or_abort( graph_model, relation_ac ); test_graph_add_relation_or_abort( graph_model, relation_ab ); outgoing_a = investigation_graph_model_list_outgoing_relations( graph_model, TEST_ENTITY_A_IDENTIFIER, &error ); assert(outgoing_a != NULL); assert(error == NULL); assert(outgoing_a->len == 2); assert( g_ptr_array_index( outgoing_a, 0 ) == relation_ab ); assert( g_ptr_array_index( outgoing_a, 1 ) == relation_ac ); incoming_a = investigation_graph_model_list_incoming_relations( graph_model, TEST_ENTITY_A_IDENTIFIER, &error ); assert(incoming_a != NULL); assert(error == NULL); assert(incoming_a->len == 1); assert( g_ptr_array_index( incoming_a, 0 ) == relation_ca ); incident_a = investigation_graph_model_list_incident_relations( graph_model, TEST_ENTITY_A_IDENTIFIER, &error ); assert(incident_a != NULL); assert(error == NULL); assert(incident_a->len == 3); assert( g_ptr_array_index( incident_a, 0 ) == relation_ab ); assert( g_ptr_array_index( incident_a, 1 ) == relation_ac ); assert( g_ptr_array_index( incident_a, 2 ) == relation_ca ); /* * B ne possède aucune relation sortante. */ outgoing_b = investigation_graph_model_list_outgoing_relations( graph_model, TEST_ENTITY_B_IDENTIFIER, &error ); assert(outgoing_b != NULL); assert(error == NULL); assert(outgoing_b->len == 0); g_ptr_array_unref( outgoing_b ); g_ptr_array_unref( incident_a ); g_ptr_array_unref( incoming_a ); g_ptr_array_unref( outgoing_a ); investigation_graph_model_free( graph_model ); } /** * @brief Vérifie les erreurs de navigation par entité. */ static void test_graph_navigation_errors(void) { InvestigationGraphModel *graph_model = NULL; GPtrArray *relations = NULL; GError *error = NULL; graph_model = investigation_graph_model_new( &error ); assert(graph_model != NULL); assert(error == NULL); relations = investigation_graph_model_list_outgoing_relations( graph_model, "identifiant-invalide", &error ); assert(relations == NULL); test_graph_assert_error( error, INVESTIGATION_GRAPH_MODEL_ERROR_INVALID_ARGUMENT ); g_clear_error( &error ); relations = investigation_graph_model_list_incoming_relations( graph_model, TEST_ENTITY_A_IDENTIFIER, &error ); assert(relations == NULL); test_graph_assert_error( error, INVESTIGATION_GRAPH_MODEL_ERROR_ENTITY_NOT_FOUND ); g_clear_error( &error ); relations = investigation_graph_model_list_incident_relations( NULL, TEST_ENTITY_A_IDENTIFIER, &error ); assert(relations == NULL); test_graph_assert_error( error, INVESTIGATION_GRAPH_MODEL_ERROR_INVALID_ARGUMENT ); g_clear_error( &error ); investigation_graph_model_free( graph_model ); } /** * @brief Vérifie les valeurs défensives pour un graphe NULL. */ static void test_graph_null_accessors(void) { GPtrArray *items = NULL; GError *error = NULL; assert( investigation_graph_model_get_entity_count( NULL ) == 0 ); assert( investigation_graph_model_get_relation_count( NULL ) == 0 ); assert( investigation_graph_model_find_entity( NULL, TEST_ENTITY_A_IDENTIFIER ) == NULL ); assert( investigation_graph_model_find_relation( NULL, TEST_RELATION_AB_IDENTIFIER ) == NULL ); items = investigation_graph_model_list_entities( NULL, &error ); assert(items == NULL); test_graph_assert_error( error, INVESTIGATION_GRAPH_MODEL_ERROR_INVALID_ARGUMENT ); g_clear_error( &error ); items = investigation_graph_model_list_relations( NULL, &error ); assert(items == NULL); test_graph_assert_error( error, INVESTIGATION_GRAPH_MODEL_ERROR_INVALID_ARGUMENT ); g_clear_error( &error ); } /** * @brief Vérifie que GError est facultatif. */ static void test_graph_optional_error(void) { InvestigationGraphModel *graph_model = NULL; EntityRecord *duplicate_entity = NULL; graph_model = investigation_graph_model_new( NULL ); assert(graph_model != NULL); test_graph_add_entity_or_abort( graph_model, test_graph_create_entity( TEST_ENTITY_A_IDENTIFIER, "Personne A" ) ); duplicate_entity = test_graph_create_entity( TEST_ENTITY_A_IDENTIFIER, "Duplicata" ); assert( !investigation_graph_model_add_entity( graph_model, duplicate_entity, NULL ) ); entity_record_free( duplicate_entity ); investigation_graph_model_free( graph_model ); } int main( int argc, char **argv ) { g_test_init( &argc, &argv, NULL ); test_graph_empty_lifecycle(); test_graph_add_and_find_entity(); test_graph_reject_null_entity(); test_graph_reject_duplicate_entity(); test_graph_list_entities_sorted(); test_graph_add_and_find_relation(); test_graph_reject_null_relation(); test_graph_reject_duplicate_relation(); test_graph_reject_missing_source(); test_graph_reject_missing_target(); test_graph_list_relations_sorted(); test_graph_navigation_indexes(); test_graph_navigation_errors(); test_graph_null_accessors(); test_graph_optional_error(); printf( "InvestigationGraphModel : tous les tests sont valides.\n" ); return 0; }