labfy-investigation/tests/test_evidence_integrity_task.c
2026-07-20 13:41:41 +02:00

1321 lines
25 KiB
C

/******************************************************************************
* @file test_evidence_integrity_task.c
* @brief Tests de la tâche asynchrone de vérification d'intégrité.
******************************************************************************/
#ifndef FILE_HASH_ENABLE_TEST_HOOKS
#define FILE_HASH_ENABLE_TEST_HOOKS
#endif
#include "core/evidence_integrity_task.h"
#include "core/background_task.h"
#include "core/evidence_integrity_verifier.h"
#include "core/file_hash.h"
#include "core/task_manager.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <glib/gstdio.h>
#define TEST_EVIDENCE_INTEGRITY_TASK_TIMEOUT_SECONDS 5
#define TEST_SHA256_ABC \
"ba7816bf8f01cfea414140de5dae2223" \
"b00361a396177a9cb410ff61f20015ad"
#define TEST_SHA256_EMPTY \
"e3b0c44298fc1c149afbf4c8996fb924" \
"27ae41e4649b934ca495991b7852b855"
/**
* @brief Contexte permettant d'attendre la fin d'une tâche.
*/
typedef struct
{
GMainLoop *main_loop;
GThread *main_thread;
GThread *callback_thread;
guint completion_count;
gboolean completed;
gboolean timed_out;
} TestEvidenceIntegrityTaskCompletionContext;
/**
* @brief Enquête temporaire utilisée par les tests.
*/
typedef struct
{
char *root_directory;
char *evidence_directory;
char *documents_directory;
TaskManager *task_manager;
} TestEvidenceIntegrityTaskFixture;
/**
* @brief Contexte du crochet d'annulation après le premier bloc lu.
*/
typedef struct
{
GMutex mutex;
GCond condition;
BackgroundTask *task;
guint block_count;
gboolean task_is_ready;
} TestEvidenceIntegrityTaskCancellationContext;
/**
* @brief Callback final commun aux tests.
*/
static void test_evidence_integrity_task_completed(
BackgroundTask *task,
gpointer user_data
)
{
TestEvidenceIntegrityTaskCompletionContext *context =
user_data;
assert(task != NULL);
assert(context != NULL);
assert(context->main_loop != NULL);
context->callback_thread =
g_thread_self();
context->completion_count++;
context->completed = TRUE;
g_main_loop_quit(
context->main_loop
);
}
/**
* @brief Interrompt un test asynchrone trop long.
*/
static gboolean test_evidence_integrity_task_timeout(
gpointer user_data
)
{
TestEvidenceIntegrityTaskCompletionContext *context =
user_data;
assert(context != NULL);
context->timed_out = TRUE;
g_main_loop_quit(
context->main_loop
);
return G_SOURCE_REMOVE;
}
/**
* @brief Initialise un contexte d'attente.
*/
static TestEvidenceIntegrityTaskCompletionContext
test_evidence_integrity_task_completion_context_create(void)
{
TestEvidenceIntegrityTaskCompletionContext context =
{0};
context.main_loop =
g_main_loop_new(
NULL,
FALSE
);
assert(context.main_loop != NULL);
context.main_thread =
g_thread_self();
return context;
}
/**
* @brief Libère un contexte d'attente.
*/
static void test_evidence_integrity_task_completion_context_clear(
TestEvidenceIntegrityTaskCompletionContext *context
)
{
assert(context != NULL);
assert(context->main_loop != NULL);
g_main_loop_unref(
context->main_loop
);
context->main_loop = NULL;
context->main_thread = NULL;
context->callback_thread = NULL;
}
/**
* @brief Attend la fin d'une tâche avec un délai maximal.
*/
static void test_evidence_integrity_task_wait(
TestEvidenceIntegrityTaskCompletionContext *context
)
{
guint timeout_source_id = 0;
assert(context != NULL);
assert(context->main_loop != NULL);
timeout_source_id =
g_timeout_add_seconds(
TEST_EVIDENCE_INTEGRITY_TASK_TIMEOUT_SECONDS,
test_evidence_integrity_task_timeout,
context
);
assert(timeout_source_id != 0);
g_main_loop_run(
context->main_loop
);
if (!context->timed_out)
{
assert(
g_source_remove(
timeout_source_id
)
);
}
assert(!context->timed_out);
assert(context->completed);
assert(context->completion_count == 1);
/*
* GTask rappelle le callback sur le contexte principal ayant
* démarré la tâche.
*/
assert(
context->callback_thread ==
context->main_thread
);
}
/**
* @brief Crée une enquête temporaire minimale.
*/
static TestEvidenceIntegrityTaskFixture
test_evidence_integrity_task_fixture_create(void)
{
TestEvidenceIntegrityTaskFixture fixture =
{0};
GError *error =
NULL;
fixture.root_directory =
g_dir_make_tmp(
"labfy-evidence-integrity-task-test-XXXXXX",
&error
);
assert(fixture.root_directory != NULL);
assert(error == NULL);
fixture.evidence_directory =
g_build_filename(
fixture.root_directory,
"01_Preuves_Originales",
NULL
);
fixture.documents_directory =
g_build_filename(
fixture.evidence_directory,
"Documents",
NULL
);
assert(fixture.evidence_directory != NULL);
assert(fixture.documents_directory != NULL);
assert(
g_mkdir(
fixture.evidence_directory,
0700
) == 0
);
assert(
g_mkdir(
fixture.documents_directory,
0700
) == 0
);
fixture.task_manager =
task_manager_new();
assert(fixture.task_manager != NULL);
return fixture;
}
/**
* @brief Libère une fixture dont les fichiers ont déjà été supprimés.
*/
static void test_evidence_integrity_task_fixture_clear(
TestEvidenceIntegrityTaskFixture *fixture
)
{
assert(fixture != NULL);
task_manager_free(
fixture->task_manager
);
assert(
g_rmdir(
fixture->documents_directory
) == 0
);
assert(
g_rmdir(
fixture->evidence_directory
) == 0
);
assert(
g_rmdir(
fixture->root_directory
) == 0
);
g_free(
fixture->documents_directory
);
g_free(
fixture->evidence_directory
);
g_free(
fixture->root_directory
);
fixture->task_manager = NULL;
fixture->documents_directory = NULL;
fixture->evidence_directory = NULL;
fixture->root_directory = NULL;
}
/**
* @brief Construit un chemin absolu dans Documents.
*/
static char *test_evidence_integrity_task_build_file_path(
const TestEvidenceIntegrityTaskFixture *fixture,
const char *file_name
)
{
assert(fixture != NULL);
assert(fixture->documents_directory != NULL);
assert(file_name != NULL);
return g_build_filename(
fixture->documents_directory,
file_name,
NULL
);
}
/**
* @brief Construit le chemin relatif enregistré dans SQLite.
*/
static char *test_evidence_integrity_task_build_relative_path(
const char *file_name
)
{
assert(file_name != NULL);
return g_build_filename(
"01_Preuves_Originales",
"Documents",
file_name,
NULL
);
}
/**
* @brief Démarre une tâche avec un callback de test.
*/
static BackgroundTask *test_evidence_integrity_task_start(
TestEvidenceIntegrityTaskFixture *fixture,
const EvidenceIntegrityTaskRequest *request,
TestEvidenceIntegrityTaskCompletionContext *completion_context
)
{
BackgroundTask *task =
NULL;
GError *error =
NULL;
assert(fixture != NULL);
assert(fixture->task_manager != NULL);
assert(request != NULL);
assert(completion_context != NULL);
task =
evidence_integrity_task_start(
fixture->task_manager,
request,
test_evidence_integrity_task_completed,
completion_context,
NULL,
&error
);
assert(task != NULL);
assert(error == NULL);
assert(
task_manager_get_count(
fixture->task_manager
) == 1
);
return task;
}
/**
* @brief Vérifie l'état final et retourne le résultat emprunté.
*/
static const EvidenceIntegrityVerificationResult *
test_evidence_integrity_task_assert_completed(
BackgroundTask *task,
EvidenceIntegrityStatus expected_status
)
{
const EvidenceIntegrityVerificationResult *result =
NULL;
GError *error =
NULL;
assert(task != NULL);
assert(
background_task_get_state(
task
) ==
BACKGROUND_TASK_STATE_COMPLETED
);
assert(
background_task_get_progress(
task
) == 1.0
);
error =
background_task_dup_error(
task
);
assert(error == NULL);
result =
background_task_get_result(
task
);
assert(result != NULL);
assert(
evidence_integrity_verification_result_get_status(
result
) ==
expected_status
);
return result;
}
/**
* @brief Vérifie le refus des arguments invalides.
*/
static void test_evidence_integrity_task_invalid_arguments(void)
{
EvidenceIntegrityTaskRequest request =
{0};
BackgroundTask *task =
NULL;
TaskManager *task_manager =
NULL;
GError *error =
NULL;
task =
evidence_integrity_task_start(
NULL,
&request,
NULL,
NULL,
NULL,
&error
);
assert(task == NULL);
assert(error != NULL);
assert(
error->domain ==
EVIDENCE_INTEGRITY_TASK_ERROR
);
assert(
error->code ==
EVIDENCE_INTEGRITY_TASK_ERROR_INVALID_ARGUMENT
);
g_clear_error(
&error
);
task_manager =
task_manager_new();
assert(task_manager != NULL);
task =
evidence_integrity_task_start(
task_manager,
&request,
NULL,
NULL,
NULL,
&error
);
assert(task == NULL);
assert(error != NULL);
assert(
error->code ==
EVIDENCE_INTEGRITY_TASK_ERROR_INVALID_ARGUMENT
);
assert(
task_manager_get_count(
task_manager
) == 0
);
g_clear_error(
&error
);
task_manager_free(
task_manager
);
}
/**
* @brief Vérifie une preuve intacte.
*/
static void test_evidence_integrity_task_valid(void)
{
TestEvidenceIntegrityTaskFixture fixture =
test_evidence_integrity_task_fixture_create();
TestEvidenceIntegrityTaskCompletionContext completion_context =
test_evidence_integrity_task_completion_context_create();
EvidenceIntegrityTaskRequest request =
{0};
BackgroundTask *task =
NULL;
const EvidenceIntegrityVerificationResult *result =
NULL;
char *file_path =
NULL;
char *relative_path =
NULL;
GError *error =
NULL;
file_path =
test_evidence_integrity_task_build_file_path(
&fixture,
"intact.txt"
);
relative_path =
test_evidence_integrity_task_build_relative_path(
"intact.txt"
);
assert(file_path != NULL);
assert(relative_path != NULL);
assert(
g_file_set_contents(
file_path,
"abc",
3,
&error
)
);
assert(error == NULL);
request.investigation_root_path =
fixture.root_directory;
request.relative_path =
relative_path;
request.expected_sha256 =
TEST_SHA256_ABC;
task =
test_evidence_integrity_task_start(
&fixture,
&request,
&completion_context
);
test_evidence_integrity_task_wait(
&completion_context
);
result =
test_evidence_integrity_task_assert_completed(
task,
EVIDENCE_INTEGRITY_STATUS_VALID
);
assert(
strcmp(
evidence_integrity_verification_result_get_computed_sha256(
result
),
TEST_SHA256_ABC
) == 0
);
assert(
evidence_integrity_verification_result_get_size_bytes(
result
) == 3
);
assert(
background_task_get_title(
task
) != NULL
);
background_task_unref(
task
);
assert(
g_remove(
file_path
) == 0
);
g_free(
relative_path
);
g_free(
file_path
);
test_evidence_integrity_task_completion_context_clear(
&completion_context
);
test_evidence_integrity_task_fixture_clear(
&fixture
);
}
/**
* @brief Vérifie qu'une empreinte différente produit MODIFIED.
*/
static void test_evidence_integrity_task_modified(void)
{
TestEvidenceIntegrityTaskFixture fixture =
test_evidence_integrity_task_fixture_create();
TestEvidenceIntegrityTaskCompletionContext completion_context =
test_evidence_integrity_task_completion_context_create();
EvidenceIntegrityTaskRequest request =
{0};
BackgroundTask *task =
NULL;
char *file_path =
NULL;
char *relative_path =
NULL;
GError *error =
NULL;
file_path =
test_evidence_integrity_task_build_file_path(
&fixture,
"modified.txt"
);
relative_path =
test_evidence_integrity_task_build_relative_path(
"modified.txt"
);
assert(file_path != NULL);
assert(relative_path != NULL);
assert(
g_file_set_contents(
file_path,
"abc",
3,
&error
)
);
assert(error == NULL);
request.investigation_root_path =
fixture.root_directory;
request.relative_path =
relative_path;
request.expected_sha256 =
TEST_SHA256_EMPTY;
task =
test_evidence_integrity_task_start(
&fixture,
&request,
&completion_context
);
test_evidence_integrity_task_wait(
&completion_context
);
test_evidence_integrity_task_assert_completed(
task,
EVIDENCE_INTEGRITY_STATUS_MODIFIED
);
background_task_unref(
task
);
assert(
g_remove(
file_path
) == 0
);
g_free(
relative_path
);
g_free(
file_path
);
test_evidence_integrity_task_completion_context_clear(
&completion_context
);
test_evidence_integrity_task_fixture_clear(
&fixture
);
}
/**
* @brief Vérifie qu'un fichier absent produit MISSING.
*/
static void test_evidence_integrity_task_missing(void)
{
TestEvidenceIntegrityTaskFixture fixture =
test_evidence_integrity_task_fixture_create();
TestEvidenceIntegrityTaskCompletionContext completion_context =
test_evidence_integrity_task_completion_context_create();
EvidenceIntegrityTaskRequest request =
{0};
BackgroundTask *task =
NULL;
char *relative_path =
NULL;
relative_path =
test_evidence_integrity_task_build_relative_path(
"absent.txt"
);
assert(relative_path != NULL);
request.investigation_root_path =
fixture.root_directory;
request.relative_path =
relative_path;
request.expected_sha256 =
TEST_SHA256_ABC;
task =
test_evidence_integrity_task_start(
&fixture,
&request,
&completion_context
);
test_evidence_integrity_task_wait(
&completion_context
);
test_evidence_integrity_task_assert_completed(
task,
EVIDENCE_INTEGRITY_STATUS_MISSING
);
background_task_unref(
task
);
g_free(
relative_path
);
test_evidence_integrity_task_completion_context_clear(
&completion_context
);
test_evidence_integrity_task_fixture_clear(
&fixture
);
}
/**
* @brief Vérifie qu'un dossier produit un résultat métier ERROR.
*/
static void test_evidence_integrity_task_error_result(void)
{
TestEvidenceIntegrityTaskFixture fixture =
test_evidence_integrity_task_fixture_create();
TestEvidenceIntegrityTaskCompletionContext completion_context =
test_evidence_integrity_task_completion_context_create();
EvidenceIntegrityTaskRequest request =
{0};
BackgroundTask *task =
NULL;
request.investigation_root_path =
fixture.root_directory;
request.relative_path =
"01_Preuves_Originales/Documents";
request.expected_sha256 =
TEST_SHA256_ABC;
task =
test_evidence_integrity_task_start(
&fixture,
&request,
&completion_context
);
test_evidence_integrity_task_wait(
&completion_context
);
test_evidence_integrity_task_assert_completed(
task,
EVIDENCE_INTEGRITY_STATUS_ERROR
);
/*
* ERROR est un résultat métier. La BackgroundTask ne doit donc
* pas être marquée FAILED.
*/
assert(
background_task_get_state(
task
) ==
BACKGROUND_TASK_STATE_COMPLETED
);
background_task_unref(
task
);
test_evidence_integrity_task_completion_context_clear(
&completion_context
);
test_evidence_integrity_task_fixture_clear(
&fixture
);
}
/**
* @brief Vérifie que la tâche possède ses propres copies des chaînes.
*/
static void test_evidence_integrity_task_argument_ownership(void)
{
TestEvidenceIntegrityTaskFixture fixture =
test_evidence_integrity_task_fixture_create();
TestEvidenceIntegrityTaskCompletionContext completion_context =
test_evidence_integrity_task_completion_context_create();
EvidenceIntegrityTaskRequest request =
{0};
BackgroundTask *task =
NULL;
char *root_path =
NULL;
char *relative_path =
NULL;
char *expected_sha256 =
NULL;
char *file_path =
NULL;
GError *error =
NULL;
file_path =
test_evidence_integrity_task_build_file_path(
&fixture,
"owned.txt"
);
assert(file_path != NULL);
assert(
g_file_set_contents(
file_path,
"abc",
3,
&error
)
);
assert(error == NULL);
root_path =
g_strdup(
fixture.root_directory
);
relative_path =
test_evidence_integrity_task_build_relative_path(
"owned.txt"
);
expected_sha256 =
g_strdup(
TEST_SHA256_ABC
);
assert(root_path != NULL);
assert(relative_path != NULL);
assert(expected_sha256 != NULL);
request.investigation_root_path =
root_path;
request.relative_path =
relative_path;
request.expected_sha256 =
expected_sha256;
task =
test_evidence_integrity_task_start(
&fixture,
&request,
&completion_context
);
/*
* L'appelant peut libérer immédiatement ses chaînes.
*/
g_free(
expected_sha256
);
g_free(
relative_path
);
g_free(
root_path
);
test_evidence_integrity_task_wait(
&completion_context
);
test_evidence_integrity_task_assert_completed(
task,
EVIDENCE_INTEGRITY_STATUS_VALID
);
background_task_unref(
task
);
assert(
g_remove(
file_path
) == 0
);
g_free(
file_path
);
test_evidence_integrity_task_completion_context_clear(
&completion_context
);
test_evidence_integrity_task_fixture_clear(
&fixture
);
}
/**
* @brief Annule la tâche après le premier bloc lu par FileHash.
*/
static void test_evidence_integrity_task_cancel_after_first_block(
guint64 total_size_bytes,
gpointer user_data
)
{
TestEvidenceIntegrityTaskCancellationContext *context =
user_data;
BackgroundTask *task =
NULL;
assert(context != NULL);
assert(total_size_bytes > 0);
g_mutex_lock(
&context->mutex
);
context->block_count++;
if (context->block_count != 1)
{
g_mutex_unlock(
&context->mutex
);
return;
}
while (!context->task_is_ready)
{
g_cond_wait(
&context->condition,
&context->mutex
);
}
task =
background_task_ref(
context->task
);
g_mutex_unlock(
&context->mutex
);
background_task_cancel(
task
);
background_task_unref(
task
);
}
/**
* @brief Vérifie l'annulation coopérative pendant le calcul.
*/
static void test_evidence_integrity_task_cancelled(void)
{
TestEvidenceIntegrityTaskFixture fixture =
test_evidence_integrity_task_fixture_create();
TestEvidenceIntegrityTaskCompletionContext completion_context =
test_evidence_integrity_task_completion_context_create();
TestEvidenceIntegrityTaskCancellationContext cancellation_context =
{0};
EvidenceIntegrityTaskRequest request =
{0};
BackgroundTask *task =
NULL;
guint8 *file_data =
NULL;
char *file_path =
NULL;
char *relative_path =
NULL;
const gsize file_size =
512U * 1024U;
gsize data_index =
0;
GError *error =
NULL;
file_path =
test_evidence_integrity_task_build_file_path(
&fixture,
"cancelled.bin"
);
relative_path =
test_evidence_integrity_task_build_relative_path(
"cancelled.bin"
);
assert(file_path != NULL);
assert(relative_path != NULL);
file_data =
g_malloc(
file_size
);
assert(file_data != NULL);
for (data_index = 0;
data_index < file_size;
data_index++)
{
file_data[data_index] =
(guint8) (data_index % 251U);
}
assert(
g_file_set_contents(
file_path,
(const char *) file_data,
(gssize) file_size,
&error
)
);
assert(error == NULL);
g_mutex_init(
&cancellation_context.mutex
);
g_cond_init(
&cancellation_context.condition
);
file_hash_test_set_block_hook(
test_evidence_integrity_task_cancel_after_first_block,
&cancellation_context
);
request.investigation_root_path =
fixture.root_directory;
request.relative_path =
relative_path;
/*
* La valeur exacte n'est pas importante : l'annulation intervient
* avant la comparaison finale.
*/
request.expected_sha256 =
TEST_SHA256_ABC;
task =
test_evidence_integrity_task_start(
&fixture,
&request,
&completion_context
);
g_mutex_lock(
&cancellation_context.mutex
);
cancellation_context.task =
task;
cancellation_context.task_is_ready =
TRUE;
g_cond_signal(
&cancellation_context.condition
);
g_mutex_unlock(
&cancellation_context.mutex
);
test_evidence_integrity_task_wait(
&completion_context
);
file_hash_test_set_block_hook(
NULL,
NULL
);
assert(
background_task_get_state(
task
) ==
BACKGROUND_TASK_STATE_CANCELLED
);
assert(
background_task_get_result(
task
) == NULL
);
error =
background_task_dup_error(
task
);
assert(error != NULL);
assert(
g_error_matches(
error,
G_IO_ERROR,
G_IO_ERROR_CANCELLED
)
);
g_clear_error(
&error
);
assert(
cancellation_context.block_count == 1
);
background_task_unref(
task
);
g_cond_clear(
&cancellation_context.condition
);
g_mutex_clear(
&cancellation_context.mutex
);
assert(
g_remove(
file_path
) == 0
);
g_free(
file_data
);
g_free(
relative_path
);
g_free(
file_path
);
test_evidence_integrity_task_completion_context_clear(
&completion_context
);
test_evidence_integrity_task_fixture_clear(
&fixture
);
}
int main(void)
{
test_evidence_integrity_task_invalid_arguments();
test_evidence_integrity_task_valid();
test_evidence_integrity_task_modified();
test_evidence_integrity_task_missing();
test_evidence_integrity_task_error_result();
test_evidence_integrity_task_argument_ownership();
test_evidence_integrity_task_cancelled();
printf(
"EvidenceIntegrityTask : tous les tests sont valides.\n"
);
return 0;
}