labfy-investigation/tests/test_eml_analysis_persistence.c
fy59 d65f9559e6 chore(checkpoint): preserve local investigation workbench
Preserve the local graph-first investigation stack through macro V1.

Include reviewed source, tests, contracts, documentation and synthetic assets.

Retain the standalone financial V21 test foundation without runtime migration.

Runtime remains V20; operational JobStore remains V3.

Local experimental checkpoint, not a stable release.
2026-09-27 08:34:46 +02:00

560 lines
24 KiB
C

#define _POSIX_C_SOURCE 200809L
/******************************************************************************
* @file test_eml_analysis_persistence.c
* @brief Analyse EML native, publication V20 et projection de provenance.
******************************************************************************/
#include "core/core_graph_projection_service.h"
#include "core/eml_analysis_persistence_service.h"
#include "core/eml_analysis_persistence_service_test.h"
#include "core/eml_graph_specimen.h"
#include "core/evidence_importer.h"
#include "core/observation_review_service.h"
#include "dao/evidence_entity_dao.h"
#include "database/database.h"
#include "database/statement.h"
#include "models/evidence_observation.h"
#include "models/evidence_record.h"
#include <glib.h>
#include <glib/gstdio.h>
#include <json-glib/json-glib.h>
#include <unistd.h>
typedef struct
{
char *root;
char *database_path;
char *controlled_path;
char *evidence_identifier;
} TestFixture;
static const char *const specimen_eml =
"From: Élodie Exemple <Elodie@Atelier.test>\r\n"
"To: Noé <noe@destination.test>\r\n"
"Received: from relais.atelier.test ([192.0.2.42]) by mx.test;\r\n"
"Message-ID: <specimen@atelier.test>\r\n"
"Subject: SPECIMEN\r\n\r\nCorps local.\r\n";
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_count(Database *database, const char *table)
{
char *sql = g_strdup_printf("SELECT COUNT(*) FROM %s;", table);
DatabaseStatement *statement = database_statement_prepare(database, sql);
gint64 value = -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, &value));
database_statement_finalize(statement);
g_free(sql);
return value;
}
static void test_assert_database_ok(Database *database)
{
DatabaseStatement *statement = database_statement_prepare(database,
"PRAGMA integrity_check;");
char *value = NULL;
g_assert_nonnull(statement);
g_assert_cmpint(database_statement_step(statement), ==,
DATABASE_STATEMENT_STEP_ROW);
g_assert_true(database_statement_column_text(statement, 0, &value));
g_assert_cmpstr(value, ==, "ok");
g_free(value);
database_statement_finalize(statement);
statement = database_statement_prepare(database, "PRAGMA foreign_key_check;");
g_assert_nonnull(statement);
g_assert_cmpint(database_statement_step(statement), ==,
DATABASE_STATEMENT_STEP_DONE);
database_statement_finalize(statement);
}
static TestFixture *test_fixture_new(const char *contents, gssize length)
{
GError *error = NULL;
TestFixture *fixture = g_new0(TestFixture, 1);
char *source_path = NULL;
char *destination = NULL;
Database *database = NULL;
EvidenceImporter *importer = NULL;
EvidenceRecord *record = NULL;
fixture->root = g_dir_make_tmp("labfy-eml-persistence-XXXXXX", &error);
g_assert_no_error(error);
fixture->database_path = g_build_filename(fixture->root,
"Enquete.sqlite", NULL);
source_path = g_build_filename(fixture->root, "SPECIMEN-input.eml", NULL);
destination = g_build_filename(fixture->root,
"01_Preuves_Originales", "Emails", NULL);
g_assert_cmpint(g_mkdir_with_parents(destination, 0700), ==, 0);
g_assert_true(g_file_set_contents(source_path, contents, length, &error));
g_assert_no_error(error);
g_assert_true(database_initialize(fixture->database_path,
"SPECIMEN EML service", fixture->root));
database = database_open(fixture->database_path);
g_assert_nonnull(database);
importer = evidence_importer_new(database, &error);
g_assert_no_error(error);
EvidenceImportRequest request = {
.source_path = source_path,
.destination_directory = destination,
.relative_directory = "01_Preuves_Originales/Emails",
.type_identifier = "email",
.collected_at = "2026-09-26T14:00:00Z",
.source = "Fixture SPECIMEN",
.description = "Entrée synthétique"
};
record = evidence_importer_import(importer, &request, NULL, &error);
g_assert_no_error(error);
g_assert_nonnull(record);
fixture->evidence_identifier = g_strdup(
evidence_record_get_identifier(record));
fixture->controlled_path = g_build_filename(fixture->root,
evidence_record_get_relative_path(record), NULL);
evidence_record_free(record);
evidence_importer_free(importer);
database_close(database);
g_free(destination);
g_free(source_path);
return fixture;
}
static void test_fixture_free(TestFixture *fixture)
{
if (fixture == NULL) return;
test_remove_tree(fixture->root);
g_free(fixture->evidence_identifier);
g_free(fixture->controlled_path);
g_free(fixture->database_path);
g_free(fixture->root);
g_free(fixture);
}
static EmlAnalysisPublicationResult *test_analyze(
Database *database,
TestFixture *fixture,
const char *request_identifier,
const char *derivative_identifier,
GError **error)
{
EmlAnalysisPersistenceRequest request = {
.request_identifier = request_identifier,
.source_evidence_identifier = fixture->evidence_identifier,
.derivative_evidence_identifier = derivative_identifier,
.requested_at = "2026-09-26T14:00:00Z"
};
EmlAnalysisPersistenceService *service =
eml_analysis_persistence_service_new(database, fixture->root, error);
EmlAnalysisPrepared *prepared = service != NULL
? eml_analysis_persistence_service_prepare(service, &request, NULL, error)
: NULL;
EmlAnalysisPublicationResult *result = prepared != NULL
? eml_analysis_persistence_service_publish(service, prepared, NULL, error)
: NULL;
eml_analysis_prepared_free(prepared);
eml_analysis_persistence_service_free(service);
return result;
}
static void test_native_analysis_persists_and_replays(void)
{
GError *error = NULL;
TestFixture *fixture = test_fixture_new(specimen_eml, -1);
Database *database = database_open(fixture->database_path);
EmlAnalysisPublicationResult *first = test_analyze(database, fixture,
EML_GRAPH_REQUEST_PRIMARY, EML_GRAPH_DERIVATIVE_PRIMARY, &error);
g_assert_no_error(error);
g_assert_nonnull(first);
g_assert_false(eml_analysis_publication_result_was_reused(first));
g_assert_cmpuint(eml_analysis_publication_result_get_observation_count(first),
>, 0U);
g_assert_cmpint(test_count(database, "entites"), ==, 0);
g_assert_cmpint(test_count(database, "relations"), ==, 0);
g_assert_cmpint(test_count(database, "extractions"), ==, 1);
g_assert_cmpint(test_count(database, "preuves"), ==, 2);
EvidenceEntityDao *dao = evidence_entity_dao_new(database, &error);
GPtrArray *observations = evidence_entity_dao_list_observations(dao,
fixture->evidence_identifier, &error);
g_assert_no_error(error);
g_assert_nonnull(observations);
gboolean found_normalization = FALSE;
for (guint index = 0; index < observations->len; index++)
{
EvidenceObservation *item = g_ptr_array_index(observations, index);
g_assert_cmpstr(item->verification_status, ==, "proposed");
g_assert_null(item->entity_identifier);
g_assert_cmpstr(item->extraction_identifier, ==,
EML_GRAPH_REQUEST_PRIMARY);
if (g_strcmp0(item->value_raw, "Elodie@Atelier.test") == 0 &&
g_strcmp0(item->value_normalized, "elodie@atelier.test") == 0)
found_normalization = TRUE;
}
g_assert_true(found_normalization);
test_assert_database_ok(database);
g_ptr_array_unref(observations);
evidence_entity_dao_free(dao);
database_close(database);
database = database_open(fixture->database_path);
EmlAnalysisPublicationResult *replay = test_analyze(database, fixture,
EML_GRAPH_REQUEST_PRIMARY, EML_GRAPH_DERIVATIVE_PRIMARY, &error);
g_assert_no_error(error);
g_assert_nonnull(replay);
g_assert_true(eml_analysis_publication_result_was_reused(replay));
g_assert_cmpuint(eml_analysis_publication_result_get_observation_count(replay),
==, eml_analysis_publication_result_get_observation_count(first));
g_assert_cmpint(test_count(database, "extractions"), ==, 1);
g_assert_cmpint(test_count(database, "evidence_entity_observations"), ==,
(gint64) eml_analysis_publication_result_get_observation_count(first));
EmlAnalysisPersistenceRequest conflict = {
.request_identifier = EML_GRAPH_REQUEST_PRIMARY,
.source_evidence_identifier = fixture->evidence_identifier,
.derivative_evidence_identifier = EML_GRAPH_DERIVATIVE_REANALYSIS,
.requested_at = "2026-09-26T14:00:00Z"
};
EmlAnalysisPersistenceService *service =
eml_analysis_persistence_service_new(database, fixture->root, &error);
g_assert_nonnull(service);
g_assert_null(eml_analysis_persistence_service_prepare(service, &conflict,
NULL, &error));
g_assert_error(error, EML_ANALYSIS_PERSISTENCE_ERROR,
EML_ANALYSIS_PERSISTENCE_ERROR_CONFLICT);
g_clear_error(&error);
eml_analysis_persistence_service_free(service);
DatabaseStatement *mutation = database_statement_prepare(database,
"UPDATE evidence_entity_observations SET value_normalized='tampered' "
"WHERE extraction_id='61000000-0000-4000-8000-000000000031' "
"AND id=(SELECT MIN(id) FROM evidence_entity_observations WHERE "
"extraction_id='61000000-0000-4000-8000-000000000031');");
g_assert_nonnull(mutation);
g_assert_cmpint(database_statement_step(mutation), ==,
DATABASE_STATEMENT_STEP_DONE);
database_statement_finalize(mutation);
g_assert_null(test_analyze(database, fixture, EML_GRAPH_REQUEST_PRIMARY,
EML_GRAPH_DERIVATIVE_PRIMARY, &error));
g_assert_error(error, EML_ANALYSIS_PERSISTENCE_ERROR,
EML_ANALYSIS_PERSISTENCE_ERROR_CONFLICT);
g_clear_error(&error);
g_assert_true(g_file_set_contents(fixture->controlled_path,
"From: altered-replay@specimen.test\r\n\r\n", -1, &error));
g_assert_no_error(error);
g_assert_null(test_analyze(database, fixture, EML_GRAPH_REQUEST_PRIMARY,
EML_GRAPH_DERIVATIVE_PRIMARY, &error));
g_assert_error(error, EML_ANALYSIS_PERSISTENCE_ERROR,
EML_ANALYSIS_PERSISTENCE_ERROR_SOURCE);
g_clear_error(&error);
g_assert_cmpint(test_count(database, "extractions"), ==, 1);
eml_analysis_publication_result_free(replay);
eml_analysis_publication_result_free(first);
database_close(database);
test_fixture_free(fixture);
}
static void test_replay_accepts_journalled_review_and_promotion(void)
{
GError *error = NULL;
TestFixture *fixture = test_fixture_new(specimen_eml, -1);
Database *database = database_open(fixture->database_path);
EmlAnalysisPublicationResult *first = test_analyze(database, fixture,
EML_GRAPH_REQUEST_PRIMARY, EML_GRAPH_DERIVATIVE_PRIMARY, &error);
EvidenceEntityDao *dao = evidence_entity_dao_new(database, &error);
GPtrArray *observations = evidence_entity_dao_list_observations(dao,
fixture->evidence_identifier, &error);
const char *observation_identifier = NULL;
ObservationReviewService *review_service = NULL;
ObservationReviewResult review_result = {0};
g_assert_no_error(error);
g_assert_nonnull(first);
g_assert_nonnull(observations);
for (guint index = 0U; index < observations->len; index++)
{
EvidenceObservation *item = g_ptr_array_index(observations, index);
if (g_strcmp0(item->value_raw, "Elodie@Atelier.test") == 0)
{
observation_identifier = item->identifier;
break;
}
}
g_assert_nonnull(observation_identifier);
review_service = observation_review_service_new(database, &error);
g_assert_no_error(error);
g_assert_nonnull(review_service);
ObservationReviewRequest decide = {
.operation_identifier = "63000000-0000-4000-8000-000000000001",
.evidence_identifier = fixture->evidence_identifier,
.observation_identifier = observation_identifier,
.expected_revision = 0U,
.action = OBSERVATION_REVIEW_ACTION_DECIDE,
.verification_status = "confirmed",
.author = "analyste-specimen",
.reason = "Confirmation humaine synthétique",
.occurred_at = "2026-09-26T14:05:00Z"
};
g_assert_true(observation_review_service_apply(review_service, &decide,
&review_result, &error));
g_assert_no_error(error);
observation_review_result_clear(&review_result);
ObservationReviewRequest correct = decide;
correct.operation_identifier =
"63000000-0000-4000-8000-000000000002";
correct.expected_revision = 1U;
correct.action = OBSERVATION_REVIEW_ACTION_CORRECT;
correct.verification_status = NULL;
correct.corrected_value = "elodie.revue@atelier.test";
g_assert_true(observation_review_service_apply(review_service, &correct,
&review_result, &error));
g_assert_no_error(error);
observation_review_result_clear(&review_result);
ObservationReviewRequest promote = decide;
promote.operation_identifier =
"63000000-0000-4000-8000-000000000003";
promote.expected_revision = 2U;
promote.action = OBSERVATION_REVIEW_ACTION_PROMOTE_CREATE;
promote.verification_status = NULL;
g_assert_true(observation_review_service_apply(review_service, &promote,
&review_result, &error));
g_assert_no_error(error);
g_assert_true(review_result.entity_created);
observation_review_result_clear(&review_result);
EmlAnalysisPublicationResult *promoted_replay = test_analyze(database,
fixture, EML_GRAPH_REQUEST_PRIMARY, EML_GRAPH_DERIVATIVE_PRIMARY,
&error);
g_assert_no_error(error);
g_assert_nonnull(promoted_replay);
g_assert_true(eml_analysis_publication_result_was_reused(promoted_replay));
ObservationReviewRequest withdraw = decide;
withdraw.operation_identifier =
"63000000-0000-4000-8000-000000000004";
withdraw.expected_revision = 3U;
withdraw.action = OBSERVATION_REVIEW_ACTION_WITHDRAW_PROMOTION;
withdraw.verification_status = NULL;
g_assert_true(observation_review_service_apply(review_service, &withdraw,
&review_result, &error));
g_assert_no_error(error);
observation_review_result_clear(&review_result);
EmlAnalysisPublicationResult *withdrawn_replay = test_analyze(database,
fixture, EML_GRAPH_REQUEST_PRIMARY, EML_GRAPH_DERIVATIVE_PRIMARY,
&error);
g_assert_no_error(error);
g_assert_nonnull(withdrawn_replay);
g_assert_true(eml_analysis_publication_result_was_reused(withdrawn_replay));
test_assert_database_ok(database);
eml_analysis_publication_result_free(withdrawn_replay);
eml_analysis_publication_result_free(promoted_replay);
observation_review_service_free(review_service);
g_ptr_array_unref(observations);
evidence_entity_dao_free(dao);
eml_analysis_publication_result_free(first);
database_close(database);
test_fixture_free(fixture);
}
static void test_publication_failure_rolls_back_and_compensates(void)
{
GError *error = NULL;
TestFixture *fixture = test_fixture_new(specimen_eml, -1);
Database *database = database_open(fixture->database_path);
EmlAnalysisPersistenceRequest request = {
.request_identifier = EML_GRAPH_REQUEST_PRIMARY,
.source_evidence_identifier = fixture->evidence_identifier,
.derivative_evidence_identifier = EML_GRAPH_DERIVATIVE_PRIMARY,
.requested_at = "2026-09-26T14:00:00Z"
};
EmlAnalysisPersistenceService *service =
eml_analysis_persistence_service_new(database, fixture->root, &error);
EmlAnalysisPrepared *prepared =
eml_analysis_persistence_service_prepare(service, &request, NULL, &error);
char *artifact = g_build_filename(fixture->root,
"02_Preuves_Traitees", "Extractions", "EML",
"62000000-0000-4000-8000-000000000031.json", NULL);
g_assert_no_error(error);
g_assert_nonnull(prepared);
eml_analysis_persistence_test_fail_after_derivative_insert(TRUE);
g_assert_null(eml_analysis_persistence_service_publish(service, prepared,
NULL, &error));
eml_analysis_persistence_test_fail_after_derivative_insert(FALSE);
g_assert_error(error, EML_ANALYSIS_PERSISTENCE_ERROR,
EML_ANALYSIS_PERSISTENCE_ERROR_DATABASE);
g_clear_error(&error);
g_assert_cmpint(test_count(database, "preuves"), ==, 1);
g_assert_cmpint(test_count(database, "extractions"), ==, 0);
g_assert_cmpint(test_count(database, "evidence_entity_observations"), ==, 0);
g_assert_false(g_file_test(artifact, G_FILE_TEST_EXISTS));
test_assert_database_ok(database);
g_free(artifact);
eml_analysis_prepared_free(prepared);
eml_analysis_persistence_service_free(service);
database_close(database);
test_fixture_free(fixture);
}
static void test_missing_modified_and_oversize_sources(void)
{
GError *error = NULL;
TestFixture *missing = test_fixture_new(specimen_eml, -1);
Database *database = database_open(missing->database_path);
g_assert_cmpint(g_remove(missing->controlled_path), ==, 0);
g_assert_null(test_analyze(database, missing, EML_GRAPH_REQUEST_PRIMARY,
EML_GRAPH_DERIVATIVE_PRIMARY, &error));
g_assert_error(error, EML_ANALYSIS_PERSISTENCE_ERROR,
EML_ANALYSIS_PERSISTENCE_ERROR_SOURCE);
g_clear_error(&error);
database_close(database);
test_fixture_free(missing);
TestFixture *modified = test_fixture_new(specimen_eml, -1);
database = database_open(modified->database_path);
g_assert_true(g_file_set_contents(modified->controlled_path,
"From: changed@specimen.test\r\n\r\n", -1, &error));
g_assert_no_error(error);
g_assert_null(test_analyze(database, modified, EML_GRAPH_REQUEST_PRIMARY,
EML_GRAPH_DERIVATIVE_PRIMARY, &error));
g_assert_error(error, EML_ANALYSIS_PERSISTENCE_ERROR,
EML_ANALYSIS_PERSISTENCE_ERROR_SOURCE);
g_clear_error(&error);
database_close(database);
test_fixture_free(modified);
GString *large = g_string_sized_new(EML_ANALYSIS_MAX_SOURCE_BYTES + 1U);
g_string_append(large, "From: large@specimen.test\r\n\r\n");
while (large->len <= EML_ANALYSIS_MAX_SOURCE_BYTES)
g_string_append_c(large, 'x');
TestFixture *oversize = test_fixture_new(large->str, large->len);
g_string_free(large, TRUE);
database = database_open(oversize->database_path);
g_assert_null(test_analyze(database, oversize, EML_GRAPH_REQUEST_PRIMARY,
EML_GRAPH_DERIVATIVE_PRIMARY, &error));
g_assert_error(error, EML_ANALYSIS_PERSISTENCE_ERROR,
EML_ANALYSIS_PERSISTENCE_ERROR_LIMIT);
g_clear_error(&error);
database_close(database);
test_fixture_free(oversize);
}
static void test_specimen_reanalysis_and_projection_failures(void)
{
GError *error = NULL;
char *directory = g_dir_make_tmp("labfy-eml-graph-test-XXXXXX", &error);
EmlGraphSpecimenPaths paths = {0};
g_assert_no_error(error);
g_assert_true(eml_graph_specimen_generate(directory,
EML_GRAPH_SPECIMEN_DEFAULT, &paths, &error));
g_assert_no_error(error);
Database *reader = database_open_read_only(paths.database_path, &error);
g_assert_no_error(error);
g_assert_nonnull(reader);
g_assert_cmpint(test_count(reader, "extractions"), ==, 2);
CoreGraphLimits too_small = {1U, 1U, 1024U * 1024U};
g_assert_null(core_graph_projection_service_collect(reader, too_small,
&error));
g_assert_nonnull(error);
g_clear_error(&error);
g_assert_null(core_graph_projection_service_collect(reader, too_small,
NULL));
database_close(reader);
eml_graph_specimen_paths_clear(&paths);
test_remove_tree(directory);
g_free(directory);
}
static void test_empty_analysis_and_cancellation(void)
{
GError *error = NULL;
TestFixture *fixture = test_fixture_new(
"Subject: aucun indicateur\r\n\r\nCorps SPECIMEN.\r\n", -1);
Database *database = database_open(fixture->database_path);
EmlAnalysisPublicationResult *empty = test_analyze(database, fixture,
EML_GRAPH_REQUEST_PRIMARY, EML_GRAPH_DERIVATIVE_PRIMARY, &error);
g_assert_no_error(error);
g_assert_nonnull(empty);
g_assert_cmpuint(eml_analysis_publication_result_get_observation_count(empty),
==, 0U);
g_assert_cmpint(test_count(database, "extractions"), ==, 1);
g_assert_cmpint(test_count(database, "evidence_entity_observations"), ==, 0);
EmlAnalysisPersistenceRequest request = {
.request_identifier = EML_GRAPH_REQUEST_REANALYSIS,
.source_evidence_identifier = fixture->evidence_identifier,
.derivative_evidence_identifier = EML_GRAPH_DERIVATIVE_REANALYSIS,
.requested_at = "2026-09-26T14:01:00Z"
};
EmlAnalysisPersistenceService *service =
eml_analysis_persistence_service_new(database, fixture->root, &error);
GCancellable *cancellable = g_cancellable_new();
g_cancellable_cancel(cancellable);
g_assert_null(eml_analysis_persistence_service_prepare(service, &request,
cancellable, &error));
g_assert_error(error, EML_ANALYSIS_PERSISTENCE_ERROR,
EML_ANALYSIS_PERSISTENCE_ERROR_CANCELLED);
g_clear_error(&error);
g_assert_cmpint(test_count(database, "extractions"), ==, 1);
g_object_unref(cancellable);
eml_analysis_persistence_service_free(service);
eml_analysis_publication_result_free(empty);
database_close(database);
test_fixture_free(fixture);
}
static void test_derivative_directory_rejects_symlink(void)
{
GError *error = NULL;
TestFixture *fixture = test_fixture_new(specimen_eml, -1);
Database *database = database_open(fixture->database_path);
char *link_path = g_build_filename(fixture->root,
"02_Preuves_Traitees", NULL);
g_assert_cmpint(symlink("01_Preuves_Originales", link_path), ==, 0);
g_assert_null(test_analyze(database, fixture, EML_GRAPH_REQUEST_PRIMARY,
EML_GRAPH_DERIVATIVE_PRIMARY, &error));
g_assert_error(error, EML_ANALYSIS_PERSISTENCE_ERROR,
EML_ANALYSIS_PERSISTENCE_ERROR_ARTIFACT);
g_clear_error(&error);
g_assert_cmpint(test_count(database, "preuves"), ==, 1);
g_assert_cmpint(test_count(database, "extractions"), ==, 0);
g_assert_cmpint(g_remove(link_path), ==, 0);
g_free(link_path);
database_close(database);
test_fixture_free(fixture);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/eml-analysis/persistence/replay",
test_native_analysis_persists_and_replays);
g_test_add_func("/eml-analysis/persistence/replay-reviewed",
test_replay_accepts_journalled_review_and_promotion);
g_test_add_func("/eml-analysis/persistence/rollback",
test_publication_failure_rolls_back_and_compensates);
g_test_add_func("/eml-analysis/source/validation",
test_missing_modified_and_oversize_sources);
g_test_add_func("/eml-analysis/graph/reanalysis",
test_specimen_reanalysis_and_projection_failures);
g_test_add_func("/eml-analysis/analysis/empty-and-cancelled",
test_empty_analysis_and_cancellation);
g_test_add_func("/eml-analysis/artifact/symlink",
test_derivative_directory_rejects_symlink);
return g_test_run();
}