diff --git a/Makefile b/Makefile index 6888953..ab99d6c 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,7 @@ EVIDENCE_IMPORT_DIALOG_TEST_LDFLAGS := \ EVIDENCE_INTEGRITY_VERIFIER_TEST_CFLAGS := \ $(TEST_CFLAGS) \ -Wpedantic +ENTITY_RECORD_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic SRC := $(shell find src -name "*.c") @@ -82,6 +83,7 @@ TEST_EVIDENCE_LIST_MODEL := tests/test_evidence_list_model TEST_EVIDENCE_CATEGORY_ITEM := tests/test_evidence_category_item TEST_EVIDENCE_CATEGORY_MODEL := tests/test_evidence_category_model TEST_EVIDENCE_INTEGRITY_TASK := tests/test_evidence_integrity_task +TEST_ENTITY_RECORD := tests/test_entity_record all: $(TARGET) @@ -158,7 +160,8 @@ $(TEST_INVESTIGATION_RECORD): \ $(TEST_EVIDENCE_RECORD): \ tests/test_evidence_record.c \ - src/models/evidence_record.c + src/models/evidence_record.c \ + src/models/entity_record.c $(CC) $(EVIDENCE_RECORD_TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) $(TEST_EVIDENCE_LIST_ITEM): \ @@ -367,6 +370,11 @@ $(TEST_EVIDENCE_INTEGRITY_TASK): \ -DFILE_HASH_ENABLE_TEST_HOOKS \ $^ -o $@ $(TEST_LDFLAGS) +$(TEST_ENTITY_RECORD): \ + tests/test_entity_record.c \ + src/models/entity_record.c + $(CC) $(ENTITY_RECORD_TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + test: \ $(TEST_NODE) \ $(TEST_TREE_MODEL) \ @@ -401,7 +409,8 @@ test: \ $(TEST_EVIDENCE_IMPORTER) \ $(TEST_EVIDENCE_IMPORT_TASK) \ $(TEST_EVIDENCE_IMPORT_DIALOG) \ - $(TEST_EVIDENCE_INTEGRITY_TASK) + $(TEST_EVIDENCE_INTEGRITY_TASK) \ + $(TEST_ENTITY_RECORD) @echo "Exécution des tests..." @./$(TEST_NODE) @./$(TEST_TREE_MODEL) @@ -437,6 +446,7 @@ test: \ @$(TEST_EVIDENCE_IMPORT_TASK) @$(TEST_EVIDENCE_IMPORT_DIALOG) @$(TEST_EVIDENCE_INTEGRITY_TASK) + @$(TEST_ENTITY_RECORD) @echo "Tous les tests sont valides." %.o: %.c diff --git a/include/models/entity_record.h b/include/models/entity_record.h new file mode 100644 index 0000000..d17cd13 --- /dev/null +++ b/include/models/entity_record.h @@ -0,0 +1,175 @@ +/****************************************************************************** + * @file entity_record.h + * @brief Modèle représentant une entité persistée. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_ENTITY_RECORD_H +#define LABFY_INVESTIGATION_ENTITY_RECORD_H + +#include + +G_BEGIN_DECLS + +/** + * @brief Modèle opaque représentant une entité. + */ +typedef struct EntityRecord EntityRecord; + +/** + * @brief Statut métier d'une entité. + * + * UNKNOWN sert uniquement de valeur défensive et ne doit jamais être + * persisté dans SQLite. + */ +typedef enum +{ + ENTITY_STATUS_UNKNOWN, + ENTITY_STATUS_ACTIVE, + ENTITY_STATUS_ARCHIVED, + ENTITY_STATUS_DELETED +} EntityStatus; + +/** + * @brief Codes d'erreur produits par EntityRecord. + */ +typedef enum +{ + ENTITY_RECORD_ERROR_INVALID_ARGUMENT, + ENTITY_RECORD_ERROR_INVALID_IDENTIFIER, + ENTITY_RECORD_ERROR_INVALID_TYPE, + ENTITY_RECORD_ERROR_INVALID_VALUE, + ENTITY_RECORD_ERROR_INVALID_CONFIDENCE, + ENTITY_RECORD_ERROR_INVALID_DATE, + ENTITY_RECORD_ERROR_INVALID_STATUS +} EntityRecordError; + +/** + * @brief Domaine d'erreur du modèle EntityRecord. + */ +#define ENTITY_RECORD_ERROR \ + entity_record_error_quark() + +/** + * @brief Retourne le domaine d'erreur du modèle. + */ +GQuark entity_record_error_quark(void); + +/** + * @brief Crée une entité métier. + * + * Toutes les chaînes sont copiées. + * + * Les champs identifier, type_identifier, value, created_at et updated_at + * sont obligatoires. + * + * label et description peuvent être NULL ou vides. + * + * confidence doit être compris entre 0 et 100. + * + * status doit être ACTIVE, ARCHIVED ou DELETED. + * + * @param identifier UUID de l'entité. + * @param type_identifier Code métier du type d'entité. + * @param value Valeur principale de l'entité. + * @param label Libellé facultatif. + * @param description Description facultative. + * @param confidence Niveau de confiance compris entre 0 et 100. + * @param created_at Date UTC de création. + * @param updated_at Date UTC de dernière modification. + * @param status Statut métier. + * @param error Emplacement facultatif recevant une erreur. + * + * @return Nouvelle entité, ou NULL lorsque les données sont invalides. + */ +EntityRecord *entity_record_new( + const char *identifier, + const char *type_identifier, + const char *value, + const char *label, + const char *description, + gint confidence, + const char *created_at, + const char *updated_at, + EntityStatus status, + GError **error +); + +/** + * @brief Libère une entité. + * + * Cette fonction accepte NULL. + */ +void entity_record_free( + EntityRecord *entity_record +); + +/** + * @brief Retourne l'UUID de l'entité. + */ +const char *entity_record_get_identifier( + const EntityRecord *entity_record +); + +/** + * @brief Retourne le code métier du type. + */ +const char *entity_record_get_type_identifier( + const EntityRecord *entity_record +); + +/** + * @brief Retourne la valeur principale. + */ +const char *entity_record_get_value( + const EntityRecord *entity_record +); + +/** + * @brief Retourne le libellé facultatif. + */ +const char *entity_record_get_label( + const EntityRecord *entity_record +); + +/** + * @brief Retourne la description facultative. + */ +const char *entity_record_get_description( + const EntityRecord *entity_record +); + +/** + * @brief Retourne le niveau de confiance. + * + * @return Valeur comprise entre 0 et 100, ou 0 si entity_record est NULL. + */ +gint entity_record_get_confidence( + const EntityRecord *entity_record +); + +/** + * @brief Retourne la date UTC de création. + */ +const char *entity_record_get_created_at( + const EntityRecord *entity_record +); + +/** + * @brief Retourne la date UTC de dernière modification. + */ +const char *entity_record_get_updated_at( + const EntityRecord *entity_record +); + +/** + * @brief Retourne le statut métier. + * + * @return Statut ou ENTITY_STATUS_UNKNOWN si entity_record est NULL. + */ +EntityStatus entity_record_get_status( + const EntityRecord *entity_record +); + +G_END_DECLS + +#endif diff --git a/src/models/entity_record.c b/src/models/entity_record.c new file mode 100644 index 0000000..8d6f2ac --- /dev/null +++ b/src/models/entity_record.c @@ -0,0 +1,584 @@ +/****************************************************************************** + * @file entity_record.c + * @brief Implémentation du modèle représentant une entité persistée. + ******************************************************************************/ + +#include "models/entity_record.h" + +#include + +/** + * @brief Représentation privée d'une entité. + */ +struct EntityRecord +{ + char *identifier; + char *type_identifier; + char *value; + char *label; + char *description; + + gint confidence; + + char *created_at; + char *updated_at; + + EntityStatus status; +}; + +/** + * @brief Copie et nettoie une chaîne. + */ +static char *entity_record_duplicate_trimmed( + const char *value +) +{ + char *copy = + NULL; + + if (value == NULL) + { + return NULL; + } + + copy = + g_strdup( + value + ); + + g_strstrip( + copy + ); + + return copy; +} + +/** + * @brief Normalise un champ facultatif. + * + * Une chaîne vide après nettoyage devient NULL. + */ +static char *entity_record_duplicate_optional( + const char *value +) +{ + char *copy = + NULL; + + copy = + entity_record_duplicate_trimmed( + value + ); + + if (copy != NULL && + copy[0] == '\0') + { + g_free( + copy + ); + + copy = + NULL; + } + + return copy; +} + +/** + * @brief Vérifie qu'une valeur obligatoire est présente. + */ +static gboolean entity_record_required_value_is_valid( + const char *value +) +{ + return value != NULL && + value[0] != '\0'; +} + +/** + * @brief Vérifie une date UTC stricte YYYY-MM-DDTHH:MM:SSZ. + */ +static gboolean entity_record_timestamp_is_valid( + const char *timestamp +) +{ + GDateTime *date_time = + NULL; + + char *normalized_timestamp = + NULL; + + gboolean valid = + FALSE; + + gsize character_index = + 0; + + if (timestamp == NULL || + strlen(timestamp) != 20U) + { + return FALSE; + } + + if (timestamp[4] != '-' || + timestamp[7] != '-' || + timestamp[10] != 'T' || + timestamp[13] != ':' || + timestamp[16] != ':' || + timestamp[19] != 'Z') + { + return FALSE; + } + + for (character_index = 0; + character_index < 20U; + character_index++) + { + if (character_index == 4U || + character_index == 7U || + character_index == 10U || + character_index == 13U || + character_index == 16U || + character_index == 19U) + { + continue; + } + + if (!g_ascii_isdigit( + timestamp[character_index] + )) + { + return FALSE; + } + } + + date_time = + g_date_time_new_from_iso8601( + timestamp, + NULL + ); + + if (date_time == NULL) + { + return FALSE; + } + + normalized_timestamp = + g_date_time_format( + date_time, + "%Y-%m-%dT%H:%M:%SZ" + ); + + valid = + normalized_timestamp != NULL && + g_strcmp0( + normalized_timestamp, + timestamp + ) == 0; + + g_free( + normalized_timestamp + ); + + g_date_time_unref( + date_time + ); + + return valid; +} + +/** + * @brief Vérifie qu'un statut peut être persisté. + */ +static gboolean entity_record_status_is_valid( + EntityStatus status +) +{ + return status == ENTITY_STATUS_ACTIVE || + status == ENTITY_STATUS_ARCHIVED || + status == ENTITY_STATUS_DELETED; +} + +GQuark entity_record_error_quark(void) +{ + return g_quark_from_static_string( + "entity-record-error-quark" + ); +} + +EntityRecord *entity_record_new( + const char *identifier, + const char *type_identifier, + const char *value, + const char *label, + const char *description, + gint confidence, + const char *created_at, + const char *updated_at, + EntityStatus status, + GError **error +) +{ + EntityRecord *entity_record = + NULL; + + char *identifier_copy = + NULL; + + char *type_identifier_copy = + NULL; + + char *value_copy = + NULL; + + char *label_copy = + NULL; + + char *description_copy = + NULL; + + char *created_at_copy = + NULL; + + char *updated_at_copy = + NULL; + + g_return_val_if_fail( + error == NULL || *error == NULL, + NULL + ); + + identifier_copy = + entity_record_duplicate_trimmed( + identifier + ); + + if (!entity_record_required_value_is_valid( + identifier_copy + ) || + !g_uuid_string_is_valid( + identifier_copy + )) + { + g_set_error_literal( + error, + ENTITY_RECORD_ERROR, + ENTITY_RECORD_ERROR_INVALID_IDENTIFIER, + "L'identifiant de l'entité n'est pas un UUID valide." + ); + + goto cleanup; + } + + type_identifier_copy = + entity_record_duplicate_trimmed( + type_identifier + ); + + if (!entity_record_required_value_is_valid( + type_identifier_copy + )) + { + g_set_error_literal( + error, + ENTITY_RECORD_ERROR, + ENTITY_RECORD_ERROR_INVALID_TYPE, + "Le type de l'entité est obligatoire." + ); + + goto cleanup; + } + + value_copy = + entity_record_duplicate_trimmed( + value + ); + + if (!entity_record_required_value_is_valid( + value_copy + )) + { + g_set_error_literal( + error, + ENTITY_RECORD_ERROR, + ENTITY_RECORD_ERROR_INVALID_VALUE, + "La valeur de l'entité est obligatoire." + ); + + goto cleanup; + } + + if (confidence < 0 || + confidence > 100) + { + g_set_error_literal( + error, + ENTITY_RECORD_ERROR, + ENTITY_RECORD_ERROR_INVALID_CONFIDENCE, + "Le niveau de confiance doit être compris entre 0 et 100." + ); + + goto cleanup; + } + + created_at_copy = + entity_record_duplicate_trimmed( + created_at + ); + + if (!entity_record_timestamp_is_valid( + created_at_copy + )) + { + g_set_error_literal( + error, + ENTITY_RECORD_ERROR, + ENTITY_RECORD_ERROR_INVALID_DATE, + "La date de création de l'entité est invalide." + ); + + goto cleanup; + } + + updated_at_copy = + entity_record_duplicate_trimmed( + updated_at + ); + + if (!entity_record_timestamp_is_valid( + updated_at_copy + )) + { + g_set_error_literal( + error, + ENTITY_RECORD_ERROR, + ENTITY_RECORD_ERROR_INVALID_DATE, + "La date de modification de l'entité est invalide." + ); + + goto cleanup; + } + + if (!entity_record_status_is_valid( + status + )) + { + g_set_error_literal( + error, + ENTITY_RECORD_ERROR, + ENTITY_RECORD_ERROR_INVALID_STATUS, + "Le statut de l'entité est invalide." + ); + + goto cleanup; + } + + label_copy = + entity_record_duplicate_optional( + label + ); + + description_copy = + entity_record_duplicate_optional( + description + ); + + entity_record = + g_new0( + EntityRecord, + 1 + ); + + entity_record->identifier = + identifier_copy; + + entity_record->type_identifier = + type_identifier_copy; + + entity_record->value = + value_copy; + + entity_record->label = + label_copy; + + entity_record->description = + description_copy; + + entity_record->confidence = + confidence; + + entity_record->created_at = + created_at_copy; + + entity_record->updated_at = + updated_at_copy; + + entity_record->status = + status; + + /* + * La propriété a été transférée au modèle. + */ + identifier_copy = NULL; + type_identifier_copy = NULL; + value_copy = NULL; + label_copy = NULL; + description_copy = NULL; + created_at_copy = NULL; + updated_at_copy = NULL; + +cleanup: + + g_free( + updated_at_copy + ); + + g_free( + created_at_copy + ); + + g_free( + description_copy + ); + + g_free( + label_copy + ); + + g_free( + value_copy + ); + + g_free( + type_identifier_copy + ); + + g_free( + identifier_copy + ); + + return entity_record; +} + +void entity_record_free( + EntityRecord *entity_record +) +{ + if (entity_record == NULL) + { + return; + } + + g_free( + entity_record->updated_at + ); + + g_free( + entity_record->created_at + ); + + g_free( + entity_record->description + ); + + g_free( + entity_record->label + ); + + g_free( + entity_record->value + ); + + g_free( + entity_record->type_identifier + ); + + g_free( + entity_record->identifier + ); + + g_free( + entity_record + ); +} + +const char *entity_record_get_identifier( + const EntityRecord *entity_record +) +{ + return entity_record != NULL + ? entity_record->identifier + : NULL; +} + +const char *entity_record_get_type_identifier( + const EntityRecord *entity_record +) +{ + return entity_record != NULL + ? entity_record->type_identifier + : NULL; +} + +const char *entity_record_get_value( + const EntityRecord *entity_record +) +{ + return entity_record != NULL + ? entity_record->value + : NULL; +} + +const char *entity_record_get_label( + const EntityRecord *entity_record +) +{ + return entity_record != NULL + ? entity_record->label + : NULL; +} + +const char *entity_record_get_description( + const EntityRecord *entity_record +) +{ + return entity_record != NULL + ? entity_record->description + : NULL; +} + +gint entity_record_get_confidence( + const EntityRecord *entity_record +) +{ + return entity_record != NULL + ? entity_record->confidence + : 0; +} + +const char *entity_record_get_created_at( + const EntityRecord *entity_record +) +{ + return entity_record != NULL + ? entity_record->created_at + : NULL; +} + +const char *entity_record_get_updated_at( + const EntityRecord *entity_record +) +{ + return entity_record != NULL + ? entity_record->updated_at + : NULL; +} + +EntityStatus entity_record_get_status( + const EntityRecord *entity_record +) +{ + return entity_record != NULL + ? entity_record->status + : ENTITY_STATUS_UNKNOWN; +} diff --git a/tests/test_entity_record.c b/tests/test_entity_record.c new file mode 100644 index 0000000..b0b234b --- /dev/null +++ b/tests/test_entity_record.c @@ -0,0 +1,610 @@ +/****************************************************************************** + * @file test_entity_record.c + * @brief Tests unitaires du modèle EntityRecord. + ******************************************************************************/ + +#include "models/entity_record.h" + +#include + +#define TEST_ENTITY_IDENTIFIER \ + "9b6ea3f8-b60b-48ac-908a-94c211f524dd" + +typedef struct +{ + const char *identifier; + const char *type_identifier; + const char *value; + const char *label; + const char *description; + + gint confidence; + + const char *created_at; + const char *updated_at; + + EntityStatus status; +} TestEntityRecordInput; + +static TestEntityRecordInput test_entity_record_valid_input(void) +{ + return (TestEntityRecordInput) + { + .identifier = TEST_ENTITY_IDENTIFIER, + .type_identifier = "email_address", + .value = "suspect@example.org", + .label = "Adresse utilisée pour la transaction", + .description = "Adresse communiquée dans la conversation.", + .confidence = 80, + .created_at = "2026-07-20T19:00:00Z", + .updated_at = "2026-07-20T19:00:00Z", + .status = ENTITY_STATUS_ACTIVE + }; +} + +static EntityRecord *test_entity_record_create( + const TestEntityRecordInput *input, + GError **error +) +{ + g_assert_nonnull( + input + ); + + return entity_record_new( + input->identifier, + input->type_identifier, + input->value, + input->label, + input->description, + input->confidence, + input->created_at, + input->updated_at, + input->status, + error + ); +} + +static void test_entity_record_assert_invalid( + const TestEntityRecordInput *input, + EntityRecordError expected_error +) +{ + EntityRecord *entity_record = + NULL; + + GError *error = + NULL; + + entity_record = + test_entity_record_create( + input, + &error + ); + + g_assert_null( + entity_record + ); + + g_assert_error( + error, + ENTITY_RECORD_ERROR, + (gint) expected_error + ); + + g_clear_error( + &error + ); +} + +static void test_entity_record_valid_full(void) +{ + TestEntityRecordInput input = + test_entity_record_valid_input(); + + EntityRecord *entity_record = + NULL; + + GError *error = + NULL; + + entity_record = + test_entity_record_create( + &input, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + entity_record + ); + + g_assert_cmpstr( + entity_record_get_identifier(entity_record), + ==, + TEST_ENTITY_IDENTIFIER + ); + + g_assert_cmpstr( + entity_record_get_type_identifier(entity_record), + ==, + "email_address" + ); + + g_assert_cmpstr( + entity_record_get_value(entity_record), + ==, + "suspect@example.org" + ); + + g_assert_cmpstr( + entity_record_get_label(entity_record), + ==, + "Adresse utilisée pour la transaction" + ); + + g_assert_cmpstr( + entity_record_get_description(entity_record), + ==, + "Adresse communiquée dans la conversation." + ); + + g_assert_cmpint( + entity_record_get_confidence(entity_record), + ==, + 80 + ); + + g_assert_cmpstr( + entity_record_get_created_at(entity_record), + ==, + "2026-07-20T19:00:00Z" + ); + + g_assert_cmpstr( + entity_record_get_updated_at(entity_record), + ==, + "2026-07-20T19:00:00Z" + ); + + g_assert_cmpint( + entity_record_get_status(entity_record), + ==, + ENTITY_STATUS_ACTIVE + ); + + entity_record_free( + entity_record + ); +} + +static void test_entity_record_optional_fields(void) +{ + TestEntityRecordInput input = + test_entity_record_valid_input(); + + EntityRecord *entity_record = + NULL; + + GError *error = + NULL; + + input.label = " "; + input.description = NULL; + + entity_record = + test_entity_record_create( + &input, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + entity_record + ); + + g_assert_null( + entity_record_get_label( + entity_record + ) + ); + + g_assert_null( + entity_record_get_description( + entity_record + ) + ); + + entity_record_free( + entity_record + ); +} + +static void test_entity_record_values_are_trimmed(void) +{ + TestEntityRecordInput input = + test_entity_record_valid_input(); + + EntityRecord *entity_record = + NULL; + + GError *error = + NULL; + + input.identifier = + " 9b6ea3f8-b60b-48ac-908a-94c211f524dd "; + + input.type_identifier = + " iban "; + + input.value = + " FR7612345678901234567890123 "; + + input.label = + " Compte destinataire "; + + input.description = + " IBAN communiqué par le vendeur. "; + + input.created_at = + " 2026-07-20T19:00:00Z "; + + input.updated_at = + "\t2026-07-20T19:00:00Z\t"; + + entity_record = + test_entity_record_create( + &input, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_cmpstr( + entity_record_get_type_identifier(entity_record), + ==, + "iban" + ); + + g_assert_cmpstr( + entity_record_get_value(entity_record), + ==, + "FR7612345678901234567890123" + ); + + g_assert_cmpstr( + entity_record_get_label(entity_record), + ==, + "Compte destinataire" + ); + + entity_record_free( + entity_record + ); +} + +static void test_entity_record_invalid_uuid(void) +{ + TestEntityRecordInput input = + test_entity_record_valid_input(); + + input.identifier = "uuid-invalide"; + + test_entity_record_assert_invalid( + &input, + ENTITY_RECORD_ERROR_INVALID_IDENTIFIER + ); +} + +static void test_entity_record_missing_type(void) +{ + TestEntityRecordInput input = + test_entity_record_valid_input(); + + input.type_identifier = " "; + + test_entity_record_assert_invalid( + &input, + ENTITY_RECORD_ERROR_INVALID_TYPE + ); +} + +static void test_entity_record_empty_value(void) +{ + TestEntityRecordInput input = + test_entity_record_valid_input(); + + input.value = "\t"; + + test_entity_record_assert_invalid( + &input, + ENTITY_RECORD_ERROR_INVALID_VALUE + ); +} + +static void test_entity_record_confidence_below_range(void) +{ + TestEntityRecordInput input = + test_entity_record_valid_input(); + + input.confidence = -1; + + test_entity_record_assert_invalid( + &input, + ENTITY_RECORD_ERROR_INVALID_CONFIDENCE + ); +} + +static void test_entity_record_confidence_above_range(void) +{ + TestEntityRecordInput input = + test_entity_record_valid_input(); + + input.confidence = 101; + + test_entity_record_assert_invalid( + &input, + ENTITY_RECORD_ERROR_INVALID_CONFIDENCE + ); +} + +static void test_entity_record_invalid_created_at(void) +{ + TestEntityRecordInput input = + test_entity_record_valid_input(); + + input.created_at = "2026-02-30T19:00:00Z"; + + test_entity_record_assert_invalid( + &input, + ENTITY_RECORD_ERROR_INVALID_DATE + ); +} + +static void test_entity_record_invalid_updated_at(void) +{ + TestEntityRecordInput input = + test_entity_record_valid_input(); + + input.updated_at = "2026-07-20 19:00:00Z"; + + test_entity_record_assert_invalid( + &input, + ENTITY_RECORD_ERROR_INVALID_DATE + ); +} + +static void test_entity_record_valid_statuses(void) +{ + EntityStatus statuses[] = + { + ENTITY_STATUS_ACTIVE, + ENTITY_STATUS_ARCHIVED, + ENTITY_STATUS_DELETED + }; + + guint status_index = + 0; + + for (status_index = 0; + status_index < G_N_ELEMENTS(statuses); + status_index++) + { + TestEntityRecordInput input = + test_entity_record_valid_input(); + + EntityRecord *entity_record = + NULL; + + GError *error = + NULL; + + input.status = + statuses[status_index]; + + entity_record = + test_entity_record_create( + &input, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + entity_record + ); + + g_assert_cmpint( + entity_record_get_status(entity_record), + ==, + statuses[status_index] + ); + + entity_record_free( + entity_record + ); + } +} + +static void test_entity_record_invalid_status(void) +{ + TestEntityRecordInput input = + test_entity_record_valid_input(); + + input.status = ENTITY_STATUS_UNKNOWN; + + test_entity_record_assert_invalid( + &input, + ENTITY_RECORD_ERROR_INVALID_STATUS + ); + + input.status = (EntityStatus) 999; + + test_entity_record_assert_invalid( + &input, + ENTITY_RECORD_ERROR_INVALID_STATUS + ); +} + +static void test_entity_record_null_accessors(void) +{ + g_assert_null( + entity_record_get_identifier(NULL) + ); + + g_assert_null( + entity_record_get_type_identifier(NULL) + ); + + g_assert_null( + entity_record_get_value(NULL) + ); + + g_assert_null( + entity_record_get_label(NULL) + ); + + g_assert_null( + entity_record_get_description(NULL) + ); + + g_assert_cmpint( + entity_record_get_confidence(NULL), + ==, + 0 + ); + + g_assert_null( + entity_record_get_created_at(NULL) + ); + + g_assert_null( + entity_record_get_updated_at(NULL) + ); + + g_assert_cmpint( + entity_record_get_status(NULL), + ==, + ENTITY_STATUS_UNKNOWN + ); + + entity_record_free( + NULL + ); +} + +static void test_entity_record_optional_error(void) +{ + TestEntityRecordInput input = + test_entity_record_valid_input(); + + EntityRecord *entity_record = + NULL; + + input.identifier = + "invalide"; + + entity_record = + test_entity_record_create( + &input, + NULL + ); + + g_assert_null( + entity_record + ); +} + +int main( + int argc, + char **argv +) +{ + g_test_init( + &argc, + &argv, + NULL + ); + + g_test_add_func( + "/entity-record/valid-full", + test_entity_record_valid_full + ); + + g_test_add_func( + "/entity-record/optional-fields", + test_entity_record_optional_fields + ); + + g_test_add_func( + "/entity-record/values-are-trimmed", + test_entity_record_values_are_trimmed + ); + + g_test_add_func( + "/entity-record/invalid-uuid", + test_entity_record_invalid_uuid + ); + + g_test_add_func( + "/entity-record/missing-type", + test_entity_record_missing_type + ); + + g_test_add_func( + "/entity-record/empty-value", + test_entity_record_empty_value + ); + + g_test_add_func( + "/entity-record/confidence-below-range", + test_entity_record_confidence_below_range + ); + + g_test_add_func( + "/entity-record/confidence-above-range", + test_entity_record_confidence_above_range + ); + + g_test_add_func( + "/entity-record/invalid-created-at", + test_entity_record_invalid_created_at + ); + + g_test_add_func( + "/entity-record/invalid-updated-at", + test_entity_record_invalid_updated_at + ); + + g_test_add_func( + "/entity-record/valid-statuses", + test_entity_record_valid_statuses + ); + + g_test_add_func( + "/entity-record/invalid-status", + test_entity_record_invalid_status + ); + + g_test_add_func( + "/entity-record/null-accessors", + test_entity_record_null_accessors + ); + + g_test_add_func( + "/entity-record/optional-error", + test_entity_record_optional_error + ); + + return g_test_run(); +}