diff --git a/Makefile b/Makefile index 0ffe982..6888953 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ TEST_LDFLAGS = $(shell $(PKG_CONFIG) --libs glib-2.0 gio-2.0) EVIDENCE_RECORD_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic EVIDENCE_TYPE_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic +ENTITY_TYPE_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic EVIDENCE_TYPE_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic EVIDENCE_IMPORT_DIALOG_TEST_CFLAGS := \ -std=c17 \ @@ -73,6 +74,7 @@ TEST_EVIDENCE_COPY := tests/test_evidence_copy TEST_EVIDENCE_IMPORTER := tests/test_evidence_importer TEST_EVIDENCE_IMPORT_TASK := tests/test_evidence_import_task TEST_EVIDENCE_TYPE := tests/test_evidence_type +TEST_ENTITY_TYPE := tests/test_entity_type TEST_EVIDENCE_TYPE_DAO := tests/test_evidence_type_dao TEST_EVIDENCE_IMPORT_DIALOG := tests/test_evidence_import_dialog TEST_EVIDENCE_LIST_ITEM := tests/test_evidence_list_item @@ -315,6 +317,11 @@ $(TEST_EVIDENCE_TYPE): \ src/models/evidence_type.c $(CC) $(EVIDENCE_TYPE_TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) +$(TEST_ENTITY_TYPE): \ + tests/test_entity_type.c \ + src/models/entity_type.c + $(CC) $(ENTITY_TYPE_TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + $(TEST_EVIDENCE_TYPE_DAO): \ tests/test_evidence_type_dao.c \ src/dao/evidence_type_dao.c \ @@ -376,6 +383,7 @@ test: \ $(TEST_EVIDENCE_CATEGORY_ITEM) \ $(TEST_EVIDENCE_CATEGORY_MODEL) \ $(TEST_EVIDENCE_TYPE) \ + $(TEST_ENTITY_TYPE) \ $(TEST_INVESTIGATION_DAO) \ $(TEST_EVIDENCE_DAO) \ $(TEST_EVIDENCE_TYPE_DAO) \ @@ -410,6 +418,7 @@ test: \ @$(TEST_EVIDENCE_CATEGORY_MODEL) @$(TEST_EVIDENCE_RECORD) @$(TEST_EVIDENCE_TYPE) + @$(TEST_ENTITY_TYPE) @$(TEST_INVESTIGATION_DAO) @$(TEST_EVIDENCE_DAO) @$(TEST_EVIDENCE_TYPE_DAO) @@ -453,6 +462,7 @@ clean: $(TEST_EVIDENCE_CATEGORY_ITEM) \ $(TEST_EVIDENCE_CATEGORY_MODEL) \ $(TEST_EVIDENCE_TYPE) \ + $(TEST_ENTITY_TYPE) \ $(TEST_EVIDENCE_TYPE_DAO) \ $(TEST_INVESTIGATION_DAO) \ $(TEST_EVIDENCE_DAO) \ diff --git a/include/models/entity_type.h b/include/models/entity_type.h new file mode 100644 index 0000000..51f0c60 --- /dev/null +++ b/include/models/entity_type.h @@ -0,0 +1,132 @@ +/****************************************************************************** + * @file entity_type.h + * @brief Modèle représentant un type d'entité. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_ENTITY_TYPE_H +#define LABFY_INVESTIGATION_ENTITY_TYPE_H + +#include + +G_BEGIN_DECLS + +/** + * @brief Modèle opaque représentant un type d'entité. + */ +typedef struct EntityType EntityType; + +/** + * @brief Codes d'erreur produits par EntityType. + */ +typedef enum +{ + /** + * Un argument général est invalide. + */ + ENTITY_TYPE_ERROR_INVALID_ARGUMENT, + + /** + * L'identifiant numérique est invalide. + */ + ENTITY_TYPE_ERROR_INVALID_IDENTIFIER, + + /** + * Le code métier est invalide. + */ + ENTITY_TYPE_ERROR_INVALID_CODE, + + /** + * Le libellé utilisateur est invalide. + */ + ENTITY_TYPE_ERROR_INVALID_LABEL +} EntityTypeError; + +/** + * @brief Domaine d'erreur du modèle EntityType. + */ +#define ENTITY_TYPE_ERROR \ + entity_type_error_quark() + +/** + * @brief Retourne le domaine d'erreur du modèle. + * + * @return Quark GLib associé à EntityType. + */ +GQuark entity_type_error_quark(void); + +/** + * @brief Crée un type d'entité. + * + * Les chaînes sont copiées et appartiennent au nouvel objet. + * + * L'identifiant doit être strictement positif. + * + * Le code et le libellé sont obligatoires. + * La description peut être NULL ou vide. + * + * @param identifier Identifiant numérique SQLite. + * @param code Code métier du type. + * @param label Libellé destiné à l'utilisateur. + * @param description Description facultative. + * @param error Emplacement facultatif recevant une erreur. + * + * @return Nouveau modèle, ou NULL lorsque les données sont invalides. + */ +EntityType *entity_type_new( + gint64 identifier, + const char *code, + const char *label, + const char *description, + GError **error +); + +/** + * @brief Libère un modèle EntityType. + * + * Cette fonction accepte entity_type == NULL. + * + * @param entity_type Modèle à libérer. + */ +void entity_type_free( + EntityType *entity_type +); + +/** + * @brief Retourne l'identifiant numérique du type. + * + * @return Identifiant, ou 0 lorsque entity_type est NULL. + */ +gint64 entity_type_get_identifier( + const EntityType *entity_type +); + +/** + * @brief Retourne le code métier du type. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *entity_type_get_code( + const EntityType *entity_type +); + +/** + * @brief Retourne le libellé utilisateur du type. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *entity_type_get_label( + const EntityType *entity_type +); + +/** + * @brief Retourne la description du type. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *entity_type_get_description( + const EntityType *entity_type +); + +G_END_DECLS + +#endif diff --git a/src/models/entity_type.c b/src/models/entity_type.c new file mode 100644 index 0000000..87e3c65 --- /dev/null +++ b/src/models/entity_type.c @@ -0,0 +1,275 @@ +/****************************************************************************** + * @file entity_type.c + * @brief Implémentation du modèle représentant un type d'entité. + ******************************************************************************/ + +#include "models/entity_type.h" + +#include + +/** + * @struct EntityType + * @brief Représentation interne d'un type d'entité. + */ +struct EntityType +{ + gint64 identifier; + char *code; + char *label; + char *description; +}; + +/** + * @brief Copie une chaîne après suppression des espaces périphériques. + * + * @param value Chaîne à copier. + * + * @return Nouvelle chaîne normalisée, ou NULL lorsque value vaut NULL. + */ +static char *entity_type_duplicate_trimmed( + const char *value +) +{ + char *copy = + NULL; + + if (value == NULL) + { + return NULL; + } + + copy = + g_strdup( + value + ); + + g_strstrip( + copy + ); + + return copy; +} + +/** + * @brief Vérifie qu'une chaîne obligatoire contient du texte. + * + * @param value Chaîne déjà normalisée. + * + * @return TRUE lorsque la chaîne contient au moins un caractère. + */ +static gboolean entity_type_required_value_is_valid( + const char *value +) +{ + return value != NULL && + value[0] != '\0'; +} + +GQuark entity_type_error_quark(void) +{ + return g_quark_from_static_string( + "entity-type-error-quark" + ); +} + +EntityType *entity_type_new( + gint64 identifier, + const char *code, + const char *label, + const char *description, + GError **error +) +{ + EntityType *entity_type = + NULL; + + char *normalized_code = + NULL; + + char *normalized_label = + NULL; + + char *normalized_description = + NULL; + + g_return_val_if_fail( + error == NULL || *error == NULL, + NULL + ); + + if (identifier <= 0) + { + g_set_error_literal( + error, + ENTITY_TYPE_ERROR, + ENTITY_TYPE_ERROR_INVALID_IDENTIFIER, + "L'identifiant du type d'entité doit être strictement positif." + ); + + return NULL; + } + + normalized_code = + entity_type_duplicate_trimmed( + code + ); + + if (!entity_type_required_value_is_valid( + normalized_code + )) + { + g_set_error_literal( + error, + ENTITY_TYPE_ERROR, + ENTITY_TYPE_ERROR_INVALID_CODE, + "Le code du type d'entité est obligatoire." + ); + + g_free( + normalized_code + ); + + return NULL; + } + + normalized_label = + entity_type_duplicate_trimmed( + label + ); + + if (!entity_type_required_value_is_valid( + normalized_label + )) + { + g_set_error_literal( + error, + ENTITY_TYPE_ERROR, + ENTITY_TYPE_ERROR_INVALID_LABEL, + "Le libellé du type d'entité est obligatoire." + ); + + g_free( + normalized_label + ); + + g_free( + normalized_code + ); + + return NULL; + } + + normalized_description = + entity_type_duplicate_trimmed( + description + ); + + /* + * Une description vide ou composée uniquement d'espaces + * est représentée par NULL. + */ + if (normalized_description != NULL && + normalized_description[0] == '\0') + { + g_free( + normalized_description + ); + + normalized_description = + NULL; + } + + entity_type = + g_new0( + EntityType, + 1 + ); + + entity_type->identifier = + identifier; + + entity_type->code = + normalized_code; + + entity_type->label = + normalized_label; + + entity_type->description = + normalized_description; + + return entity_type; +} + +void entity_type_free( + EntityType *entity_type +) +{ + if (entity_type == NULL) + { + return; + } + + g_free( + entity_type->description + ); + + g_free( + entity_type->label + ); + + g_free( + entity_type->code + ); + + g_free( + entity_type + ); +} + +gint64 entity_type_get_identifier( + const EntityType *entity_type +) +{ + if (entity_type == NULL) + { + return 0; + } + + return entity_type->identifier; +} + +const char *entity_type_get_code( + const EntityType *entity_type +) +{ + if (entity_type == NULL) + { + return NULL; + } + + return entity_type->code; +} + +const char *entity_type_get_label( + const EntityType *entity_type +) +{ + if (entity_type == NULL) + { + return NULL; + } + + return entity_type->label; +} + +const char *entity_type_get_description( + const EntityType *entity_type +) +{ + if (entity_type == NULL) + { + return NULL; + } + + return entity_type->description; +} diff --git a/tests/test_entity_type.c b/tests/test_entity_type.c new file mode 100644 index 0000000..ca49b5b --- /dev/null +++ b/tests/test_entity_type.c @@ -0,0 +1,523 @@ +/****************************************************************************** + * @file test_entity_type.c + * @brief Tests unitaires du modèle EntityType. + ******************************************************************************/ + +#include "models/entity_type.h" + +#include + +/** + * @brief Vérifie la création d'un type valide. + */ +static void test_entity_type_new_valid(void) +{ + EntityType *entity_type = + NULL; + + GError *error = + NULL; + + entity_type = + entity_type_new( + 3, + "email", + "Adresse e-mail", + "Adresse électronique associée à l'enquête.", + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + entity_type + ); + + g_assert_cmpint( + entity_type_get_identifier( + entity_type + ), + ==, + 3 + ); + + g_assert_cmpstr( + entity_type_get_code( + entity_type + ), + ==, + "email" + ); + + g_assert_cmpstr( + entity_type_get_label( + entity_type + ), + ==, + "Adresse e-mail" + ); + + g_assert_cmpstr( + entity_type_get_description( + entity_type + ), + ==, + "Adresse électronique associée à l'enquête." + ); + + entity_type_free( + entity_type + ); +} + +/** + * @brief Vérifie la suppression des espaces périphériques. + */ +static void test_entity_type_values_are_trimmed(void) +{ + EntityType *entity_type = + NULL; + + GError *error = + NULL; + + entity_type = + entity_type_new( + 4, + " iban ", + " IBAN ", + " Identifiant international d'un compte bancaire. ", + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + entity_type + ); + + g_assert_cmpstr( + entity_type_get_code( + entity_type + ), + ==, + "iban" + ); + + g_assert_cmpstr( + entity_type_get_label( + entity_type + ), + ==, + "IBAN" + ); + + g_assert_cmpstr( + entity_type_get_description( + entity_type + ), + ==, + "Identifiant international d'un compte bancaire." + ); + + entity_type_free( + entity_type + ); +} + +/** + * @brief Vérifie qu'une description absente reste facultative. + */ +static void test_entity_type_description_is_optional(void) +{ + EntityType *entity_type = + NULL; + + GError *error = + NULL; + + entity_type = + entity_type_new( + 14, + "other", + "Autre", + NULL, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + entity_type + ); + + g_assert_null( + entity_type_get_description( + entity_type + ) + ); + + entity_type_free( + entity_type + ); +} + +/** + * @brief Vérifie qu'une description vide est normalisée vers NULL. + */ +static void test_entity_type_empty_description_becomes_null(void) +{ + EntityType *entity_type = + NULL; + + GError *error = + NULL; + + entity_type = + entity_type_new( + 7, + "person", + "Personne", + " ", + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + entity_type + ); + + g_assert_null( + entity_type_get_description( + entity_type + ) + ); + + entity_type_free( + entity_type + ); +} + +/** + * @brief Vérifie le refus d'un identifiant nul. + */ +static void test_entity_type_zero_identifier(void) +{ + EntityType *entity_type = + NULL; + + GError *error = + NULL; + + entity_type = + entity_type_new( + 0, + "email", + "Adresse e-mail", + NULL, + &error + ); + + g_assert_null( + entity_type + ); + + g_assert_error( + error, + ENTITY_TYPE_ERROR, + ENTITY_TYPE_ERROR_INVALID_IDENTIFIER + ); + + g_clear_error( + &error + ); +} + +/** + * @brief Vérifie le refus d'un identifiant négatif. + */ +static void test_entity_type_negative_identifier(void) +{ + EntityType *entity_type = + NULL; + + GError *error = + NULL; + + entity_type = + entity_type_new( + -1, + "email", + "Adresse e-mail", + NULL, + &error + ); + + g_assert_null( + entity_type + ); + + g_assert_error( + error, + ENTITY_TYPE_ERROR, + ENTITY_TYPE_ERROR_INVALID_IDENTIFIER + ); + + g_clear_error( + &error + ); +} + +/** + * @brief Vérifie le refus d'un code absent. + */ +static void test_entity_type_missing_code(void) +{ + EntityType *entity_type = + NULL; + + GError *error = + NULL; + + entity_type = + entity_type_new( + 3, + NULL, + "Adresse e-mail", + NULL, + &error + ); + + g_assert_null( + entity_type + ); + + g_assert_error( + error, + ENTITY_TYPE_ERROR, + ENTITY_TYPE_ERROR_INVALID_CODE + ); + + g_clear_error( + &error + ); +} + +/** + * @brief Vérifie le refus d'un code vide. + */ +static void test_entity_type_empty_code(void) +{ + EntityType *entity_type = + NULL; + + GError *error = + NULL; + + entity_type = + entity_type_new( + 3, + " ", + "Adresse e-mail", + NULL, + &error + ); + + g_assert_null( + entity_type + ); + + g_assert_error( + error, + ENTITY_TYPE_ERROR, + ENTITY_TYPE_ERROR_INVALID_CODE + ); + + g_clear_error( + &error + ); +} + +/** + * @brief Vérifie le refus d'un libellé absent. + */ +static void test_entity_type_missing_label(void) +{ + EntityType *entity_type = + NULL; + + GError *error = + NULL; + + entity_type = + entity_type_new( + 3, + "email", + NULL, + NULL, + &error + ); + + g_assert_null( + entity_type + ); + + g_assert_error( + error, + ENTITY_TYPE_ERROR, + ENTITY_TYPE_ERROR_INVALID_LABEL + ); + + g_clear_error( + &error + ); +} + +/** + * @brief Vérifie le refus d'un libellé vide. + */ +static void test_entity_type_empty_label(void) +{ + EntityType *entity_type = + NULL; + + GError *error = + NULL; + + entity_type = + entity_type_new( + 3, + "email", + " ", + NULL, + &error + ); + + g_assert_null( + entity_type + ); + + g_assert_error( + error, + ENTITY_TYPE_ERROR, + ENTITY_TYPE_ERROR_INVALID_LABEL + ); + + g_clear_error( + &error + ); +} + +/** + * @brief Vérifie le comportement des getters avec NULL. + */ +static void test_entity_type_null_getters(void) +{ + g_assert_cmpint( + entity_type_get_identifier( + NULL + ), + ==, + 0 + ); + + g_assert_null( + entity_type_get_code( + NULL + ) + ); + + g_assert_null( + entity_type_get_label( + NULL + ) + ); + + g_assert_null( + entity_type_get_description( + NULL + ) + ); + + entity_type_free( + NULL + ); +} + +int main( + int argc, + char **argv +) +{ + g_test_init( + &argc, + &argv, + NULL + ); + + g_test_add_func( + "/entity-type/new-valid", + test_entity_type_new_valid + ); + + g_test_add_func( + "/entity-type/values-are-trimmed", + test_entity_type_values_are_trimmed + ); + + g_test_add_func( + "/entity-type/description-is-optional", + test_entity_type_description_is_optional + ); + + g_test_add_func( + "/entity-type/empty-description-becomes-null", + test_entity_type_empty_description_becomes_null + ); + + g_test_add_func( + "/entity-type/zero-identifier", + test_entity_type_zero_identifier + ); + + g_test_add_func( + "/entity-type/negative-identifier", + test_entity_type_negative_identifier + ); + + g_test_add_func( + "/entity-type/missing-code", + test_entity_type_missing_code + ); + + g_test_add_func( + "/entity-type/empty-code", + test_entity_type_empty_code + ); + + g_test_add_func( + "/entity-type/missing-label", + test_entity_type_missing_label + ); + + g_test_add_func( + "/entity-type/empty-label", + test_entity_type_empty_label + ); + + g_test_add_func( + "/entity-type/null-getters", + test_entity_type_null_getters + ); + + return g_test_run(); +}