/** @file test_dashboard.c @brief Core Dashboard characterization parity. */ #include #include #include #include #include #include "trainlog/dashboard.h" #define CHECK(condition) do { if (!(condition)) { \ (void)fprintf(stderr, "CHECK failed at %s:%d: %s\n", \ __FILE__, __LINE__, #condition); return false; } } while (0) static bool dashboard_load(TrainlogDatabase *database, TrainlogDashboardPeriod period, TrainlogDashboardSnapshot *snapshot) { const TrainlogDashboardQuery query = {period, INT64_C(1789560000)}; return trainlog_dashboard_load(database, &query, snapshot) == TRAINLOG_STATUS_OK; } static bool dashboard_insert_completed_set_dose(TrainlogDatabase *database, const char *session_id, const char *started_at, const char *ended_at, TrainlogLoadMode load_mode, const char *equipment_id, double weight, int dose) { TrainlogSetInput set = {dose, 0, true, weight}; TrainlogSessionExerciseInput occurrence; TrainlogSessionInput session; (void)memset(&occurrence, 0, sizeof(occurrence)); (void)memset(&session, 0, sizeof(session)); (void)snprintf(occurrence.exercise_id, sizeof(occurrence.exercise_id), "%s", "ex_33333333-3333-4333-8333-333333333333"); (void)snprintf(occurrence.equipment_id, sizeof(occurrence.equipment_id), "%s", equipment_id); occurrence.recording_mode = TRAINLOG_RECORDING_SETS; occurrence.load_mode = load_mode; occurrence.rest_seconds = 60; occurrence.target_sets = 2; occurrence.target_reps = 10; occurrence.target_has_weight = true; occurrence.target_weight_kg = 999.0; occurrence.sets = &set; occurrence.set_count = 1U; (void)snprintf(session.session_id, sizeof(session.session_id), "%s", session_id); (void)snprintf(session.started_at, sizeof(session.started_at), "%s", started_at); (void)snprintf(session.ended_at, sizeof(session.ended_at), "%s", ended_at); session.session_type = TRAINLOG_SESSION_TRAINING; session.exercises = &occurrence; session.exercise_count = 1U; return trainlog_database_insert_session(database, &session) == TRAINLOG_STATUS_OK; } static bool dashboard_insert_completed_set(TrainlogDatabase *database, const char *session_id, const char *started_at, const char *ended_at, TrainlogLoadMode load_mode, const char *equipment_id, double weight) { return dashboard_insert_completed_set_dose(database, session_id, started_at, ended_at, load_mode, equipment_id, weight, 8); } static bool dashboard_insert_max_for_exercise(TrainlogDatabase *database, const char *session_id, const char *entry_id, const char *exercise_id, const char *started_at, double weight) { TrainlogSessionExerciseInput occurrence; TrainlogSessionInput session; (void)memset(&occurrence, 0, sizeof(occurrence)); (void)memset(&session, 0, sizeof(session)); (void)snprintf(occurrence.entry_id, sizeof(occurrence.entry_id), "%s", entry_id); (void)snprintf(occurrence.exercise_id, sizeof(occurrence.exercise_id), "%s", exercise_id); (void)snprintf(occurrence.equipment_id, sizeof(occurrence.equipment_id), "%s", "leg_press"); occurrence.recording_mode = TRAINLOG_RECORDING_SETS; occurrence.tracking_mode = TRAINLOG_TRACKING_REPS; occurrence.load_mode = TRAINLOG_LOAD_NONE; occurrence.has_max_weight = true; occurrence.max_weight_kg = weight; (void)snprintf(session.session_id, sizeof(session.session_id), "%s", session_id); (void)snprintf(session.started_at, sizeof(session.started_at), "%s", started_at); session.session_type = TRAINLOG_SESSION_MAX_TEST; session.exercises = &occurrence; session.exercise_count = 1U; return trainlog_database_insert_session(database, &session) == TRAINLOG_STATUS_OK; } static bool dashboard_insert_plan_for_exercise(TrainlogDatabase *database, const char *session_id, const char *exercise_id, const char *started_at) { TrainlogSessionExerciseInput occurrence; TrainlogSessionInput session; (void)memset(&occurrence, 0, sizeof(occurrence)); (void)memset(&session, 0, sizeof(session)); (void)snprintf(occurrence.exercise_id, sizeof(occurrence.exercise_id), "%s", exercise_id); (void)snprintf(occurrence.equipment_id, sizeof(occurrence.equipment_id), "%s", "leg_press"); occurrence.recording_mode = TRAINLOG_RECORDING_SETS; occurrence.tracking_mode = TRAINLOG_TRACKING_REPS; occurrence.load_mode = TRAINLOG_LOAD_EXTERNAL; occurrence.target_sets = 3; occurrence.target_reps = 10; occurrence.target_has_weight = true; occurrence.target_weight_kg = 50.0; (void)snprintf(session.session_id, sizeof(session.session_id), "%s", session_id); (void)snprintf(session.started_at, sizeof(session.started_at), "%s", started_at); session.session_type = TRAINLOG_SESSION_TRAINING; session.exercises = &occurrence; session.exercise_count = 1U; return trainlog_database_insert_session(database, &session) == TRAINLOG_STATUS_OK; } static bool dashboard_insert_optional_weight_set(TrainlogDatabase *database, const char *session_id, const char *exercise_id, const char *started_at, bool has_weight, double weight) { TrainlogSetInput set = {10, 0, has_weight, weight}; TrainlogSessionExerciseInput occurrence; TrainlogSessionInput session; (void)memset(&occurrence, 0, sizeof(occurrence)); (void)memset(&session, 0, sizeof(session)); (void)snprintf(occurrence.exercise_id, sizeof(occurrence.exercise_id), "%s", exercise_id); (void)snprintf(occurrence.equipment_id, sizeof(occurrence.equipment_id), "%s", "leg_press"); occurrence.recording_mode = TRAINLOG_RECORDING_SETS; occurrence.tracking_mode = TRAINLOG_TRACKING_REPS; occurrence.load_mode = TRAINLOG_LOAD_EXTERNAL; occurrence.target_sets = 1; occurrence.target_reps = 10; occurrence.target_has_weight = true; occurrence.target_weight_kg = 25.0; occurrence.sets = &set; occurrence.set_count = 1U; (void)snprintf(session.session_id, sizeof(session.session_id), "%s", session_id); (void)snprintf(session.started_at, sizeof(session.started_at), "%s", started_at); session.session_type = TRAINLOG_SESSION_TRAINING; session.exercises = &occurrence; session.exercise_count = 1U; return trainlog_database_insert_session(database, &session) == TRAINLOG_STATUS_OK; } static bool test_dashboard_characterization_empty_and_window_boundaries(void) { TrainlogDatabase *database = NULL; TrainlogDashboardSnapshot dashboard; size_t index; CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK); (void)memset(&dashboard, 0, sizeof(dashboard)); dashboard.period = TRAINLOG_DASHBOARD_30_DAYS; CHECK(dashboard_load(database, dashboard.period, &dashboard)); CHECK(!dashboard.error && !dashboard.partial && !dashboard.invalid_data); CHECK(!dashboard.has_performance && !dashboard.has_explicit_max && !dashboard.has_body); CHECK(dashboard.performance_count == 0U && dashboard.body_count == 0U); CHECK(dashboard.completed_session_count == 0U && dashboard.performed_set_count == 0U && dashboard.distinct_exercise_count == 0U && dashboard.explicit_max_count == 0U); /* The database is empty of training history, not of the seeded canonical * catalogue: zone distribution is therefore deliberately non-empty. */ CHECK(dashboard.zone_count == 4U); CHECK(dashboard.zones[0].zone_id[0] == '\0' && dashboard.zones[0].count == 2U); CHECK(dashboard.week_count == 6U); for (index = 0U; index < dashboard.week_count; ++index) CHECK(dashboard.weeks[index].sessions == 0U && dashboard.weeks[index].maxima == 0U && dashboard.weeks[index].working_improvements == 0U && dashboard.weeks[index].max_improvements == 0U); CHECK(trainlog_database_insert_exercise_profiled(database, "ex_33333333-3333-4333-8333-333333333333", "Frontière", "frontiere", TRAINLOG_TRACKING_REPS, TRAINLOG_RECORDING_SETS, (TrainlogExerciseDataFields)0) == TRAINLOG_STATUS_OK); CHECK(dashboard_insert_completed_set(database, "se_31000000-0000-4000-8000-000000000001", "2026-08-17T11:59:59Z", "", TRAINLOG_LOAD_EXTERNAL, "leg_press", 10.0)); CHECK(dashboard_insert_completed_set(database, "se_31000000-0000-4000-8000-000000000002", "2026-08-17T12:00:00Z", "", TRAINLOG_LOAD_EXTERNAL, "leg_press", 20.0)); CHECK(dashboard_insert_completed_set(database, "se_31000000-0000-4000-8000-000000000003", "2026-08-17T12:00:01Z", "", TRAINLOG_LOAD_EXTERNAL, "leg_press", 30.0)); CHECK(dashboard_load(database, dashboard.period, &dashboard)); CHECK(!dashboard.error && !dashboard.invalid_data); CHECK(dashboard.completed_session_count == 2U); CHECK(dashboard.performed_set_count == 2U); CHECK(dashboard.distinct_exercise_count == 1U); CHECK(dashboard.has_performance && dashboard.performance_count == 2U); CHECK(strcmp(dashboard.performance[0].started_at, "2026-08-17T12:00:00Z") == 0); CHECK(strcmp(dashboard.performance[1].started_at, "2026-08-17T12:00:01Z") == 0); trainlog_database_close(database); return true; } static bool test_dashboard_characterization_modalities_ties_and_zones(void) { static const char *const primary_secondary[] = {"back"}; TrainlogDatabase *database = NULL; TrainlogDashboardSnapshot dashboard; TrainlogSetInput rep_sets[] = { {10, 0, true, 40.0}, {8, 0, true, 1000.0} }; TrainlogSetInput repeated_set = {12, 0, true, 50.0}; TrainlogSetInput duration_set = {0, 60, true, 30.0}; TrainlogSetInput assistance_set = {10, 0, true, 5.0}; TrainlogSetInput unweighted_set = {10, 0, false, 0.0}; TrainlogSessionExerciseInput occurrences[4]; TrainlogSessionInput session; bool chest = false; bool back = false; bool unclassified = false; size_t working = 0U; size_t index; CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK); CHECK(trainlog_database_insert_exercise_profiled(database, "ex_10000000-0000-4000-8000-000000000000", "MAX égalité", "max egalite", TRAINLOG_TRACKING_REPS, TRAINLOG_RECORDING_SETS, (TrainlogExerciseDataFields)0) == TRAINLOG_STATUS_OK); CHECK(trainlog_database_insert_exercise_profiled(database, "ex_33333333-3333-4333-8333-333333333333", "Répétitions", "repetitions", TRAINLOG_TRACKING_REPS, TRAINLOG_RECORDING_SETS, (TrainlogExerciseDataFields)0) == TRAINLOG_STATUS_OK); CHECK(trainlog_database_insert_exercise_profiled(database, "ex_10000000-0000-4000-8000-000000000001", "MAX secondaire", "max secondaire", TRAINLOG_TRACKING_REPS, TRAINLOG_RECORDING_SETS, (TrainlogExerciseDataFields)0) == TRAINLOG_STATUS_OK); CHECK(trainlog_database_insert_exercise_profiled(database, "ex_10000000-0000-4000-8000-000000000002", "Durée séries", "duree series", TRAINLOG_TRACKING_DURATION, TRAINLOG_RECORDING_SETS, (TrainlogExerciseDataFields)0) == TRAINLOG_STATUS_OK); CHECK(trainlog_database_insert_exercise_profiled(database, "ex_10000000-0000-4000-8000-000000000003", "Continu", "continu", TRAINLOG_TRACKING_DURATION, TRAINLOG_RECORDING_CONTINUOUS, (TrainlogExerciseDataFields)0) == TRAINLOG_STATUS_OK); CHECK(trainlog_database_insert_exercise_profiled(database, "ex_10000000-0000-4000-8000-000000000004", "Assistance", "assistance", TRAINLOG_TRACKING_REPS, TRAINLOG_RECORDING_SETS, (TrainlogExerciseDataFields)0) == TRAINLOG_STATUS_OK); CHECK(trainlog_database_insert_exercise_profiled(database, "ex_10000000-0000-4000-8000-000000000005", "Poids absent antérieur", "poids absent anterieur", TRAINLOG_TRACKING_REPS, TRAINLOG_RECORDING_SETS, (TrainlogExerciseDataFields)0) == TRAINLOG_STATUS_OK); CHECK(trainlog_database_replace_exercise_body_zones(database, "ex_33333333-3333-4333-8333-333333333333", "chest", primary_secondary, 1U) == TRAINLOG_STATUS_OK); CHECK(trainlog_database_replace_exercise_body_zones(database, "ex_10000000-0000-4000-8000-000000000002", "back", NULL, 0U) == TRAINLOG_STATUS_OK); CHECK(trainlog_database_replace_exercise_body_zones(database, "ex_10000000-0000-4000-8000-000000000004", "chest", NULL, 0U) == TRAINLOG_STATUS_OK); (void)memset(occurrences, 0, sizeof(occurrences)); (void)memset(&session, 0, sizeof(session)); for (index = 0U; index < 2U; ++index) { (void)snprintf(occurrences[index].exercise_id, sizeof(occurrences[index].exercise_id), "%s", "ex_33333333-3333-4333-8333-333333333333"); (void)snprintf(occurrences[index].equipment_id, sizeof(occurrences[index].equipment_id), "%s", "leg_press"); occurrences[index].recording_mode = TRAINLOG_RECORDING_SETS; occurrences[index].tracking_mode = TRAINLOG_TRACKING_REPS; occurrences[index].load_mode = TRAINLOG_LOAD_EXTERNAL; occurrences[index].target_sets = 1; occurrences[index].target_reps = 10; occurrences[index].target_has_weight = true; occurrences[index].target_weight_kg = 40.0; } (void)snprintf(occurrences[0].entry_id, sizeof(occurrences[0].entry_id), "%s", "sxe_10000000-0000-4000-8000-000000000001"); occurrences[0].sets = rep_sets; occurrences[0].set_count = 2U; (void)snprintf(occurrences[1].entry_id, sizeof(occurrences[1].entry_id), "%s", "sxe_10000000-0000-4000-8000-000000000002"); occurrences[1].sets = &repeated_set; occurrences[1].set_count = 1U; (void)snprintf(occurrences[2].exercise_id, sizeof(occurrences[2].exercise_id), "%s", "ex_10000000-0000-4000-8000-000000000002"); (void)snprintf(occurrences[2].equipment_id, sizeof(occurrences[2].equipment_id), "%s", "leg_press"); occurrences[2].recording_mode = TRAINLOG_RECORDING_SETS; occurrences[2].tracking_mode = TRAINLOG_TRACKING_DURATION; occurrences[2].load_mode = TRAINLOG_LOAD_EXTERNAL; occurrences[2].target_sets = 1; occurrences[2].target_duration_seconds = 60; occurrences[2].target_has_weight = true; occurrences[2].target_weight_kg = 30.0; occurrences[2].sets = &duration_set; occurrences[2].set_count = 1U; (void)snprintf(occurrences[3].exercise_id, sizeof(occurrences[3].exercise_id), "%s", "ex_10000000-0000-4000-8000-000000000003"); occurrences[3].recording_mode = TRAINLOG_RECORDING_CONTINUOUS; occurrences[3].tracking_mode = TRAINLOG_TRACKING_DURATION; occurrences[3].load_mode = TRAINLOG_LOAD_NONE; occurrences[3].continuous_duration_seconds = 600; (void)snprintf(session.session_id, sizeof(session.session_id), "%s", "se_32000000-0000-4000-8000-000000000001"); (void)snprintf(session.started_at, sizeof(session.started_at), "%s", "2026-09-10T08:00:00Z"); session.session_type = TRAINLOG_SESSION_TRAINING; session.exercises = occurrences; session.exercise_count = 4U; CHECK(trainlog_database_insert_session(database, &session) == TRAINLOG_STATUS_OK); CHECK(dashboard_insert_optional_weight_set(database, "se_32000000-0000-4000-8000-000000000009", "ex_10000000-0000-4000-8000-000000000005", "2026-09-08T08:00:00Z", false, 0.0)); CHECK(dashboard_insert_optional_weight_set(database, "se_32000000-0000-4000-8000-000000000010", "ex_10000000-0000-4000-8000-000000000005", "2026-09-09T08:00:00Z", true, 25.0)); (void)memset(&occurrences[0], 0, sizeof(occurrences[0])); (void)memset(&session, 0, sizeof(session)); (void)snprintf(occurrences[0].exercise_id, sizeof(occurrences[0].exercise_id), "%s", "ex_10000000-0000-4000-8000-000000000004"); (void)snprintf(occurrences[0].equipment_id, sizeof(occurrences[0].equipment_id), "%s", "assisted_dip_chin_machine"); occurrences[0].recording_mode = TRAINLOG_RECORDING_SETS; occurrences[0].tracking_mode = TRAINLOG_TRACKING_REPS; occurrences[0].load_mode = TRAINLOG_LOAD_ASSISTANCE; occurrences[0].target_sets = 1; occurrences[0].target_reps = 10; occurrences[0].target_has_weight = true; occurrences[0].target_weight_kg = 5.0; occurrences[0].sets = &assistance_set; occurrences[0].set_count = 1U; (void)snprintf(session.session_id, sizeof(session.session_id), "%s", "se_32000000-0000-4000-8000-000000000002"); (void)snprintf(session.started_at, sizeof(session.started_at), "%s", "2026-09-11T08:00:00Z"); (void)snprintf(session.ended_at, sizeof(session.ended_at), "%s", "2026-09-11T09:00:00Z"); session.session_type = TRAINLOG_SESSION_TRAINING; session.exercises = occurrences; session.exercise_count = 1U; CHECK(trainlog_database_insert_session(database, &session) == TRAINLOG_STATUS_OK); CHECK(dashboard_insert_plan_for_exercise(database, "se_32000000-0000-4000-8000-000000000003", "ex_10000000-0000-4000-8000-000000000001", "2026-09-12T08:00:00Z")); (void)memset(&occurrences[0], 0, sizeof(occurrences[0])); (void)memset(&session, 0, sizeof(session)); (void)snprintf(occurrences[0].exercise_id, sizeof(occurrences[0].exercise_id), "%s", "ex_33333333-3333-4333-8333-333333333333"); (void)snprintf(occurrences[0].equipment_id, sizeof(occurrences[0].equipment_id), "%s", "leg_press"); occurrences[0].recording_mode = TRAINLOG_RECORDING_SETS; occurrences[0].tracking_mode = TRAINLOG_TRACKING_REPS; occurrences[0].load_mode = TRAINLOG_LOAD_EXTERNAL; occurrences[0].target_sets = 1; occurrences[0].target_reps = 10; occurrences[0].target_has_weight = true; occurrences[0].target_weight_kg = 40.0; occurrences[0].sets = &unweighted_set; occurrences[0].set_count = 1U; (void)snprintf(session.session_id, sizeof(session.session_id), "%s", "se_32000000-0000-4000-8000-000000000008"); (void)snprintf(session.started_at, sizeof(session.started_at), "%s", "2026-09-12T12:00:00Z"); session.session_type = TRAINLOG_SESSION_TRAINING; session.exercises = occurrences; session.exercise_count = 1U; CHECK(trainlog_database_insert_session(database, &session) == TRAINLOG_STATUS_OK); CHECK(dashboard_insert_completed_set_dose(database, "se_32000000-0000-4000-8000-000000000004", "2026-09-13T08:00:00Z", "", TRAINLOG_LOAD_EXTERNAL, "leg_press", 45.0, 10)); CHECK(dashboard_insert_completed_set_dose(database, "se_32000000-0000-4000-8000-000000000005", "2026-09-13T08:00:00Z", "", TRAINLOG_LOAD_EXTERNAL, "leg_press", 50.0, 10)); CHECK(dashboard_insert_max_for_exercise(database, "se_32000000-0000-4000-8000-000000000006", "sxe_32000000-0000-4000-8000-000000000006", "ex_10000000-0000-4000-8000-000000000001", "2026-09-14T08:00:00Z", 100.0)); CHECK(dashboard_insert_max_for_exercise(database, "se_32000000-0000-4000-8000-000000000007", "sxe_32000000-0000-4000-8000-000000000007", "ex_10000000-0000-4000-8000-000000000000", "2026-09-14T08:00:00Z", 200.0)); (void)memset(&dashboard, 0, sizeof(dashboard)); dashboard.period = TRAINLOG_DASHBOARD_30_DAYS; CHECK(dashboard_load(database, dashboard.period, &dashboard)); CHECK(!dashboard.error && !dashboard.partial && !dashboard.invalid_data); CHECK(dashboard.completed_session_count == 9U); CHECK(dashboard.performed_set_count == 10U); CHECK(dashboard.explicit_max_count == 2U); CHECK(dashboard.distinct_exercise_count == 7U); CHECK(dashboard.has_performance); CHECK(strcmp(dashboard.exercise.exercise_id, "ex_33333333-3333-4333-8333-333333333333") == 0); CHECK(dashboard.performance_tracking_mode == TRAINLOG_TRACKING_REPS && dashboard.performance_dose == 10); CHECK(dashboard.performance_count == 4U); CHECK(dashboard.performance[0].weight_kg == 40.0 && dashboard.performance[1].has_weight == 1 && dashboard.performance[1].weight_kg == 0.0 && dashboard.performance[2].weight_kg == 45.0 && dashboard.performance[3].weight_kg == 50.0); CHECK(dashboard.has_explicit_max && dashboard.max_weight_kg == 200.0); CHECK(strcmp(dashboard.max_exercise.exercise_id, "ex_10000000-0000-4000-8000-000000000000") == 0); for (index = 0U; index < dashboard.week_count; ++index) working += dashboard.weeks[index].working_improvements; /* Existing behavior: a matching earlier unweighted set supplies a 0.0 * baseline and therefore makes the later weighted set an improvement. */ CHECK(working == 3U); CHECK(dashboard.zone_count == 4U); for (index = 0U; index < dashboard.zone_count; ++index) { chest = chest || (strcmp(dashboard.zones[index].zone_id, "chest") == 0 && dashboard.zones[index].count == 3U); back = back || (strcmp(dashboard.zones[index].zone_id, "back") == 0 && dashboard.zones[index].count == 2U); unclassified = unclassified || (dashboard.zones[index].zone_id[0] == '\0' && dashboard.zones[index].count == 6U); } CHECK(chest && back && unclassified); trainlog_database_close(database); return true; } static bool test_dashboard_characterization_session_and_fact_bounds(void) { TrainlogDatabase *database = NULL; TrainlogDashboardSnapshot dashboard; TrainlogSetInput *sets = NULL; TrainlogSessionExerciseInput occurrence; TrainlogSessionInput session; size_t index; CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK); CHECK(trainlog_database_insert_exercise_profiled(database, "ex_33333333-3333-4333-8333-333333333333", "Bornes", "bornes", TRAINLOG_TRACKING_REPS, TRAINLOG_RECORDING_SETS, (TrainlogExerciseDataFields)0) == TRAINLOG_STATUS_OK); for (index = 0U; index < TRAINLOG_DASHBOARD_SESSION_CAPACITY; ++index) { char session_id[TRAINLOG_ID_MAX + 1U]; (void)snprintf(session_id, sizeof(session_id), "se_33000000-0000-4000-8000-%012zu", index); CHECK(dashboard_insert_completed_set(database, session_id, "2026-09-10T08:00:00Z", "", TRAINLOG_LOAD_NONE, "", 1.0)); } (void)memset(&dashboard, 0, sizeof(dashboard)); dashboard.period = TRAINLOG_DASHBOARD_ALL; CHECK(dashboard_load(database, dashboard.period, &dashboard)); CHECK(!dashboard.error && dashboard.partial); CHECK(dashboard.completed_session_count == TRAINLOG_DASHBOARD_SESSION_CAPACITY); CHECK(dashboard.performed_set_count == TRAINLOG_DASHBOARD_SESSION_CAPACITY); CHECK(dashboard_insert_completed_set(database, "se_33000000-0000-4000-8000-000000000128", "2026-09-10T08:00:00Z", "", TRAINLOG_LOAD_NONE, "", 1.0)); CHECK(dashboard_load(database, dashboard.period, &dashboard)); CHECK(!dashboard.error && dashboard.partial); CHECK(dashboard.completed_session_count == TRAINLOG_DASHBOARD_SESSION_CAPACITY); CHECK(dashboard.performed_set_count == TRAINLOG_DASHBOARD_SESSION_CAPACITY + 1U); trainlog_database_close(database); CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK); CHECK(trainlog_database_insert_exercise_profiled(database, "ex_33333333-3333-4333-8333-333333333333", "Faits bornés", "faits bornes", TRAINLOG_TRACKING_REPS, TRAINLOG_RECORDING_SETS, (TrainlogExerciseDataFields)0) == TRAINLOG_STATUS_OK); sets = calloc(TRAINLOG_DASHBOARD_FACT_CAPACITY, sizeof(*sets)); CHECK(sets != NULL); (void)memset(&occurrence, 0, sizeof(occurrence)); (void)memset(&session, 0, sizeof(session)); (void)snprintf(occurrence.exercise_id, sizeof(occurrence.exercise_id), "%s", "ex_33333333-3333-4333-8333-333333333333"); occurrence.recording_mode = TRAINLOG_RECORDING_SETS; occurrence.tracking_mode = TRAINLOG_TRACKING_REPS; occurrence.load_mode = TRAINLOG_LOAD_NONE; occurrence.target_sets = 1; occurrence.target_reps = 1; occurrence.sets = sets; occurrence.set_count = TRAINLOG_DASHBOARD_FACT_CAPACITY; (void)snprintf(session.session_id, sizeof(session.session_id), "%s", "se_34000000-0000-4000-8000-000000000001"); (void)snprintf(session.started_at, sizeof(session.started_at), "%s", "2026-09-10T08:00:00Z"); session.session_type = TRAINLOG_SESSION_TRAINING; session.exercises = &occurrence; session.exercise_count = 1U; CHECK(trainlog_database_insert_session(database, &session) == TRAINLOG_STATUS_OK); free(sets); sets = NULL; (void)memset(&dashboard, 0, sizeof(dashboard)); dashboard.period = TRAINLOG_DASHBOARD_ALL; CHECK(dashboard_load(database, dashboard.period, &dashboard)); CHECK(!dashboard.error && !dashboard.partial); CHECK(dashboard.performed_set_count == TRAINLOG_DASHBOARD_FACT_CAPACITY); /* A second honest persisted occurrence crosses the fact capacity without * bypassing the public persistence contract. */ CHECK(dashboard_insert_completed_set(database, "se_34000000-0000-4000-8000-000000000002", "2026-09-10T08:00:01Z", "", TRAINLOG_LOAD_NONE, "", 1.0)); CHECK(dashboard_load(database, dashboard.period, &dashboard)); CHECK(!dashboard.error && dashboard.partial); CHECK(dashboard.performed_set_count == TRAINLOG_DASHBOARD_FACT_CAPACITY); trainlog_database_close(database); return true; } int main(void) { if (!test_dashboard_characterization_empty_and_window_boundaries() || !test_dashboard_characterization_modalities_ties_and_zones() || !test_dashboard_characterization_session_and_fact_bounds()) return 1; return 0; }