feat: persist evidence records
This commit is contained in:
parent
7d6f7f672f
commit
c7bf0da4e6
24 changed files with 4940 additions and 53 deletions
18
Makefile
18
Makefile
|
|
@ -43,6 +43,7 @@ TEST_INVESTIGATION_RECORD = tests/test_investigation_record
|
||||||
TEST_EVIDENCE_RECORD := tests/test_evidence_record
|
TEST_EVIDENCE_RECORD := tests/test_evidence_record
|
||||||
TEST_EVIDENCE_RECORD := tests/test_evidence_record
|
TEST_EVIDENCE_RECORD := tests/test_evidence_record
|
||||||
TEST_INVESTIGATION_DAO := tests/test_investigation_dao
|
TEST_INVESTIGATION_DAO := tests/test_investigation_dao
|
||||||
|
TEST_EVIDENCE_DAO := tests/test_evidence_dao
|
||||||
TEST_INVESTIGATION_SESSION := tests/test_investigation_session
|
TEST_INVESTIGATION_SESSION := tests/test_investigation_session
|
||||||
TEST_BACKGROUND_TASK := tests/test_background_task
|
TEST_BACKGROUND_TASK := tests/test_background_task
|
||||||
TEST_TASK_MANAGER := tests/test_task_manager
|
TEST_TASK_MANAGER := tests/test_task_manager
|
||||||
|
|
@ -90,7 +91,8 @@ $(TEST_DATABASE): \
|
||||||
src/database/database.c \
|
src/database/database.c \
|
||||||
src/database/transaction.c \
|
src/database/transaction.c \
|
||||||
src/database/statement.c \
|
src/database/statement.c \
|
||||||
src/database/schema.c
|
src/database/schema.c \
|
||||||
|
src/database/error.c
|
||||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||||
|
|
||||||
$(TEST_STATEMENT): \
|
$(TEST_STATEMENT): \
|
||||||
|
|
@ -140,6 +142,17 @@ $(TEST_INVESTIGATION_DAO): \
|
||||||
src/database/error.c
|
src/database/error.c
|
||||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||||
|
|
||||||
|
$(TEST_EVIDENCE_DAO): \
|
||||||
|
tests/test_evidence_dao.c \
|
||||||
|
src/dao/evidence_dao.c \
|
||||||
|
src/models/evidence_record.c \
|
||||||
|
src/database/database.c \
|
||||||
|
src/database/schema.c \
|
||||||
|
src/database/statement.c \
|
||||||
|
src/database/transaction.c \
|
||||||
|
src/database/error.c
|
||||||
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||||
|
|
||||||
$(TEST_INVESTIGATION_SESSION): \
|
$(TEST_INVESTIGATION_SESSION): \
|
||||||
tests/test_investigation_session.c \
|
tests/test_investigation_session.c \
|
||||||
src/core/investigation_session.c \
|
src/core/investigation_session.c \
|
||||||
|
|
@ -211,6 +224,7 @@ test: \
|
||||||
$(TEST_INVESTIGATION_RECORD) \
|
$(TEST_INVESTIGATION_RECORD) \
|
||||||
$(TEST_EVIDENCE_RECORD) \
|
$(TEST_EVIDENCE_RECORD) \
|
||||||
$(TEST_INVESTIGATION_DAO) \
|
$(TEST_INVESTIGATION_DAO) \
|
||||||
|
$(TEST_EVIDENCE_DAO) \
|
||||||
$(TEST_INVESTIGATION_SESSION) \
|
$(TEST_INVESTIGATION_SESSION) \
|
||||||
$(TEST_BACKGROUND_TASK) \
|
$(TEST_BACKGROUND_TASK) \
|
||||||
$(TEST_TASK_MANAGER) \
|
$(TEST_TASK_MANAGER) \
|
||||||
|
|
@ -231,6 +245,7 @@ test: \
|
||||||
@$(TEST_INVESTIGATION_RECORD)
|
@$(TEST_INVESTIGATION_RECORD)
|
||||||
@$(TEST_EVIDENCE_RECORD)
|
@$(TEST_EVIDENCE_RECORD)
|
||||||
@$(TEST_INVESTIGATION_DAO)
|
@$(TEST_INVESTIGATION_DAO)
|
||||||
|
@$(TEST_EVIDENCE_DAO)
|
||||||
@$(TEST_INVESTIGATION_SESSION)
|
@$(TEST_INVESTIGATION_SESSION)
|
||||||
@$(TEST_BACKGROUND_TASK)
|
@$(TEST_BACKGROUND_TASK)
|
||||||
@$(TEST_TASK_MANAGER)
|
@$(TEST_TASK_MANAGER)
|
||||||
|
|
@ -260,6 +275,7 @@ clean:
|
||||||
$(TEST_INVESTIGATION_RECORD) \
|
$(TEST_INVESTIGATION_RECORD) \
|
||||||
$(TEST_EVIDENCE_RECORD) \
|
$(TEST_EVIDENCE_RECORD) \
|
||||||
$(TEST_INVESTIGATION_DAO) \
|
$(TEST_INVESTIGATION_DAO) \
|
||||||
|
$(TEST_EVIDENCE_DAO) \
|
||||||
$(TEST_INVESTIGATION_SESSION) \
|
$(TEST_INVESTIGATION_SESSION) \
|
||||||
$(TEST_BACKGROUND_TASK) \
|
$(TEST_BACKGROUND_TASK) \
|
||||||
$(TEST_TASK_MANAGER) \
|
$(TEST_TASK_MANAGER) \
|
||||||
|
|
|
||||||
105
database/schema_v2.sql
Normal file
105
database/schema_v2.sql
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* Labfy Investigation
|
||||||
|
*
|
||||||
|
* Migration du schéma SQLite V1 vers V2
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Nom du fichier tel qu’il existait avant son import.
|
||||||
|
*
|
||||||
|
* La colonne name existante conserve le nom interne utilisé dans
|
||||||
|
* l’arborescence de l’enquête.
|
||||||
|
*/
|
||||||
|
ALTER TABLE preuves
|
||||||
|
ADD COLUMN original_name TEXT;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Date déclarée de collecte de la preuve.
|
||||||
|
*
|
||||||
|
* Elle est distincte de :
|
||||||
|
*
|
||||||
|
* - file_created_at : date technique connue du fichier ;
|
||||||
|
* - imported_at : date d’import dans l’enquête.
|
||||||
|
*/
|
||||||
|
ALTER TABLE preuves
|
||||||
|
ADD COLUMN collected_at TEXT;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Description textuelle initiale de la provenance.
|
||||||
|
*
|
||||||
|
* Une future évolution pourra relier une preuve à la table sources
|
||||||
|
* avec un identifiant métier.
|
||||||
|
*/
|
||||||
|
ALTER TABLE preuves
|
||||||
|
ADD COLUMN source TEXT;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Correspondance avec EvidenceIntegrityStatus :
|
||||||
|
*
|
||||||
|
* 0 = UNKNOWN
|
||||||
|
* 1 = VALID
|
||||||
|
* 2 = MISSING
|
||||||
|
* 3 = MODIFIED
|
||||||
|
* 4 = ERROR
|
||||||
|
*/
|
||||||
|
ALTER TABLE preuves
|
||||||
|
ADD COLUMN integrity_status INTEGER NOT NULL DEFAULT 0
|
||||||
|
CHECK (
|
||||||
|
integrity_status BETWEEN 0 AND 4
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Les éventuelles preuves V1 utilisent leur ancien nom visible comme
|
||||||
|
* nom original afin de rester lisibles après migration.
|
||||||
|
*/
|
||||||
|
UPDATE preuves
|
||||||
|
SET original_name = name
|
||||||
|
WHERE original_name IS NULL
|
||||||
|
OR length(trim(original_name)) = 0;
|
||||||
|
|
||||||
|
CREATE INDEX idx_preuves_imported_at
|
||||||
|
ON preuves(imported_at);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SQLite ne permet pas d’ajouter directement une contrainte NOT NULL
|
||||||
|
* à une colonne ajoutée lorsque des lignes peuvent déjà exister.
|
||||||
|
*
|
||||||
|
* Ces triggers renforcent donc les insertions et modifications V2.
|
||||||
|
*/
|
||||||
|
CREATE TRIGGER preuves_v2_validate_insert
|
||||||
|
BEFORE INSERT ON preuves
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN
|
||||||
|
NEW.original_name IS NULL
|
||||||
|
OR length(trim(NEW.original_name)) = 0
|
||||||
|
OR NEW.size_bytes IS NULL
|
||||||
|
OR NEW.size_bytes < 0
|
||||||
|
OR NEW.sha256 IS NULL
|
||||||
|
OR length(NEW.sha256) != 64
|
||||||
|
OR NEW.sha256 != lower(NEW.sha256)
|
||||||
|
OR NEW.integrity_status NOT BETWEEN 0 AND 4
|
||||||
|
BEGIN
|
||||||
|
SELECT RAISE(
|
||||||
|
ABORT,
|
||||||
|
'La preuve ne respecte pas les contraintes du schéma V2.'
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER preuves_v2_validate_update
|
||||||
|
BEFORE UPDATE ON preuves
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN
|
||||||
|
NEW.original_name IS NULL
|
||||||
|
OR length(trim(NEW.original_name)) = 0
|
||||||
|
OR NEW.size_bytes IS NULL
|
||||||
|
OR NEW.size_bytes < 0
|
||||||
|
OR NEW.sha256 IS NULL
|
||||||
|
OR length(NEW.sha256) != 64
|
||||||
|
OR NEW.sha256 != lower(NEW.sha256)
|
||||||
|
OR NEW.integrity_status NOT BETWEEN 0 AND 4
|
||||||
|
BEGIN
|
||||||
|
SELECT RAISE(
|
||||||
|
ABORT,
|
||||||
|
'La preuve ne respecte pas les contraintes du schéma V2.'
|
||||||
|
);
|
||||||
|
END;
|
||||||
142
include/dao/evidence_dao.h
Normal file
142
include/dao/evidence_dao.h
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file evidence_dao.h
|
||||||
|
* @brief Persistance des preuves numériques dans SQLite.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_EVIDENCE_DAO_H
|
||||||
|
#define LABFY_INVESTIGATION_EVIDENCE_DAO_H
|
||||||
|
|
||||||
|
#include "database/database.h"
|
||||||
|
#include "models/evidence_record.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Catégories d’erreurs du DAO des preuves.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
EVIDENCE_DAO_ERROR_INVALID_ARGUMENT,
|
||||||
|
EVIDENCE_DAO_ERROR_MEMORY,
|
||||||
|
EVIDENCE_DAO_ERROR_PREPARE,
|
||||||
|
EVIDENCE_DAO_ERROR_BIND,
|
||||||
|
EVIDENCE_DAO_ERROR_EXECUTE,
|
||||||
|
EVIDENCE_DAO_ERROR_CONSTRAINT,
|
||||||
|
EVIDENCE_DAO_ERROR_READ,
|
||||||
|
EVIDENCE_DAO_ERROR_MODEL,
|
||||||
|
EVIDENCE_DAO_ERROR_SCHEMA,
|
||||||
|
EVIDENCE_DAO_ERROR_RANGE
|
||||||
|
} EvidenceDaoError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Domaine d’erreurs du DAO des preuves.
|
||||||
|
*/
|
||||||
|
#define EVIDENCE_DAO_ERROR \
|
||||||
|
evidence_dao_error_quark()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le domaine d’erreurs du DAO des preuves.
|
||||||
|
*/
|
||||||
|
GQuark evidence_dao_error_quark(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief DAO opaque donnant accès aux preuves persistées.
|
||||||
|
*
|
||||||
|
* L’objet emprunte la connexion Database reçue lors de sa création.
|
||||||
|
*/
|
||||||
|
typedef struct EvidenceDao EvidenceDao;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Crée un DAO utilisant une connexion Database existante.
|
||||||
|
*
|
||||||
|
* La connexion est empruntée et doit rester valide pendant toute la durée
|
||||||
|
* de vie du DAO.
|
||||||
|
*
|
||||||
|
* @param database Connexion ouverte.
|
||||||
|
* @param error Adresse recevant une éventuelle erreur.
|
||||||
|
*
|
||||||
|
* @return Nouveau DAO, ou NULL en cas d’échec.
|
||||||
|
*/
|
||||||
|
EvidenceDao *evidence_dao_new(
|
||||||
|
Database *database,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Libère le DAO.
|
||||||
|
*
|
||||||
|
* La connexion Database empruntée n’est pas fermée.
|
||||||
|
* Cette fonction accepte NULL.
|
||||||
|
*
|
||||||
|
* @param evidence_dao DAO à libérer.
|
||||||
|
*/
|
||||||
|
void evidence_dao_free(
|
||||||
|
EvidenceDao *evidence_dao
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Insère une nouvelle preuve.
|
||||||
|
*
|
||||||
|
* Aucun enregistrement existant n’est remplacé ou modifié.
|
||||||
|
*
|
||||||
|
* @param evidence_dao DAO valide.
|
||||||
|
* @param evidence_record Preuve empruntée.
|
||||||
|
* @param error Adresse recevant une éventuelle erreur.
|
||||||
|
*
|
||||||
|
* @return TRUE si l’insertion réussit, sinon FALSE.
|
||||||
|
*/
|
||||||
|
gboolean evidence_dao_insert(
|
||||||
|
EvidenceDao *evidence_dao,
|
||||||
|
const EvidenceRecord *evidence_record,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Recherche une preuve par son identifiant.
|
||||||
|
*
|
||||||
|
* Une preuve absente retourne NULL sans produire d’erreur.
|
||||||
|
*
|
||||||
|
* @param evidence_dao DAO valide.
|
||||||
|
* @param identifier Identifiant UUID recherché.
|
||||||
|
* @param error Adresse recevant une éventuelle erreur.
|
||||||
|
*
|
||||||
|
* @return Nouveau modèle possédé par l’appelant, ou NULL.
|
||||||
|
*/
|
||||||
|
EvidenceRecord *evidence_dao_find_by_identifier(
|
||||||
|
EvidenceDao *evidence_dao,
|
||||||
|
const char *identifier,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Charge toutes les preuves dans un ordre déterministe.
|
||||||
|
*
|
||||||
|
* Le tableau retourné utilise evidence_record_free() comme fonction
|
||||||
|
* de destruction.
|
||||||
|
*
|
||||||
|
* @param evidence_dao DAO valide.
|
||||||
|
* @param error Adresse recevant une éventuelle erreur.
|
||||||
|
*
|
||||||
|
* @return Nouveau GPtrArray, ou NULL en cas d’échec.
|
||||||
|
*/
|
||||||
|
GPtrArray *evidence_dao_list_all(
|
||||||
|
EvidenceDao *evidence_dao,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compte les preuves persistées.
|
||||||
|
*
|
||||||
|
* @param evidence_dao DAO valide.
|
||||||
|
* @param out_count Destination du nombre de preuves.
|
||||||
|
* @param error Adresse recevant une éventuelle erreur.
|
||||||
|
*
|
||||||
|
* @return TRUE si le comptage réussit, sinon FALSE.
|
||||||
|
*/
|
||||||
|
gboolean evidence_dao_count(
|
||||||
|
EvidenceDao *evidence_dao,
|
||||||
|
guint64 *out_count,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -42,6 +42,26 @@ void database_close(
|
||||||
Database *database
|
Database *database
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Met à jour une base ouverte vers la dernière version du schéma.
|
||||||
|
*
|
||||||
|
* La fonction :
|
||||||
|
*
|
||||||
|
* - lit metadata.schema_version ;
|
||||||
|
* - applique chaque migration manquante dans une transaction ;
|
||||||
|
* - met à jour la version uniquement après une migration réussie ;
|
||||||
|
* - ne modifie rien lorsque la base est déjà à jour.
|
||||||
|
*
|
||||||
|
* La fonction refuse une migration lorsqu’une transaction est déjà active.
|
||||||
|
*
|
||||||
|
* @param database Connexion Database ouverte.
|
||||||
|
*
|
||||||
|
* @return true si la base est à jour, sinon false.
|
||||||
|
*/
|
||||||
|
bool database_migrate_to_latest(
|
||||||
|
Database *database
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initialise la base SQLite d'une nouvelle enquête.
|
* @brief Initialise la base SQLite d'une nouvelle enquête.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,27 @@ bool schema_install_v1(
|
||||||
Database *database
|
Database *database
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Installe les modifications du schéma SQLite V2.
|
||||||
|
*
|
||||||
|
* La connexion doit être valide et une transaction doit déjà être active.
|
||||||
|
*
|
||||||
|
* Cette fonction :
|
||||||
|
*
|
||||||
|
* - étend la table preuves ;
|
||||||
|
* - conserve les éventuelles lignes V1 ;
|
||||||
|
* - ajoute les contraintes nécessaires aux nouvelles preuves ;
|
||||||
|
* - ajoute les index V2.
|
||||||
|
*
|
||||||
|
* Elle ne modifie pas elle-même la version enregistrée dans metadata.
|
||||||
|
* Elle ne réalise ni COMMIT ni ROLLBACK.
|
||||||
|
*
|
||||||
|
* @param database Connexion Database ouverte.
|
||||||
|
*
|
||||||
|
* @return true si la migration V2 a été appliquée, sinon false.
|
||||||
|
*/
|
||||||
|
bool schema_install_v2(
|
||||||
|
Database *database
|
||||||
|
);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -186,23 +186,6 @@ bool database_statement_bind_null(
|
||||||
int index
|
int index
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Lie une chaîne de caractères à un paramètre SQL.
|
|
||||||
*
|
|
||||||
* Les indices des paramètres SQLite commencent à 1.
|
|
||||||
*
|
|
||||||
* @param statement Requête préparée.
|
|
||||||
* @param index Indice du paramètre SQL.
|
|
||||||
* @param value Chaîne de caractères à lier.
|
|
||||||
*
|
|
||||||
* @return true en cas de succès, sinon false.
|
|
||||||
*/
|
|
||||||
bool database_statement_bind_text(
|
|
||||||
DatabaseStatement *statement,
|
|
||||||
int index,
|
|
||||||
const char *value
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Finalise une requête préparée et libère ses ressources.
|
* @brief Finalise une requête préparée et libère ses ressources.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -192,6 +192,33 @@ InvestigationSession *investigation_session_open(
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!database_migrate_to_latest(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
database_error_message =
|
||||||
|
database_error_get_message(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
|
if (database_error_message == NULL ||
|
||||||
|
database_error_message[0] == '\0')
|
||||||
|
{
|
||||||
|
database_error_message =
|
||||||
|
"La base de données n’a pas pu être mise à jour.";
|
||||||
|
}
|
||||||
|
|
||||||
|
g_set_error(
|
||||||
|
error,
|
||||||
|
INVESTIGATION_SESSION_ERROR,
|
||||||
|
INVESTIGATION_SESSION_ERROR_DATABASE,
|
||||||
|
"Impossible de mettre à jour le schéma SQLite : %s",
|
||||||
|
database_error_message
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
record = investigation_dao_load(
|
record = investigation_dao_load(
|
||||||
database
|
database
|
||||||
);
|
);
|
||||||
|
|
|
||||||
1482
src/dao/evidence_dao.c
Normal file
1482
src/dao/evidence_dao.c
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -16,7 +16,12 @@
|
||||||
/**
|
/**
|
||||||
* @brief Version actuelle du schéma SQLite.
|
* @brief Version actuelle du schéma SQLite.
|
||||||
*/
|
*/
|
||||||
#define DATABASE_SCHEMA_VERSION "1"
|
#define DATABASE_SCHEMA_VERSION_CURRENT 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Version actuelle sous forme textuelle pour metadata.
|
||||||
|
*/
|
||||||
|
#define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "2"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Nom de l'application enregistré dans les métadonnées.
|
* @brief Nom de l'application enregistré dans les métadonnées.
|
||||||
|
|
@ -225,7 +230,7 @@ static bool database_insert_all_metadata(
|
||||||
database_insert_metadata(
|
database_insert_metadata(
|
||||||
statement,
|
statement,
|
||||||
"schema_version",
|
"schema_version",
|
||||||
DATABASE_SCHEMA_VERSION
|
DATABASE_SCHEMA_VERSION_CURRENT_TEXT
|
||||||
) &&
|
) &&
|
||||||
database_insert_metadata(
|
database_insert_metadata(
|
||||||
statement,
|
statement,
|
||||||
|
|
@ -366,6 +371,22 @@ bool database_get_transaction_active(
|
||||||
return database->transaction_active;
|
return database->transaction_active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Requête de lecture de la version du schéma.
|
||||||
|
*/
|
||||||
|
static const char *const database_select_schema_version_sql =
|
||||||
|
"SELECT value "
|
||||||
|
"FROM metadata "
|
||||||
|
"WHERE key = ?;";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Requête de mise à jour de la version du schéma.
|
||||||
|
*/
|
||||||
|
static const char *const database_update_schema_version_sql =
|
||||||
|
"UPDATE metadata "
|
||||||
|
"SET value = ? "
|
||||||
|
"WHERE key = ?;";
|
||||||
|
|
||||||
void database_set_transaction_active(
|
void database_set_transaction_active(
|
||||||
Database *database,
|
Database *database,
|
||||||
bool transaction_active
|
bool transaction_active
|
||||||
|
|
@ -379,6 +400,274 @@ void database_set_transaction_active(
|
||||||
database->transaction_active = transaction_active;
|
database->transaction_active = transaction_active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lit et valide la version enregistrée dans metadata.
|
||||||
|
*/
|
||||||
|
static bool database_read_schema_version(
|
||||||
|
Database *database,
|
||||||
|
int *out_schema_version
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DatabaseStatement *statement = NULL;
|
||||||
|
DatabaseStatementStepResult step_result;
|
||||||
|
|
||||||
|
char *version_text = NULL;
|
||||||
|
char *end_pointer = NULL;
|
||||||
|
|
||||||
|
gint64 parsed_version = 0;
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
if (database == NULL ||
|
||||||
|
out_schema_version == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_schema_version = 0;
|
||||||
|
|
||||||
|
statement =
|
||||||
|
database_statement_prepare(
|
||||||
|
database,
|
||||||
|
database_select_schema_version_sql
|
||||||
|
);
|
||||||
|
|
||||||
|
if (statement == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database_statement_bind_text(
|
||||||
|
statement,
|
||||||
|
1,
|
||||||
|
"schema_version"
|
||||||
|
))
|
||||||
|
{
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
step_result =
|
||||||
|
database_statement_step(
|
||||||
|
statement
|
||||||
|
);
|
||||||
|
|
||||||
|
if (step_result == DATABASE_STATEMENT_STEP_ERROR)
|
||||||
|
{
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step_result == DATABASE_STATEMENT_STEP_DONE)
|
||||||
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
|
"La version du schéma est absente des métadonnées."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database_statement_column_text(
|
||||||
|
statement,
|
||||||
|
0,
|
||||||
|
&version_text
|
||||||
|
) ||
|
||||||
|
version_text == NULL ||
|
||||||
|
version_text[0] == '\0')
|
||||||
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
|
"La version du schéma est invalide."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed_version =
|
||||||
|
g_ascii_strtoll(
|
||||||
|
version_text,
|
||||||
|
&end_pointer,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
|
if (end_pointer == version_text ||
|
||||||
|
end_pointer == NULL ||
|
||||||
|
end_pointer[0] != '\0' ||
|
||||||
|
parsed_version < 1 ||
|
||||||
|
parsed_version > G_MAXINT)
|
||||||
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
|
"La version du schéma n’est pas un entier valide."
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* La clé metadata est primaire, mais on vérifie malgré tout
|
||||||
|
* que la requête ne retourne aucune seconde ligne.
|
||||||
|
*/
|
||||||
|
step_result =
|
||||||
|
database_statement_step(
|
||||||
|
statement
|
||||||
|
);
|
||||||
|
|
||||||
|
if (step_result != DATABASE_STATEMENT_STEP_DONE)
|
||||||
|
{
|
||||||
|
if (step_result == DATABASE_STATEMENT_STEP_ROW)
|
||||||
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
|
"Plusieurs versions du schéma sont enregistrées."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_schema_version =
|
||||||
|
(int) parsed_version;
|
||||||
|
|
||||||
|
database_clear_error_internal(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
version_text
|
||||||
|
);
|
||||||
|
|
||||||
|
database_statement_finalize(
|
||||||
|
statement
|
||||||
|
);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Met à jour la version enregistrée dans metadata.
|
||||||
|
*/
|
||||||
|
static bool database_update_schema_version(
|
||||||
|
Database *database,
|
||||||
|
const char *version_text
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DatabaseStatement *statement = NULL;
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
if (database == NULL ||
|
||||||
|
version_text == NULL ||
|
||||||
|
version_text[0] == '\0')
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
statement =
|
||||||
|
database_statement_prepare(
|
||||||
|
database,
|
||||||
|
database_update_schema_version_sql
|
||||||
|
);
|
||||||
|
|
||||||
|
if (statement == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
success =
|
||||||
|
database_statement_bind_text(
|
||||||
|
statement,
|
||||||
|
1,
|
||||||
|
version_text
|
||||||
|
) &&
|
||||||
|
database_statement_bind_text(
|
||||||
|
statement,
|
||||||
|
2,
|
||||||
|
"schema_version"
|
||||||
|
) &&
|
||||||
|
database_statement_step(
|
||||||
|
statement
|
||||||
|
) == DATABASE_STATEMENT_STEP_DONE;
|
||||||
|
|
||||||
|
database_statement_finalize(
|
||||||
|
statement
|
||||||
|
);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Applique atomiquement la migration du schéma V1 vers V2.
|
||||||
|
*/
|
||||||
|
static bool database_migrate_v1_to_v2(
|
||||||
|
Database *database
|
||||||
|
)
|
||||||
|
{
|
||||||
|
bool transaction_started = false;
|
||||||
|
|
||||||
|
if (database == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database_transaction_begin(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction_started = true;
|
||||||
|
|
||||||
|
if (!schema_install_v2(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
goto rollback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database_update_schema_version(
|
||||||
|
database,
|
||||||
|
"2"
|
||||||
|
))
|
||||||
|
{
|
||||||
|
goto rollback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database_transaction_commit(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
goto rollback;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction_started = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
rollback:
|
||||||
|
|
||||||
|
if (transaction_started)
|
||||||
|
{
|
||||||
|
if (!database_transaction_rollback(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
g_warning(
|
||||||
|
"Impossible d’annuler la migration SQLite V1 vers V2."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Database *database_open(
|
Database *database_open(
|
||||||
const char *database_path
|
const char *database_path
|
||||||
)
|
)
|
||||||
|
|
@ -445,6 +734,89 @@ Database *database_open(
|
||||||
return database;
|
return database;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool database_migrate_to_latest(
|
||||||
|
Database *database
|
||||||
|
)
|
||||||
|
{
|
||||||
|
int schema_version = 0;
|
||||||
|
|
||||||
|
if (database == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (database_get_transaction_active(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
|
"La migration est impossible pendant une transaction active."
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database_read_schema_version(
|
||||||
|
database,
|
||||||
|
&schema_version
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema_version >
|
||||||
|
DATABASE_SCHEMA_VERSION_CURRENT)
|
||||||
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
|
"La base utilise une version de schéma plus récente "
|
||||||
|
"que cette version de Labfy Investigation."
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (schema_version <
|
||||||
|
DATABASE_SCHEMA_VERSION_CURRENT)
|
||||||
|
{
|
||||||
|
switch (schema_version)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
if (!database_migrate_v1_to_v2(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
schema_version = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
|
"La version du schéma ne possède aucune "
|
||||||
|
"migration prise en charge."
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
database->schema_version =
|
||||||
|
schema_version;
|
||||||
|
|
||||||
|
database_clear_error_internal(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool database_initialize(
|
bool database_initialize(
|
||||||
const char *database_path,
|
const char *database_path,
|
||||||
const char *investigation_name,
|
const char *investigation_name,
|
||||||
|
|
@ -514,6 +886,17 @@ bool database_initialize(
|
||||||
goto rollback;
|
goto rollback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Une nouvelle base reçoit immédiatement toutes les versions du schéma
|
||||||
|
* dans la transaction initiale.
|
||||||
|
*/
|
||||||
|
if (!schema_install_v2(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
goto rollback;
|
||||||
|
}
|
||||||
|
|
||||||
if (!database_insert_all_metadata(
|
if (!database_insert_all_metadata(
|
||||||
database,
|
database,
|
||||||
created_at,
|
created_at,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* @file schema.c
|
* @file schema.c
|
||||||
* @brief Installation du schéma SQLite de Labfy Investigation.
|
* @brief Installation des versions du schéma SQLite.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#include "database/schema.h"
|
#include "database/schema.h"
|
||||||
|
|
@ -11,22 +11,34 @@
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Charge le schéma SQL V1 depuis le fichier du projet.
|
* @brief Charge un fichier SQL du projet.
|
||||||
*
|
*
|
||||||
* @param database Connexion utilisée pour enregistrer une éventuelle erreur.
|
* @param database Connexion recevant les erreurs.
|
||||||
|
* @param schema_path Chemin du fichier SQL.
|
||||||
|
* @param schema_name Nom utilisé dans les diagnostics.
|
||||||
*
|
*
|
||||||
* @return Une nouvelle chaîne terminée par zéro, à libérer avec g_free(),
|
* @return Nouvelle chaîne SQL, ou NULL.
|
||||||
* ou NULL en cas d'échec.
|
|
||||||
*/
|
*/
|
||||||
static char *schema_load_v1_sql(
|
static char *schema_load_sql(
|
||||||
Database *database
|
Database *database,
|
||||||
|
const char *schema_path,
|
||||||
|
const char *schema_name
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
char *schema_sql = NULL;
|
char *schema_sql = NULL;
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
|
||||||
|
if (database == NULL ||
|
||||||
|
schema_path == NULL ||
|
||||||
|
schema_path[0] == '\0' ||
|
||||||
|
schema_name == NULL ||
|
||||||
|
schema_name[0] == '\0')
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (!g_file_get_contents(
|
if (!g_file_get_contents(
|
||||||
"database/schema_v1.sql",
|
schema_path,
|
||||||
&schema_sql,
|
&schema_sql,
|
||||||
NULL,
|
NULL,
|
||||||
&error
|
&error
|
||||||
|
|
@ -37,17 +49,21 @@ static char *schema_load_v1_sql(
|
||||||
DATABASE_ERROR_INVALID_STATE,
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
error != NULL
|
error != NULL
|
||||||
? error->message
|
? error->message
|
||||||
: "Impossible de charger le schéma SQLite V1."
|
: "Impossible de charger le schéma SQLite."
|
||||||
);
|
);
|
||||||
|
|
||||||
g_warning(
|
g_warning(
|
||||||
"Impossible de charger database/schema_v1.sql : %s",
|
"Impossible de charger %s depuis '%s' : %s",
|
||||||
|
schema_name,
|
||||||
|
schema_path,
|
||||||
error != NULL
|
error != NULL
|
||||||
? error->message
|
? error->message
|
||||||
: "erreur inconnue"
|
: "erreur inconnue"
|
||||||
);
|
);
|
||||||
|
|
||||||
g_clear_error(&error);
|
g_clear_error(
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -55,11 +71,19 @@ static char *schema_load_v1_sql(
|
||||||
return schema_sql;
|
return schema_sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool schema_install_v1(
|
/**
|
||||||
Database *database
|
* @brief Exécute le contenu d’un fichier de schéma SQL.
|
||||||
|
*
|
||||||
|
* La transaction reste sous la responsabilité du code appelant.
|
||||||
|
*/
|
||||||
|
static bool schema_execute_file(
|
||||||
|
Database *database,
|
||||||
|
const char *schema_path,
|
||||||
|
const char *schema_name
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
sqlite3 *database_handle = NULL;
|
sqlite3 *database_handle = NULL;
|
||||||
|
|
||||||
char *schema_sql = NULL;
|
char *schema_sql = NULL;
|
||||||
char *error_message = NULL;
|
char *error_message = NULL;
|
||||||
|
|
||||||
|
|
@ -70,9 +94,10 @@ bool schema_install_v1(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
database_handle = database_get_handle(
|
database_handle =
|
||||||
database
|
database_get_handle(
|
||||||
);
|
database
|
||||||
|
);
|
||||||
|
|
||||||
if (database_handle == NULL)
|
if (database_handle == NULL)
|
||||||
{
|
{
|
||||||
|
|
@ -85,24 +110,30 @@ bool schema_install_v1(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
schema_sql = schema_load_v1_sql(
|
schema_sql =
|
||||||
database
|
schema_load_sql(
|
||||||
);
|
database,
|
||||||
|
schema_path,
|
||||||
|
schema_name
|
||||||
|
);
|
||||||
|
|
||||||
if (schema_sql == NULL)
|
if (schema_sql == NULL)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = sqlite3_exec(
|
result =
|
||||||
database_handle,
|
sqlite3_exec(
|
||||||
schema_sql,
|
database_handle,
|
||||||
NULL,
|
schema_sql,
|
||||||
NULL,
|
NULL,
|
||||||
&error_message
|
NULL,
|
||||||
);
|
&error_message
|
||||||
|
);
|
||||||
|
|
||||||
g_free(schema_sql);
|
g_free(
|
||||||
|
schema_sql
|
||||||
|
);
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
if (result != SQLITE_OK)
|
||||||
{
|
{
|
||||||
|
|
@ -115,18 +146,23 @@ bool schema_install_v1(
|
||||||
);
|
);
|
||||||
|
|
||||||
g_warning(
|
g_warning(
|
||||||
"Impossible d'installer le schéma SQLite V1 : %s",
|
"Impossible d’installer %s : %s",
|
||||||
|
schema_name,
|
||||||
error_message != NULL
|
error_message != NULL
|
||||||
? error_message
|
? error_message
|
||||||
: sqlite3_errmsg(database_handle)
|
: sqlite3_errmsg(database_handle)
|
||||||
);
|
);
|
||||||
|
|
||||||
sqlite3_free(error_message);
|
sqlite3_free(
|
||||||
|
error_message
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_free(error_message);
|
sqlite3_free(
|
||||||
|
error_message
|
||||||
|
);
|
||||||
|
|
||||||
database_clear_error_internal(
|
database_clear_error_internal(
|
||||||
database
|
database
|
||||||
|
|
@ -134,3 +170,25 @@ bool schema_install_v1(
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool schema_install_v1(
|
||||||
|
Database *database
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return schema_execute_file(
|
||||||
|
database,
|
||||||
|
"database/schema_v1.sql",
|
||||||
|
"le schéma SQLite V1"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool schema_install_v2(
|
||||||
|
Database *database
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return schema_execute_file(
|
||||||
|
database,
|
||||||
|
"database/schema_v2.sql",
|
||||||
|
"la migration SQLite V2"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -4,6 +4,7 @@
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#include "database/database.h"
|
#include "database/database.h"
|
||||||
|
#include "database/error.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
@ -69,6 +70,183 @@ static char *test_database_read_single_text(
|
||||||
return result_text;
|
return result_text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Exécute une ou plusieurs instructions SQL de test.
|
||||||
|
*/
|
||||||
|
static void test_database_execute_sql(
|
||||||
|
sqlite3 *database,
|
||||||
|
const char *sql
|
||||||
|
)
|
||||||
|
{
|
||||||
|
char *error_message = NULL;
|
||||||
|
int result = SQLITE_ERROR;
|
||||||
|
|
||||||
|
assert(database != NULL);
|
||||||
|
assert(sql != NULL);
|
||||||
|
assert(sql[0] != '\0');
|
||||||
|
|
||||||
|
result = sqlite3_exec(
|
||||||
|
database,
|
||||||
|
sql,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
&error_message
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result != SQLITE_OK)
|
||||||
|
{
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"Erreur SQL de test : %s\n",
|
||||||
|
error_message != NULL
|
||||||
|
? error_message
|
||||||
|
: sqlite3_errmsg(database)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(result == SQLITE_OK);
|
||||||
|
|
||||||
|
sqlite3_free(
|
||||||
|
error_message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Crée une base V1 contenant une ancienne preuve.
|
||||||
|
*
|
||||||
|
* La base est construite directement avec schema_v1.sql afin de tester
|
||||||
|
* le véritable chemin de migration, sans passer par database_initialize()
|
||||||
|
* qui crée désormais directement une base V2.
|
||||||
|
*/
|
||||||
|
static void test_database_create_v1_database(
|
||||||
|
const char *database_path
|
||||||
|
)
|
||||||
|
{
|
||||||
|
sqlite3 *database = NULL;
|
||||||
|
|
||||||
|
char *schema_sql = NULL;
|
||||||
|
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
int result = SQLITE_ERROR;
|
||||||
|
|
||||||
|
assert(database_path != NULL);
|
||||||
|
assert(database_path[0] != '\0');
|
||||||
|
|
||||||
|
result = sqlite3_open_v2(
|
||||||
|
database_path,
|
||||||
|
&database,
|
||||||
|
SQLITE_OPEN_READWRITE |
|
||||||
|
SQLITE_OPEN_CREATE,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(result == SQLITE_OK);
|
||||||
|
assert(database != NULL);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
g_file_get_contents(
|
||||||
|
"database/schema_v1.sql",
|
||||||
|
&schema_sql,
|
||||||
|
NULL,
|
||||||
|
&error
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(error == NULL);
|
||||||
|
assert(schema_sql != NULL);
|
||||||
|
|
||||||
|
test_database_execute_sql(
|
||||||
|
database,
|
||||||
|
"BEGIN IMMEDIATE;"
|
||||||
|
);
|
||||||
|
|
||||||
|
test_database_execute_sql(
|
||||||
|
database,
|
||||||
|
schema_sql
|
||||||
|
);
|
||||||
|
|
||||||
|
test_database_execute_sql(
|
||||||
|
database,
|
||||||
|
"INSERT INTO metadata"
|
||||||
|
"("
|
||||||
|
" key,"
|
||||||
|
" value"
|
||||||
|
")"
|
||||||
|
"VALUES"
|
||||||
|
" ('schema_version', '1'),"
|
||||||
|
" ('application', 'Labfy Investigation'),"
|
||||||
|
" ('created_at', '2026-07-18T10:00:00Z'),"
|
||||||
|
" ("
|
||||||
|
" 'investigation_uuid',"
|
||||||
|
" '11111111-1111-4111-8111-111111111111'"
|
||||||
|
" );"
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cette ligne utilise uniquement les colonnes du schéma V1.
|
||||||
|
*
|
||||||
|
* La migration devra conserver la ligne et copier name dans
|
||||||
|
* original_name.
|
||||||
|
*/
|
||||||
|
test_database_execute_sql(
|
||||||
|
database,
|
||||||
|
"INSERT INTO preuves"
|
||||||
|
"("
|
||||||
|
" id,"
|
||||||
|
" name,"
|
||||||
|
" relative_path,"
|
||||||
|
" type_id,"
|
||||||
|
" size_bytes,"
|
||||||
|
" sha256,"
|
||||||
|
" mime_type,"
|
||||||
|
" description,"
|
||||||
|
" commentaire,"
|
||||||
|
" categorie_id,"
|
||||||
|
" file_created_at,"
|
||||||
|
" imported_at,"
|
||||||
|
" updated_at,"
|
||||||
|
" status,"
|
||||||
|
" locked"
|
||||||
|
")"
|
||||||
|
"VALUES"
|
||||||
|
"("
|
||||||
|
" '22222222-2222-4222-8222-222222222222',"
|
||||||
|
" 'ancienne_capture.png',"
|
||||||
|
" '01_Preuves_Originales/ancienne_capture.png',"
|
||||||
|
" (SELECT id FROM types_preuve "
|
||||||
|
" WHERE code = 'screenshot'),"
|
||||||
|
" 128,"
|
||||||
|
" '0123456789abcdef0123456789abcdef"
|
||||||
|
"0123456789abcdef0123456789abcdef',"
|
||||||
|
" 'image/png',"
|
||||||
|
" 'Preuve créée avec le schéma V1.',"
|
||||||
|
" NULL,"
|
||||||
|
" NULL,"
|
||||||
|
" NULL,"
|
||||||
|
" '2026-07-18T10:00:00Z',"
|
||||||
|
" '2026-07-18T10:00:00Z',"
|
||||||
|
" 'active',"
|
||||||
|
" 1"
|
||||||
|
");"
|
||||||
|
);
|
||||||
|
|
||||||
|
test_database_execute_sql(
|
||||||
|
database,
|
||||||
|
"COMMIT;"
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
schema_sql
|
||||||
|
);
|
||||||
|
|
||||||
|
result = sqlite3_close(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(result == SQLITE_OK);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Vérifie qu'une table existe.
|
* @brief Vérifie qu'une table existe.
|
||||||
*/
|
*/
|
||||||
|
|
@ -116,6 +294,75 @@ static void test_database_assert_table_exists(
|
||||||
assert(result == SQLITE_OK);
|
assert(result == SQLITE_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie qu’une colonne existe dans une table.
|
||||||
|
*/
|
||||||
|
static void test_database_assert_column_exists(
|
||||||
|
sqlite3 *database,
|
||||||
|
const char *table_name,
|
||||||
|
const char *column_name
|
||||||
|
)
|
||||||
|
{
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
int result = SQLITE_ERROR;
|
||||||
|
|
||||||
|
assert(database != NULL);
|
||||||
|
assert(table_name != NULL);
|
||||||
|
assert(column_name != NULL);
|
||||||
|
|
||||||
|
result = sqlite3_prepare_v2(
|
||||||
|
database,
|
||||||
|
"SELECT COUNT(*) "
|
||||||
|
"FROM pragma_table_info(?) "
|
||||||
|
"WHERE 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_bind_text(
|
||||||
|
statement,
|
||||||
|
2,
|
||||||
|
column_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.
|
* @brief Vérifie l'initialisation complète d'une base.
|
||||||
*/
|
*/
|
||||||
|
|
@ -189,6 +436,34 @@ static void test_database_initialize_valid_database(void)
|
||||||
database,
|
database,
|
||||||
"investigation"
|
"investigation"
|
||||||
);
|
);
|
||||||
|
test_database_assert_table_exists(
|
||||||
|
database,
|
||||||
|
"preuves"
|
||||||
|
);
|
||||||
|
|
||||||
|
test_database_assert_column_exists(
|
||||||
|
database,
|
||||||
|
"preuves",
|
||||||
|
"original_name"
|
||||||
|
);
|
||||||
|
|
||||||
|
test_database_assert_column_exists(
|
||||||
|
database,
|
||||||
|
"preuves",
|
||||||
|
"collected_at"
|
||||||
|
);
|
||||||
|
|
||||||
|
test_database_assert_column_exists(
|
||||||
|
database,
|
||||||
|
"preuves",
|
||||||
|
"source"
|
||||||
|
);
|
||||||
|
|
||||||
|
test_database_assert_column_exists(
|
||||||
|
database,
|
||||||
|
"preuves",
|
||||||
|
"integrity_status"
|
||||||
|
);
|
||||||
|
|
||||||
schema_version = test_database_read_single_text(
|
schema_version = test_database_read_single_text(
|
||||||
database,
|
database,
|
||||||
|
|
@ -250,7 +525,7 @@ static void test_database_initialize_valid_database(void)
|
||||||
"FROM investigation;"
|
"FROM investigation;"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert(strcmp(schema_version, "1") == 0);
|
assert(strcmp(schema_version, "2") == 0);
|
||||||
assert(strcmp(application_name, "Labfy Investigation") == 0);
|
assert(strcmp(application_name, "Labfy Investigation") == 0);
|
||||||
|
|
||||||
assert(created_at[0] != '\0');
|
assert(created_at[0] != '\0');
|
||||||
|
|
@ -519,10 +794,611 @@ static void test_database_initialize_rollback(void)
|
||||||
g_free(temporary_directory);
|
g_free(temporary_directory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie la migration V1 vers V2 et sa répétition sans effet.
|
||||||
|
*/
|
||||||
|
static void test_database_migrate_v1_to_v2(void)
|
||||||
|
{
|
||||||
|
char *temporary_directory = NULL;
|
||||||
|
char *database_path = NULL;
|
||||||
|
|
||||||
|
char *schema_version = NULL;
|
||||||
|
char *original_name = NULL;
|
||||||
|
char *internal_name = NULL;
|
||||||
|
char *relative_path = NULL;
|
||||||
|
char *integrity_status = NULL;
|
||||||
|
char *evidence_count = NULL;
|
||||||
|
char *v2_column_count = NULL;
|
||||||
|
char *migration_index_count = NULL;
|
||||||
|
char *migration_trigger_count = NULL;
|
||||||
|
|
||||||
|
Database *database_context = NULL;
|
||||||
|
sqlite3 *database = NULL;
|
||||||
|
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
int result = SQLITE_ERROR;
|
||||||
|
|
||||||
|
temporary_directory =
|
||||||
|
g_dir_make_tmp(
|
||||||
|
"labfy-database-migration-test-XXXXXX",
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(temporary_directory != NULL);
|
||||||
|
assert(error == NULL);
|
||||||
|
|
||||||
|
database_path =
|
||||||
|
g_build_filename(
|
||||||
|
temporary_directory,
|
||||||
|
"Enquete.sqlite",
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(database_path != NULL);
|
||||||
|
|
||||||
|
test_database_create_v1_database(
|
||||||
|
database_path
|
||||||
|
);
|
||||||
|
|
||||||
|
database_context =
|
||||||
|
database_open(
|
||||||
|
database_path
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(database_context != NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Premier appel : migration réelle de V1 vers V2.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
database_migrate_to_latest(
|
||||||
|
database_context
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(
|
||||||
|
database_context
|
||||||
|
) == DATABASE_ERROR_NONE
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Second appel : la base est déjà en V2.
|
||||||
|
*
|
||||||
|
* Aucun ALTER TABLE, index ou trigger ne doit être rejoué.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
database_migrate_to_latest(
|
||||||
|
database_context
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(
|
||||||
|
database_context
|
||||||
|
) == DATABASE_ERROR_NONE
|
||||||
|
);
|
||||||
|
|
||||||
|
database_close(
|
||||||
|
database_context
|
||||||
|
);
|
||||||
|
|
||||||
|
result = sqlite3_open_v2(
|
||||||
|
database_path,
|
||||||
|
&database,
|
||||||
|
SQLITE_OPEN_READONLY,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(result == SQLITE_OK);
|
||||||
|
assert(database != NULL);
|
||||||
|
|
||||||
|
schema_version =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT value "
|
||||||
|
"FROM metadata "
|
||||||
|
"WHERE key = 'schema_version';"
|
||||||
|
);
|
||||||
|
|
||||||
|
evidence_count =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT CAST(COUNT(*) AS TEXT) "
|
||||||
|
"FROM preuves;"
|
||||||
|
);
|
||||||
|
|
||||||
|
internal_name =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT name "
|
||||||
|
"FROM preuves "
|
||||||
|
"WHERE id = "
|
||||||
|
"'22222222-2222-4222-8222-222222222222';"
|
||||||
|
);
|
||||||
|
|
||||||
|
original_name =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT original_name "
|
||||||
|
"FROM preuves "
|
||||||
|
"WHERE id = "
|
||||||
|
"'22222222-2222-4222-8222-222222222222';"
|
||||||
|
);
|
||||||
|
|
||||||
|
relative_path =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT relative_path "
|
||||||
|
"FROM preuves "
|
||||||
|
"WHERE id = "
|
||||||
|
"'22222222-2222-4222-8222-222222222222';"
|
||||||
|
);
|
||||||
|
|
||||||
|
integrity_status =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT CAST(integrity_status AS TEXT) "
|
||||||
|
"FROM preuves "
|
||||||
|
"WHERE id = "
|
||||||
|
"'22222222-2222-4222-8222-222222222222';"
|
||||||
|
);
|
||||||
|
|
||||||
|
v2_column_count =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT CAST(COUNT(*) AS TEXT) "
|
||||||
|
"FROM pragma_table_info('preuves') "
|
||||||
|
"WHERE name IN"
|
||||||
|
"("
|
||||||
|
" 'original_name',"
|
||||||
|
" 'collected_at',"
|
||||||
|
" 'source',"
|
||||||
|
" 'integrity_status'"
|
||||||
|
");"
|
||||||
|
);
|
||||||
|
|
||||||
|
migration_index_count =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT CAST(COUNT(*) AS TEXT) "
|
||||||
|
"FROM sqlite_master "
|
||||||
|
"WHERE type = 'index' "
|
||||||
|
"AND name = 'idx_preuves_imported_at';"
|
||||||
|
);
|
||||||
|
|
||||||
|
migration_trigger_count =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT CAST(COUNT(*) AS TEXT) "
|
||||||
|
"FROM sqlite_master "
|
||||||
|
"WHERE type = 'trigger' "
|
||||||
|
"AND name IN"
|
||||||
|
"("
|
||||||
|
" 'preuves_v2_validate_insert',"
|
||||||
|
" 'preuves_v2_validate_update'"
|
||||||
|
");"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
schema_version,
|
||||||
|
"2"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* La ligne V1 doit être conservée.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
evidence_count,
|
||||||
|
"1"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
internal_name,
|
||||||
|
"ancienne_capture.png"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Le backfill V2 doit recopier name dans original_name.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
original_name,
|
||||||
|
"ancienne_capture.png"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
relative_path,
|
||||||
|
"01_Preuves_Originales/ancienne_capture.png"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Une ancienne preuve n’ayant jamais été contrôlée reçoit UNKNOWN.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
integrity_status,
|
||||||
|
"0"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Les quatre colonnes ne doivent exister qu’une fois chacune.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
v2_column_count,
|
||||||
|
"4"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
migration_index_count,
|
||||||
|
"1"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
migration_trigger_count,
|
||||||
|
"2"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
result = sqlite3_close(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(result == SQLITE_OK);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
g_remove(
|
||||||
|
database_path
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
g_rmdir(
|
||||||
|
temporary_directory
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
migration_trigger_count
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
migration_index_count
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
v2_column_count
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
evidence_count
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
integrity_status
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
relative_path
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
internal_name
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
original_name
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
schema_version
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
database_path
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
temporary_directory
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie qu’une migration V2 échouée est entièrement annulée.
|
||||||
|
*/
|
||||||
|
static void test_database_migration_rollback(void)
|
||||||
|
{
|
||||||
|
char *temporary_directory = NULL;
|
||||||
|
char *database_path = NULL;
|
||||||
|
|
||||||
|
char *schema_version = NULL;
|
||||||
|
char *original_name_column_count = NULL;
|
||||||
|
char *collected_at_column_count = NULL;
|
||||||
|
char *source_column_count = NULL;
|
||||||
|
char *integrity_status_column_count = NULL;
|
||||||
|
char *evidence_count = NULL;
|
||||||
|
char *integrity_result = NULL;
|
||||||
|
|
||||||
|
Database *database_context = NULL;
|
||||||
|
sqlite3 *database = NULL;
|
||||||
|
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
int result = SQLITE_ERROR;
|
||||||
|
|
||||||
|
temporary_directory =
|
||||||
|
g_dir_make_tmp(
|
||||||
|
"labfy-database-migration-rollback-test-XXXXXX",
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(temporary_directory != NULL);
|
||||||
|
assert(error == NULL);
|
||||||
|
|
||||||
|
database_path =
|
||||||
|
g_build_filename(
|
||||||
|
temporary_directory,
|
||||||
|
"Enquete.sqlite",
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(database_path != NULL);
|
||||||
|
|
||||||
|
test_database_create_v1_database(
|
||||||
|
database_path
|
||||||
|
);
|
||||||
|
|
||||||
|
result = sqlite3_open_v2(
|
||||||
|
database_path,
|
||||||
|
&database,
|
||||||
|
SQLITE_OPEN_READWRITE,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(result == SQLITE_OK);
|
||||||
|
assert(database != NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* La migration V2 essaiera de créer un index portant ce nom.
|
||||||
|
* Le conflit doit provoquer un échec après les ALTER TABLE.
|
||||||
|
*/
|
||||||
|
test_database_execute_sql(
|
||||||
|
database,
|
||||||
|
"CREATE INDEX idx_preuves_imported_at "
|
||||||
|
"ON preuves(name);"
|
||||||
|
);
|
||||||
|
|
||||||
|
result = sqlite3_close(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(result == SQLITE_OK);
|
||||||
|
|
||||||
|
database = NULL;
|
||||||
|
|
||||||
|
database_context =
|
||||||
|
database_open(
|
||||||
|
database_path
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(database_context != NULL);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
!database_migrate_to_latest(
|
||||||
|
database_context
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(
|
||||||
|
database_context
|
||||||
|
) != DATABASE_ERROR_NONE
|
||||||
|
);
|
||||||
|
|
||||||
|
database_close(
|
||||||
|
database_context
|
||||||
|
);
|
||||||
|
|
||||||
|
result = sqlite3_open_v2(
|
||||||
|
database_path,
|
||||||
|
&database,
|
||||||
|
SQLITE_OPEN_READONLY,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(result == SQLITE_OK);
|
||||||
|
assert(database != NULL);
|
||||||
|
|
||||||
|
schema_version =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT value "
|
||||||
|
"FROM metadata "
|
||||||
|
"WHERE key = 'schema_version';"
|
||||||
|
);
|
||||||
|
|
||||||
|
original_name_column_count =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT CAST(COUNT(*) AS TEXT) "
|
||||||
|
"FROM pragma_table_info('preuves') "
|
||||||
|
"WHERE name = 'original_name';"
|
||||||
|
);
|
||||||
|
|
||||||
|
collected_at_column_count =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT CAST(COUNT(*) AS TEXT) "
|
||||||
|
"FROM pragma_table_info('preuves') "
|
||||||
|
"WHERE name = 'collected_at';"
|
||||||
|
);
|
||||||
|
|
||||||
|
source_column_count =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT CAST(COUNT(*) AS TEXT) "
|
||||||
|
"FROM pragma_table_info('preuves') "
|
||||||
|
"WHERE name = 'source';"
|
||||||
|
);
|
||||||
|
|
||||||
|
integrity_status_column_count =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT CAST(COUNT(*) AS TEXT) "
|
||||||
|
"FROM pragma_table_info('preuves') "
|
||||||
|
"WHERE name = 'integrity_status';"
|
||||||
|
);
|
||||||
|
|
||||||
|
evidence_count =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT CAST(COUNT(*) AS TEXT) "
|
||||||
|
"FROM preuves;"
|
||||||
|
);
|
||||||
|
|
||||||
|
integrity_result =
|
||||||
|
test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"PRAGMA integrity_check;"
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* La version ne doit pas avancer lorsque le SQL V2 échoue.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
schema_version,
|
||||||
|
"1"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Les ALTER TABLE précédant l’erreur doivent avoir été annulés.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
original_name_column_count,
|
||||||
|
"0"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
collected_at_column_count,
|
||||||
|
"0"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
source_column_count,
|
||||||
|
"0"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
integrity_status_column_count,
|
||||||
|
"0"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* La preuve V1 doit rester intacte.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
evidence_count,
|
||||||
|
"1"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
integrity_result,
|
||||||
|
"ok"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
result = sqlite3_close(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(result == SQLITE_OK);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
g_remove(
|
||||||
|
database_path
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
g_rmdir(
|
||||||
|
temporary_directory
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
integrity_result
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
evidence_count
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
integrity_status_column_count
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
source_column_count
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
collected_at_column_count
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
original_name_column_count
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
schema_version
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
database_path
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
temporary_directory
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
test_database_initialize_valid_database();
|
test_database_initialize_valid_database();
|
||||||
test_database_initialize_rollback();
|
test_database_initialize_rollback();
|
||||||
|
|
||||||
|
test_database_migrate_v1_to_v2();
|
||||||
|
test_database_migration_rollback();
|
||||||
|
|
||||||
test_database_initialize_invalid_parameters();
|
test_database_initialize_invalid_parameters();
|
||||||
test_database_initialize_missing_parent();
|
test_database_initialize_missing_parent();
|
||||||
|
|
||||||
|
|
|
||||||
BIN
tests/test_error
BIN
tests/test_error
Binary file not shown.
BIN
tests/test_evidence_dao
Executable file
BIN
tests/test_evidence_dao
Executable file
Binary file not shown.
1773
tests/test_evidence_dao.c
Normal file
1773
tests/test_evidence_dao.c
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
|
@ -6,7 +6,6 @@
|
||||||
#include "models/evidence_record.h"
|
#include "models/evidence_record.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#define TEST_EVIDENCE_IDENTIFIER \
|
#define TEST_EVIDENCE_IDENTIFIER \
|
||||||
"6e62b9af-2046-4efd-b3b9-29869f816951"
|
"6e62b9af-2046-4efd-b3b9-29869f816951"
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -548,7 +548,7 @@ static void test_open_invalid_database(void)
|
||||||
|
|
||||||
test_assert_session_error(
|
test_assert_session_error(
|
||||||
error,
|
error,
|
||||||
INVESTIGATION_SESSION_ERROR_RECORD
|
INVESTIGATION_SESSION_ERROR_DATABASE
|
||||||
);
|
);
|
||||||
|
|
||||||
g_clear_error(&error);
|
g_clear_error(&error);
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue