diff --git a/Makefile b/Makefile index f426622..83cd829 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ TEST_NODE = tests/test_investigation_node TEST_TREE_MODEL = tests/test_investigation_tree_model TEST_TREE_BUILDER = tests/test_investigation_tree_builder TEST_PROJECT = tests/test_investigation_project +TEST_DATABASE = tests/test_database all: $(TARGET) @@ -60,19 +61,27 @@ $(TEST_TREE_BUILDER): \ $(TEST_PROJECT): \ tests/test_investigation_project.c \ - src/core/investigation_project.c - $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + src/core/investigation_project.c \ + src/database/database.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 + +$(TEST_DATABASE): \ + tests/test_database.c \ + src/database/database.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 test: \ $(TEST_NODE) \ $(TEST_TREE_MODEL) \ $(TEST_TREE_BUILDER) \ - $(TEST_PROJECT) + $(TEST_PROJECT) \ + $(TEST_DATABASE) @echo "Exécution des tests..." @./$(TEST_NODE) @./$(TEST_TREE_MODEL) @./$(TEST_TREE_BUILDER) @./$(TEST_PROJECT) + @./$(TEST_DATABASE) @echo "Tous les tests sont valides." %.o: %.c @@ -86,6 +95,7 @@ clean: $(TEST_NODE) \ $(TEST_TREE_MODEL) \ $(TEST_TREE_BUILDER) \ - @./$(TEST_PROJECT) + $(TEST_PROJECT) \ + $(TEST_DATABASE) .PHONY: clean run test diff --git a/docs/tickets/closed/TICKET-022.md b/docs/tickets/closed/TICKET-022.md new file mode 100644 index 0000000..32b2cb5 --- /dev/null +++ b/docs/tickets/closed/TICKET-022.md @@ -0,0 +1,368 @@ +# Ticket #022 + +## Titre + +Initialiser la base SQLite d'une enquête. + +--- + +## Objectif + +Créer et initialiser correctement le fichier : + +```text +00_BaseDeDonnees/Enquete.sqlite +``` + +avec un premier schéma SQLite versionné. + +La base doit contenir les métadonnées minimales permettant d'identifier +l'enquête et la version du schéma. + +--- + +## Architecture + +```text +InvestigationProject + │ + ▼ +Database + │ + ▼ +SQLite +``` + +`InvestigationProject` orchestre la création de l'enquête. + +Le module `Database` est seul responsable de l'ouverture de SQLite, +de l'exécution du schéma et de la fermeture de la base. + +--- + +## Responsabilités + +### InvestigationProject + +Le module doit : + +- créer l'arborescence ; +- demander au module `Database` d'initialiser `Enquete.sqlite` ; +- considérer la création comme échouée si l'initialisation SQLite échoue ; +- nettoyer toute l'enquête créée en cas d'échec. + +### Database + +Le module doit : + +- ouvrir ou créer le fichier SQLite ; +- démarrer une transaction ; +- créer le schéma initial ; +- insérer les métadonnées ; +- valider la transaction ; +- annuler la transaction en cas d'erreur ; +- fermer proprement la connexion. + +--- + +## Nouveaux fichiers + +```text +include/database/database.h +src/database/database.c +``` + +Éventuellement : + +```text +include/database/schema.h +src/database/schema.c +``` + +si le schéma devient trop volumineux pour rester dans `database.c`. + +Pour ce ticket, un seul module `database.c` est acceptable. + +--- + +## Interface publique attendue + +```c +bool database_initialize( + const char *database_path +); +``` + +La fonction retourne : + +```c +true +``` + +si la base a été correctement initialisée. + +Elle retourne : + +```c +false +``` + +en cas d'erreur. + +--- + +## Schéma initial + +### Table `metadata` + +```sql +CREATE TABLE metadata +( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); +``` + +### Métadonnées obligatoires + +```text +schema_version +application +created_at +investigation_uuid +``` + +Valeurs attendues : + +```text +schema_version = 1 +application = Labfy Investigation +created_at = date UTC ISO 8601 +investigation_uuid = UUID unique +``` + +Exemple : + +```text +2026-07-14T18:42:15Z +``` + +--- + +## Table `investigation` + +Créer également une table minimale représentant l'enquête : + +```sql +CREATE TABLE investigation +( + id INTEGER PRIMARY KEY CHECK (id = 1), + name TEXT NOT NULL, + root_path TEXT NOT NULL, + created_at TEXT NOT NULL +); +``` + +La table contient une seule ligne correspondant à l'enquête courante. + +--- + +## Données nécessaires + +`database_initialize()` doit recevoir suffisamment d'informations pour +initialiser correctement la base. + +L'interface pourra donc évoluer vers : + +```c +bool database_initialize( + const char *database_path, + const char *investigation_name, + const char *investigation_root_path +); +``` + +Cette signature est préférée pour éviter que le module `Database` +reconstruise ou devine des informations métier. + +--- + +## UUID + +L'UUID doit être généré avec GLib : + +```c +g_uuid_string_random() +``` + +La chaîne retournée doit être libérée avec : + +```c +g_free(uuid); +``` + +--- + +## Date de création + +La date doit être produite en UTC avec GLib. + +Format attendu : + +```text +YYYY-MM-DDTHH:MM:SSZ +``` + +La date doit être enregistrée à la fois : + +- dans `metadata.created_at` ; +- dans `investigation.created_at`. + +--- + +## Transaction + +Toute l'initialisation doit se dérouler dans une transaction : + +```sql +BEGIN IMMEDIATE; +``` + +Puis : + +```sql +COMMIT; +``` + +En cas d'erreur : + +```sql +ROLLBACK; +``` + +Une base partiellement initialisée ne doit jamais être considérée comme valide. + +--- + +## Intégration avec InvestigationProject + +`investigation_project_create()` ne doit plus créer un fichier vide avec : + +```c +g_file_set_contents(...) +``` + +Il doit construire le chemin de la base puis appeler : + +```c +database_initialize( + database_path, + investigation_name, + investigation_path +); +``` + +Si l'appel échoue : + +- le fichier SQLite éventuel est supprimé ; +- tous les dossiers créés sont supprimés ; +- `investigation_project_create()` retourne `NULL`. + +--- + +## Validation + +À partir de ce ticket, `investigation_project_validate()` doit toujours +vérifier la présence du fichier SQLite, mais pas encore son contenu SQL. + +La validation du schéma sera ajoutée dans un prochain ticket dédié. + +--- + +## Hors périmètre + +Ce ticket ne doit pas : + +- créer les tables Preuves ; +- créer les tables Entites ; +- créer les relations ; +- gérer les migrations ; +- ouvrir une enquête existante ; +- modifier GTK ; +- exposer directement `sqlite3 *` hors du module Database. + +--- + +## Contraintes techniques + +- C17 ; +- SQLite3 ; +- GLib ; +- aucune dépendance GTK ; +- aucun état global ; +- requêtes SQL centralisées dans `database.c` ; +- fermeture garantie de la connexion ; +- transaction obligatoire ; +- documentation Doxygen ; +- compilation sans warning. + +--- + +## Tests + +Créer : + +```text +tests/test_database.c +``` + +Le test doit : + +- créer un dossier temporaire ; +- initialiser une base SQLite ; +- vérifier que le fichier existe ; +- ouvrir la base en lecture ; +- vérifier la présence de la table `metadata` ; +- vérifier la présence de la table `investigation` ; +- vérifier `schema_version = 1` ; +- vérifier `application = Labfy Investigation` ; +- vérifier que `created_at` n'est pas vide ; +- vérifier que l'UUID n'est pas vide ; +- vérifier la ligne unique de la table `investigation` ; +- vérifier le nom et le chemin racine ; +- vérifier qu'une initialisation sur un chemin invalide échoue ; +- nettoyer complètement les fichiers temporaires. + +Faire également évoluer : + +```text +tests/test_investigation_project.c +``` + +pour vérifier que la base créée n'est plus vide. + +--- + +## Critères d'acceptation + +- [ ] Le projet compile sans warning. +- [ ] `make test` reste entièrement valide. +- [ ] `Enquete.sqlite` est une vraie base SQLite. +- [ ] La table `metadata` existe. +- [ ] La table `investigation` existe. +- [ ] `schema_version` vaut `1`. +- [ ] Une date UTC est enregistrée. +- [ ] Un UUID est généré. +- [ ] L'enquête est enregistrée dans la base. +- [ ] L'initialisation est transactionnelle. +- [ ] Toute erreur provoque un nettoyage complet. +- [ ] Aucun type `sqlite3 *` n'est exposé publiquement. +- [ ] Aucune dépendance GTK. + +--- + +## Commit attendu + +```text +feat(database): initialize investigation database +``` diff --git a/include/database/database.h b/include/database/database.h new file mode 100644 index 0000000..c254658 --- /dev/null +++ b/include/database/database.h @@ -0,0 +1,46 @@ +/****************************************************************************** + * @file database.h + * @brief Interface publique d'initialisation de la base SQLite d'une enquête. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_DATABASE_H +#define LABFY_INVESTIGATION_DATABASE_H + +#include + +/** + * @brief Initialise la base SQLite d'une enquête. + * + * La fonction crée ou ouvre le fichier SQLite indiqué, puis initialise + * transactionnellement le schéma minimal de l'enquête. + * + * Les informations suivantes sont enregistrées : + * + * - version du schéma ; + * - nom de l'application ; + * - date de création UTC ; + * - UUID de l'enquête ; + * - nom de l'enquête ; + * - chemin racine de l'enquête. + * + * Aucun handle sqlite3 n'est exposé au code appelant. + * + * @param database_path + * Chemin complet du fichier Enquete.sqlite. + * + * @param investigation_name + * Nom de l'enquête. + * + * @param investigation_root_path + * Chemin complet du dossier racine de l'enquête. + * + * @return true si la base a été correctement initialisée, + * sinon false. + */ +bool database_initialize( + const char *database_path, + const char *investigation_name, + const char *investigation_root_path +); + +#endif diff --git a/labfy-investigation b/labfy-investigation index d5a654e..93e2c01 100755 Binary files a/labfy-investigation and b/labfy-investigation differ diff --git a/src/core/investigation_project.c b/src/core/investigation_project.c index fc04986..e70fa0b 100644 --- a/src/core/investigation_project.c +++ b/src/core/investigation_project.c @@ -4,6 +4,7 @@ ******************************************************************************/ #include "core/investigation_project.h" +#include "database/database.h" #include #include @@ -328,7 +329,6 @@ char *investigation_project_create( char *database_path = NULL; GPtrArray *created_paths = NULL; - GError *error = NULL; if (!investigation_project_validate_create_parameters( parent_directory, @@ -460,29 +460,12 @@ char *investigation_project_create( return NULL; } - /* - * Pour ce ticket, on crée seulement un fichier vide. - * - * Le schéma SQLite sera ajouté dans un prochain ticket. - */ - if (!g_file_set_contents( + if (!database_initialize( database_path, - "", - 0, - &error + investigation_name, + investigation_path )) { - if (error != NULL) - { - g_warning( - "Impossible de créer '%s' : %s", - database_path, - error->message - ); - - g_clear_error(&error); - } - g_free(database_path); investigation_project_cleanup_created_paths( diff --git a/src/database/database.c b/src/database/database.c new file mode 100644 index 0000000..af750a8 --- /dev/null +++ b/src/database/database.c @@ -0,0 +1,621 @@ +/****************************************************************************** + * @file database.c + * @brief Initialisation de la base SQLite d'une enquête. + ******************************************************************************/ + +#include "database/database.h" + +#include +#include + +/** + * @brief Version actuelle du schéma SQLite. + */ +#define DATABASE_SCHEMA_VERSION "1" + +/** + * @brief Nom de l'application enregistré dans les métadonnées. + */ +#define DATABASE_APPLICATION_NAME "Labfy Investigation" + +/** + * @brief Crée la table contenant les métadonnées de l'enquête. + */ +static const char *const database_create_metadata_table_sql = + "CREATE TABLE metadata" + "(" + " key TEXT PRIMARY KEY," + " value TEXT NOT NULL" + ");"; + +/** + * @brief Crée la table représentant l'enquête courante. + */ +static const char *const database_create_investigation_table_sql = + "CREATE TABLE investigation" + "(" + " id INTEGER PRIMARY KEY CHECK (id = 1)," + " name TEXT NOT NULL," + " root_path TEXT NOT NULL," + " created_at TEXT NOT NULL" + ");"; + +/** + * @brief Requête d'insertion d'une métadonnée. + */ +static const char *const database_insert_metadata_sql = + "INSERT INTO metadata (key, value) VALUES (?, ?);"; + +/** + * @brief Requête d'insertion de l'enquête. + */ +static const char *const database_insert_investigation_sql = + "INSERT INTO investigation" + "(" + " id," + " name," + " root_path," + " created_at" + ")" + "VALUES" + "(" + " 1," + " ?," + " ?," + " ?" + ");"; + +/** + * @brief Vérifie les paramètres publics de l'initialisation. + */ +static bool database_validate_initialize_parameters( + const char *database_path, + const char *investigation_name, + const char *investigation_root_path +) +{ + if (database_path == NULL || database_path[0] == '\0') + { + return false; + } + + if (investigation_name == NULL || investigation_name[0] == '\0') + { + return false; + } + + if (investigation_root_path == NULL || + investigation_root_path[0] == '\0') + { + return false; + } + + return true; +} + +/** + * @brief Exécute une requête SQL statique. + * + * Cette fonction convient uniquement aux requêtes qui ne contiennent + * aucune donnée provenant de l'utilisateur. + */ +static bool database_execute_sql( + sqlite3 *database, + const char *sql +) +{ + char *error_message = NULL; + int result = SQLITE_ERROR; + + if (database == NULL || sql == NULL) + { + return false; + } + + result = sqlite3_exec( + database, + sql, + NULL, + NULL, + &error_message + ); + + if (result != SQLITE_OK) + { + g_warning( + "Erreur SQLite : %s", + error_message != NULL + ? error_message + : sqlite3_errmsg(database) + ); + + sqlite3_free(error_message); + + return false; + } + + sqlite3_free(error_message); + + return true; +} + +/** + * @brief Insère une paire clé-valeur dans la table metadata. + */ +static bool database_insert_metadata( + sqlite3 *database, + sqlite3_stmt *statement, + const char *key, + const char *value +) +{ + int result = SQLITE_ERROR; + + if (database == NULL || + statement == NULL || + key == NULL || + value == NULL) + { + return false; + } + + result = sqlite3_bind_text( + statement, + 1, + key, + -1, + SQLITE_TRANSIENT + ); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible de lier la clé '%s' : %s", + key, + sqlite3_errmsg(database) + ); + + return false; + } + + result = sqlite3_bind_text( + statement, + 2, + value, + -1, + SQLITE_TRANSIENT + ); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible de lier la valeur de '%s' : %s", + key, + sqlite3_errmsg(database) + ); + + return false; + } + + result = sqlite3_step(statement); + + if (result != SQLITE_DONE) + { + g_warning( + "Impossible d'insérer la métadonnée '%s' : %s", + key, + sqlite3_errmsg(database) + ); + + return false; + } + + result = sqlite3_reset(statement); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible de réinitialiser la requête metadata : %s", + sqlite3_errmsg(database) + ); + + return false; + } + + result = sqlite3_clear_bindings(statement); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible d'effacer les paramètres metadata : %s", + sqlite3_errmsg(database) + ); + + return false; + } + + return true; +} + +/** + * @brief Insère toutes les métadonnées obligatoires. + */ +static bool database_insert_all_metadata( + sqlite3 *database, + const char *created_at, + const char *investigation_uuid +) +{ + sqlite3_stmt *statement = NULL; + int result = SQLITE_ERROR; + bool success = false; + + if (database == NULL || + created_at == NULL || + investigation_uuid == NULL) + { + return false; + } + + result = sqlite3_prepare_v2( + database, + database_insert_metadata_sql, + -1, + &statement, + NULL + ); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible de préparer l'insertion des métadonnées : %s", + sqlite3_errmsg(database) + ); + + return false; + } + + success = + database_insert_metadata( + database, + statement, + "schema_version", + DATABASE_SCHEMA_VERSION + ) && + database_insert_metadata( + database, + statement, + "application", + DATABASE_APPLICATION_NAME + ) && + database_insert_metadata( + database, + statement, + "created_at", + created_at + ) && + database_insert_metadata( + database, + statement, + "investigation_uuid", + investigation_uuid + ); + + if (sqlite3_finalize(statement) != SQLITE_OK) + { + g_warning( + "Impossible de finaliser la requête metadata : %s", + sqlite3_errmsg(database) + ); + + return false; + } + + return success; +} + +/** + * @brief Insère la ligne représentant l'enquête courante. + */ +static bool database_insert_investigation( + sqlite3 *database, + const char *investigation_name, + const char *investigation_root_path, + const char *created_at +) +{ + sqlite3_stmt *statement = NULL; + int result = SQLITE_ERROR; + bool success = false; + + if (database == NULL || + investigation_name == NULL || + investigation_root_path == NULL || + created_at == NULL) + { + return false; + } + + result = sqlite3_prepare_v2( + database, + database_insert_investigation_sql, + -1, + &statement, + NULL + ); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible de préparer l'insertion de l'enquête : %s", + sqlite3_errmsg(database) + ); + + return false; + } + + result = sqlite3_bind_text( + statement, + 1, + investigation_name, + -1, + SQLITE_TRANSIENT + ); + + if (result != SQLITE_OK) + { + goto cleanup; + } + + result = sqlite3_bind_text( + statement, + 2, + investigation_root_path, + -1, + SQLITE_TRANSIENT + ); + + if (result != SQLITE_OK) + { + goto cleanup; + } + + result = sqlite3_bind_text( + statement, + 3, + created_at, + -1, + SQLITE_TRANSIENT + ); + + if (result != SQLITE_OK) + { + goto cleanup; + } + + result = sqlite3_step(statement); + + if (result != SQLITE_DONE) + { + goto cleanup; + } + + success = true; + +cleanup: + + if (!success) + { + g_warning( + "Impossible d'insérer l'enquête : %s", + sqlite3_errmsg(database) + ); + } + + if (sqlite3_finalize(statement) != SQLITE_OK) + { + g_warning( + "Impossible de finaliser la requête investigation : %s", + sqlite3_errmsg(database) + ); + + success = false; + } + + return success; +} + +/** + * @brief Produit une date UTC au format ISO 8601 attendu. + * + * @return Une nouvelle chaîne à libérer avec g_free(), ou NULL. + */ +static char *database_create_utc_timestamp(void) +{ + GDateTime *date_time = NULL; + char *timestamp = NULL; + + date_time = g_date_time_new_now_utc(); + + if (date_time == NULL) + { + return NULL; + } + + timestamp = g_date_time_format( + date_time, + "%Y-%m-%dT%H:%M:%SZ" + ); + + g_date_time_unref(date_time); + + return timestamp; +} + +bool database_initialize( + const char *database_path, + const char *investigation_name, + const char *investigation_root_path +) +{ + sqlite3 *database = NULL; + char *created_at = NULL; + char *investigation_uuid = NULL; + + int result = SQLITE_ERROR; + bool transaction_started = false; + bool success = false; + + if (!database_validate_initialize_parameters( + database_path, + investigation_name, + investigation_root_path + )) + { + return false; + } + + created_at = database_create_utc_timestamp(); + + if (created_at == NULL) + { + return false; + } + + investigation_uuid = g_uuid_string_random(); + + if (investigation_uuid == NULL) + { + g_free(created_at); + return false; + } + + result = sqlite3_open_v2( + database_path, + &database, + SQLITE_OPEN_READWRITE | + SQLITE_OPEN_CREATE | + SQLITE_OPEN_PRIVATECACHE, + NULL + ); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible d'ouvrir la base '%s' : %s", + database_path, + database != NULL + ? sqlite3_errmsg(database) + : sqlite3_errstr(result) + ); + + goto cleanup; + } + + if (!database_execute_sql( + database, + "PRAGMA foreign_keys = ON;" + )) + { + goto cleanup; + } + + if (!database_execute_sql( + database, + "BEGIN IMMEDIATE;" + )) + { + goto cleanup; + } + + transaction_started = true; + + if (!database_execute_sql( + database, + database_create_metadata_table_sql + )) + { + goto rollback; + } + + if (!database_execute_sql( + database, + database_create_investigation_table_sql + )) + { + goto rollback; + } + + if (!database_insert_all_metadata( + database, + created_at, + investigation_uuid + )) + { + goto rollback; + } + + if (!database_insert_investigation( + database, + investigation_name, + investigation_root_path, + created_at + )) + { + goto rollback; + } + + if (!database_execute_sql( + database, + "COMMIT;" + )) + { + goto rollback; + } + + transaction_started = false; + success = true; + + goto cleanup; + +rollback: + + if (transaction_started) + { + if (!database_execute_sql( + database, + "ROLLBACK;" + )) + { + g_warning( + "Échec du rollback de la base '%s'.", + database_path + ); + } + + transaction_started = false; + } + +cleanup: + + if (database != NULL) + { + result = sqlite3_close(database); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible de fermer proprement la base '%s' : %s", + database_path, + sqlite3_errstr(result) + ); + + success = false; + } + } + + g_free(investigation_uuid); + g_free(created_at); + + return success; +} diff --git a/tests/test_database b/tests/test_database new file mode 100755 index 0000000..bad3911 Binary files /dev/null and b/tests/test_database differ diff --git a/tests/test_database.c b/tests/test_database.c new file mode 100644 index 0000000..b227363 --- /dev/null +++ b/tests/test_database.c @@ -0,0 +1,339 @@ +/****************************************************************************** + * @file test_database.c + * @brief Tests d'intégration du module Database. + ******************************************************************************/ + +#include "database/database.h" + +#include +#include +#include + +#include +#include +#include + +/** + * @brief Lit une valeur unique retournée par une requête SQL. + * + * @param database Base SQLite ouverte. + * @param sql Requête retournant une seule colonne et une seule ligne. + * + * @return Une nouvelle chaîne à libérer avec g_free(), ou NULL. + */ +static char *test_database_read_single_text( + sqlite3 *database, + const char *sql +) +{ + sqlite3_stmt *statement = NULL; + const unsigned char *text = NULL; + char *result_text = NULL; + int result = SQLITE_ERROR; + + assert(database != NULL); + assert(sql != NULL); + + result = sqlite3_prepare_v2( + database, + sql, + -1, + &statement, + NULL + ); + + assert(result == SQLITE_OK); + assert(statement != NULL); + + result = sqlite3_step(statement); + + assert(result == SQLITE_ROW); + + text = sqlite3_column_text( + statement, + 0 + ); + + assert(text != NULL); + + result_text = g_strdup( + (const char *)text + ); + + assert(result_text != NULL); + + result = sqlite3_finalize(statement); + + assert(result == SQLITE_OK); + + return result_text; +} + +/** + * @brief Vérifie qu'une table existe. + */ +static void test_database_assert_table_exists( + sqlite3 *database, + const char *table_name +) +{ + sqlite3_stmt *statement = NULL; + int result = SQLITE_ERROR; + + assert(database != NULL); + assert(table_name != NULL); + + result = sqlite3_prepare_v2( + database, + "SELECT COUNT(*) " + "FROM sqlite_master " + "WHERE type = 'table' AND name = ?;", + -1, + &statement, + NULL + ); + + assert(result == SQLITE_OK); + assert(statement != NULL); + + result = sqlite3_bind_text( + statement, + 1, + table_name, + -1, + SQLITE_TRANSIENT + ); + + assert(result == SQLITE_OK); + + result = sqlite3_step(statement); + + assert(result == SQLITE_ROW); + assert(sqlite3_column_int(statement, 0) == 1); + + result = sqlite3_finalize(statement); + + assert(result == SQLITE_OK); +} + +/** + * @brief Vérifie l'initialisation complète d'une base. + */ +static void test_database_initialize_valid_database(void) +{ + char *temporary_directory = NULL; + char *database_path = NULL; + + char *schema_version = NULL; + char *application_name = NULL; + char *created_at = NULL; + char *investigation_uuid = NULL; + char *investigation_name = NULL; + char *investigation_root_path = NULL; + char *investigation_created_at = NULL; + + sqlite3 *database = NULL; + GError *error = NULL; + int result = SQLITE_ERROR; + + temporary_directory = g_dir_make_tmp( + "labfy-database-test-XXXXXX", + &error + ); + + assert(temporary_directory != NULL); + assert(error == NULL); + + database_path = g_build_filename( + temporary_directory, + "Enquete.sqlite", + NULL + ); + + assert(database_path != NULL); + + assert( + database_initialize( + database_path, + "Enquete_Test", + temporary_directory + ) + ); + + assert( + g_file_test( + database_path, + G_FILE_TEST_IS_REGULAR + ) + ); + + result = sqlite3_open_v2( + database_path, + &database, + SQLITE_OPEN_READONLY, + NULL + ); + + assert(result == SQLITE_OK); + assert(database != NULL); + + test_database_assert_table_exists( + database, + "metadata" + ); + + test_database_assert_table_exists( + database, + "investigation" + ); + + schema_version = test_database_read_single_text( + database, + "SELECT value FROM metadata " + "WHERE key = 'schema_version';" + ); + + application_name = test_database_read_single_text( + database, + "SELECT value FROM metadata " + "WHERE key = 'application';" + ); + + created_at = test_database_read_single_text( + database, + "SELECT value FROM metadata " + "WHERE key = 'created_at';" + ); + + investigation_uuid = test_database_read_single_text( + database, + "SELECT value FROM metadata " + "WHERE key = 'investigation_uuid';" + ); + + investigation_name = test_database_read_single_text( + database, + "SELECT name FROM investigation " + "WHERE id = 1;" + ); + + investigation_root_path = test_database_read_single_text( + database, + "SELECT root_path FROM investigation " + "WHERE id = 1;" + ); + + investigation_created_at = test_database_read_single_text( + database, + "SELECT created_at FROM investigation " + "WHERE id = 1;" + ); + + assert(strcmp(schema_version, "1") == 0); + assert(strcmp(application_name, "Labfy Investigation") == 0); + + assert(created_at[0] != '\0'); + assert(investigation_uuid[0] != '\0'); + + assert(strcmp(investigation_name, "Enquete_Test") == 0); + assert(strcmp(investigation_root_path, temporary_directory) == 0); + assert(strcmp(investigation_created_at, created_at) == 0); + + result = sqlite3_close(database); + + assert(result == SQLITE_OK); + + assert(g_remove(database_path) == 0); + assert(g_rmdir(temporary_directory) == 0); + + g_free(investigation_created_at); + g_free(investigation_root_path); + g_free(investigation_name); + g_free(investigation_uuid); + g_free(created_at); + g_free(application_name); + g_free(schema_version); + g_free(database_path); + g_free(temporary_directory); +} + +/** + * @brief Vérifie le refus des paramètres invalides. + */ +static void test_database_initialize_invalid_parameters(void) +{ + assert( + !database_initialize( + NULL, + "Enquete", + "/tmp/Enquete" + ) + ); + + assert( + !database_initialize( + "", + "Enquete", + "/tmp/Enquete" + ) + ); + + assert( + !database_initialize( + "/tmp/Enquete.sqlite", + NULL, + "/tmp/Enquete" + ) + ); + + assert( + !database_initialize( + "/tmp/Enquete.sqlite", + "", + "/tmp/Enquete" + ) + ); + + assert( + !database_initialize( + "/tmp/Enquete.sqlite", + "Enquete", + NULL + ) + ); + + assert( + !database_initialize( + "/tmp/Enquete.sqlite", + "Enquete", + "" + ) + ); +} + +/** + * @brief Vérifie l'échec sur un chemin dont le parent n'existe pas. + */ +static void test_database_initialize_missing_parent(void) +{ + assert( + !database_initialize( + "/tmp/labfy-missing-parent/database/Enquete.sqlite", + "Enquete", + "/tmp/labfy-missing-parent" + ) + ); +} + +int main(void) +{ + test_database_initialize_valid_database(); + test_database_initialize_invalid_parameters(); + test_database_initialize_missing_parent(); + + printf( + "Database : tous les tests sont valides.\n" + ); + + return 0; +} diff --git a/tests/test_investigation_project b/tests/test_investigation_project index 6fdc14d..6bca42e 100755 Binary files a/tests/test_investigation_project and b/tests/test_investigation_project differ diff --git a/tests/test_investigation_project.c b/tests/test_investigation_project.c index 404620c..ddb8e41 100644 --- a/tests/test_investigation_project.c +++ b/tests/test_investigation_project.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -274,6 +275,43 @@ static void test_create_valid_investigation(void) ) == NULL ); + sqlite3 *database = NULL; + sqlite3_stmt *statement = NULL; + int result = SQLITE_ERROR; + + result = sqlite3_open_v2( + database_path, + &database, + SQLITE_OPEN_READONLY, + NULL + ); + + assert(result == SQLITE_OK); + + result = sqlite3_prepare_v2( + database, + "SELECT COUNT(*) " + "FROM sqlite_master " + "WHERE type='table' " + "AND name='metadata';", + -1, + &statement, + NULL + ); + + assert(result == SQLITE_OK); + + result = sqlite3_step(statement); + + assert(result == SQLITE_ROW); + assert(sqlite3_column_int(statement, 0) == 1); + + result = sqlite3_finalize(statement); + assert(result == SQLITE_OK); + + result = sqlite3_close(database); + assert(result == SQLITE_OK); + /* * Nettoyage complet. */