/****************************************************************************** * @file test_core_graph.c * @brief Contrats J3 lecture seule, fixture C, projection et sérialisation. ******************************************************************************/ #include "core/core_graph_projection_service.h" #include "core/core_graph_snapshot_serializer.h" #include "core/core_graph_specimen.h" #include "dao/evidence_dao.h" #include "database/database.h" #include "database/statement.h" #include "database/transaction.h" #include "models/core_graph_snapshot.h" #include #include #include #include static const CoreGraphLimits test_limits = { .max_nodes = 500U, .max_edges = 1000U, .max_serialized_bytes = 1024U * 1024U }; static void test_remove_tree(const char *path) { GDir *directory = g_dir_open(path, 0, NULL); const char *name = NULL; if (directory == NULL) { g_remove(path); return; } while ((name = g_dir_read_name(directory)) != NULL) { char *child = g_build_filename(path, name, NULL); if (g_file_test(child, G_FILE_TEST_IS_DIR)) test_remove_tree(child); else g_assert_cmpint(g_remove(child), ==, 0); g_free(child); } g_dir_close(directory); g_assert_cmpint(g_rmdir(path), ==, 0); } static gint64 test_query_count(Database *database, const char *table) { char *sql = g_strdup_printf("SELECT COUNT(*) FROM %s;", table); DatabaseStatement *statement = database_statement_prepare(database, sql); gint64 count = -1; g_assert_nonnull(statement); g_assert_cmpint(database_statement_step(statement), ==, DATABASE_STATEMENT_STEP_ROW); g_assert_true(database_statement_column_int64(statement, 0, &count)); g_assert_cmpint(database_statement_step(statement), ==, DATABASE_STATEMENT_STEP_DONE); database_statement_finalize(statement); g_free(sql); return count; } static char *test_query_metadata(Database *database, const char *key) { DatabaseStatement *statement = database_statement_prepare(database, "SELECT value FROM metadata WHERE key=?;"); char *value = NULL; g_assert_nonnull(statement); g_assert_true(database_statement_bind_text(statement, 1, key)); g_assert_cmpint(database_statement_step(statement), ==, DATABASE_STATEMENT_STEP_ROW); g_assert_true(database_statement_column_text(statement, 0, &value)); database_statement_finalize(statement); return value; } static void test_database_read_only_contract(void) { GError *error = NULL; char *directory = g_dir_make_tmp("labfy-core-readonly-XXXXXX", &error); char *path = g_build_filename(directory, "Enquete.sqlite", NULL); char *missing = g_build_filename(directory, "absente.sqlite", NULL); Database *database = NULL; DatabaseStatement *statement = NULL; g_assert_no_error(error); g_assert_true(database_initialize(path, "SPECIMEN read-only", directory)); database = database_open_read_only(path, &error); g_assert_no_error(error); g_assert_nonnull(database); g_assert_true(database_transaction_begin_read_only(database)); g_assert_cmpint(test_query_count(database, "metadata"), >, 0); g_assert_true(database_transaction_commit(database)); statement = database_statement_prepare(database, "INSERT INTO metadata(key,value) VALUES('forbidden','write');"); g_assert_nonnull(statement); g_test_expect_message(NULL, G_LOG_LEVEL_WARNING, "*attempt to write a readonly database*"); g_assert_cmpint(database_statement_step(statement), ==, DATABASE_STATEMENT_STEP_ERROR); g_test_assert_expected_messages(); database_statement_finalize(statement); database_close(database); database = database_open_read_only(missing, &error); g_assert_null(database); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_clear_error(&error); g_assert_false(g_file_test(missing, G_FILE_TEST_EXISTS)); g_free(missing); g_free(path); test_remove_tree(directory); g_free(directory); } static void test_database_read_only_rejects_unsupported_schema(void) { GError *error = NULL; char *directory = g_dir_make_tmp("labfy-core-schema-XXXXXX", &error); char *path = g_build_filename(directory, "Enquete.sqlite", NULL); Database *writer = NULL; Database *reader = NULL; DatabaseStatement *statement = NULL; g_assert_no_error(error); g_assert_true(database_initialize(path, "SPECIMEN schema", directory)); writer = database_open(path); statement = database_statement_prepare(writer, "UPDATE metadata SET value='21' WHERE key='schema_version';"); g_assert_nonnull(statement); g_assert_cmpint(database_statement_step(statement), ==, DATABASE_STATEMENT_STEP_DONE); database_statement_finalize(statement); database_close(writer); reader = database_open_read_only(path, &error); g_assert_null(reader); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); g_clear_error(&error); g_free(path); test_remove_tree(directory); g_free(directory); } static JsonObject *test_parse_object(const char *data, JsonParser **out_parser) { JsonParser *parser = json_parser_new(); GError *error = NULL; g_assert_true(json_parser_load_from_data(parser, data, -1, &error)); g_assert_no_error(error); *out_parser = parser; return json_node_get_object(json_parser_get_root(parser)); } static guint test_count_nodes(JsonArray *nodes, const char *kind, const char *state) { guint count = 0; for (guint index = 0; index < json_array_get_length(nodes); index++) { JsonObject *node = json_array_get_object_element(nodes, index); if (g_strcmp0(json_object_get_string_member(node, "object_kind"), kind) == 0 && (state == NULL || g_strcmp0(json_object_get_string_member(node, "state"), state) == 0)) count++; } return count; } static void test_assert_snapshot_contract(const char *data) { JsonParser *parser = NULL; JsonObject *root = test_parse_object(data, &parser); JsonArray *nodes = json_object_get_array_member(root, "nodes"); JsonArray *edges = json_object_get_array_member(root, "edges"); GHashTable *ids = g_hash_table_new(g_str_hash, g_str_equal); g_assert_cmpstr(json_object_get_string_member(root, "contract"), ==, "labfy.web_graph.snapshot.v2"); g_assert_cmpstr(json_object_get_string_member(root, "origin"), ==, "core"); g_assert_cmpint(json_object_get_int_member(root, "schema_version"), ==, 20); g_assert_true(json_object_get_boolean_member(root, "complete")); g_assert_cmpuint(json_array_get_length(nodes), ==, 9U); g_assert_cmpuint(json_array_get_length(edges), ==, 8U); g_assert_cmpuint(test_count_nodes(nodes, "entity", NULL), ==, 4U); g_assert_cmpuint(test_count_nodes(nodes, "evidence", NULL), ==, 2U); g_assert_cmpuint(test_count_nodes(nodes, "observation", "promoted"), ==, 1U); g_assert_cmpuint(test_count_nodes(nodes, "observation", "unpromoted"), ==, 1U); g_assert_cmpuint(test_count_nodes(nodes, "execution", NULL), ==, 1U); for (guint index = 0; index < json_array_get_length(nodes); index++) { JsonObject *node = json_array_get_object_element(nodes, index); const char *id = json_object_get_string_member(node, "id"); JsonObject *object_ref = json_object_get_object_member(node, "object_ref"); g_assert_false(g_hash_table_contains(ids, id)); g_hash_table_add(ids, (gpointer) id); g_assert_cmpstr(json_object_get_string_member(object_ref, "object_kind"), ==, json_object_get_string_member(node, "object_kind")); g_assert_cmpstr(json_object_get_string_member(object_ref, "object_id"), ==, json_object_get_string_member(node, "object_id")); } for (guint index = 0; index < json_array_get_length(edges); index++) { JsonObject *edge = json_array_get_object_element(edges, index); g_assert_true(g_hash_table_contains(ids, json_object_get_string_member(edge, "source"))); g_assert_true(g_hash_table_contains(ids, json_object_get_string_member(edge, "target"))); } g_assert_nonnull(g_strstr_len(data, -1, "