364 lines
8.4 KiB
C
364 lines
8.4 KiB
C
/**
|
|
* @file test_session_edit.c
|
|
* @brief Transaction-safe persisted session editing tests.
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "trainlog/database.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 add_exercises(
|
|
TrainlogDatabase *database
|
|
)
|
|
{
|
|
CHECK(
|
|
trainlog_database_insert_exercise(
|
|
database,
|
|
"ex_press",
|
|
"Presse",
|
|
"presse",
|
|
TRAINLOG_TRACKING_REPS
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(
|
|
trainlog_database_insert_exercise(
|
|
database,
|
|
"ex_plank",
|
|
"Gainage",
|
|
"gainage",
|
|
TRAINLOG_TRACKING_DURATION
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void bind_reps_exercise(
|
|
TrainlogSessionExerciseInput *exercise,
|
|
TrainlogSetInput *sets,
|
|
size_t set_count,
|
|
double weight
|
|
)
|
|
{
|
|
size_t index;
|
|
|
|
(void)memset(
|
|
exercise,
|
|
0,
|
|
sizeof(*exercise)
|
|
);
|
|
|
|
(void)snprintf(
|
|
exercise->exercise_id,
|
|
sizeof(exercise->exercise_id),
|
|
"%s",
|
|
"ex_press"
|
|
);
|
|
|
|
exercise->load_mode =
|
|
TRAINLOG_LOAD_EXTERNAL;
|
|
|
|
exercise->rest_seconds = 90;
|
|
exercise->target_sets = 3;
|
|
exercise->target_reps = 10;
|
|
exercise->target_has_weight = true;
|
|
exercise->target_weight_kg = weight;
|
|
exercise->notes = "note presse";
|
|
exercise->sets = sets;
|
|
exercise->set_count = set_count;
|
|
|
|
for (index = 0U;
|
|
index < set_count;
|
|
++index) {
|
|
(void)memset(
|
|
&sets[index],
|
|
0,
|
|
sizeof(sets[index])
|
|
);
|
|
|
|
sets[index].reps =
|
|
10 - (int)index;
|
|
|
|
sets[index].has_weight = true;
|
|
sets[index].weight_kg = weight;
|
|
}
|
|
}
|
|
|
|
static bool test_load_replace_and_rollback(void)
|
|
{
|
|
TrainlogDatabase *database = NULL;
|
|
TrainlogSetInput original_sets[3];
|
|
TrainlogSetInput replacement_sets[2];
|
|
TrainlogSetInput rollback_sets[1];
|
|
TrainlogSessionExerciseInput original;
|
|
TrainlogSessionExerciseInput replacement;
|
|
TrainlogSessionExerciseInput bad[2];
|
|
TrainlogSessionInput session;
|
|
TrainlogBodyObservationInput body;
|
|
|
|
TrainlogSessionSummary loaded_session;
|
|
TrainlogEditableExerciseRecord loaded_exercises[4];
|
|
TrainlogSetInput loaded_sets[16];
|
|
size_t exercise_count = 0U;
|
|
size_t set_count = 0U;
|
|
|
|
TrainlogBodyMetricPoint weight_points[4];
|
|
size_t weight_count = 0U;
|
|
|
|
CHECK(
|
|
trainlog_database_open(
|
|
":memory:",
|
|
&database
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(add_exercises(database));
|
|
|
|
bind_reps_exercise(
|
|
&original,
|
|
original_sets,
|
|
3U,
|
|
80.0
|
|
);
|
|
|
|
(void)memset(
|
|
&session,
|
|
0,
|
|
sizeof(session)
|
|
);
|
|
|
|
(void)snprintf(
|
|
session.session_id,
|
|
sizeof(session.session_id),
|
|
"%s",
|
|
"se_edit"
|
|
);
|
|
|
|
(void)snprintf(
|
|
session.started_at,
|
|
sizeof(session.started_at),
|
|
"%s",
|
|
"2026-09-06T08:00:00+02:00"
|
|
);
|
|
|
|
(void)snprintf(
|
|
session.ended_at,
|
|
sizeof(session.ended_at),
|
|
"%s",
|
|
"2026-09-06T09:00:00+02:00"
|
|
);
|
|
|
|
session.session_type =
|
|
TRAINLOG_SESSION_TRAINING;
|
|
|
|
session.exercises = &original;
|
|
session.exercise_count = 1U;
|
|
|
|
CHECK(
|
|
trainlog_database_insert_session(
|
|
database,
|
|
&session
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
(void)memset(&body, 0, sizeof(body));
|
|
|
|
(void)snprintf(
|
|
body.observation_id,
|
|
sizeof(body.observation_id),
|
|
"%s",
|
|
"bo_edit"
|
|
);
|
|
|
|
(void)snprintf(
|
|
body.observed_at,
|
|
sizeof(body.observed_at),
|
|
"%s",
|
|
"2026-09-06T09:05:00+02:00"
|
|
);
|
|
|
|
body.session_id = "se_edit";
|
|
body.has_body_weight = true;
|
|
body.body_weight_kg = 84.1;
|
|
|
|
CHECK(
|
|
trainlog_database_insert_body_observation(
|
|
database,
|
|
&body
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(
|
|
trainlog_database_load_session_editable(
|
|
database,
|
|
"se_edit",
|
|
&loaded_session,
|
|
loaded_exercises,
|
|
4U,
|
|
&exercise_count,
|
|
loaded_sets,
|
|
16U,
|
|
&set_count
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(exercise_count == 1U);
|
|
CHECK(set_count == 3U);
|
|
CHECK(
|
|
strcmp(
|
|
loaded_exercises[0].exercise_id,
|
|
"ex_press"
|
|
) == 0
|
|
);
|
|
CHECK(
|
|
strcmp(
|
|
loaded_exercises[0].notes,
|
|
"note presse"
|
|
) == 0
|
|
);
|
|
CHECK(loaded_exercises[0].set_offset == 0U);
|
|
CHECK(loaded_exercises[0].set_count == 3U);
|
|
CHECK(loaded_sets[0].reps == 10);
|
|
CHECK(loaded_sets[2].reps == 8);
|
|
CHECK(loaded_sets[0].weight_kg > 79.99);
|
|
CHECK(loaded_sets[0].weight_kg < 80.01);
|
|
|
|
bind_reps_exercise(
|
|
&replacement,
|
|
replacement_sets,
|
|
2U,
|
|
85.0
|
|
);
|
|
|
|
replacement_sets[0].reps = 12;
|
|
replacement_sets[1].reps = 11;
|
|
|
|
CHECK(
|
|
trainlog_database_replace_session_exercises(
|
|
database,
|
|
"se_edit",
|
|
&replacement,
|
|
1U
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(
|
|
trainlog_database_load_session_editable(
|
|
database,
|
|
"se_edit",
|
|
&loaded_session,
|
|
loaded_exercises,
|
|
4U,
|
|
&exercise_count,
|
|
loaded_sets,
|
|
16U,
|
|
&set_count
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(exercise_count == 1U);
|
|
CHECK(set_count == 2U);
|
|
CHECK(loaded_sets[0].reps == 12);
|
|
CHECK(loaded_sets[1].reps == 11);
|
|
CHECK(loaded_sets[0].weight_kg > 84.99);
|
|
CHECK(loaded_sets[0].weight_kg < 85.01);
|
|
|
|
/*
|
|
* The body observation is linked to the stable sessions row. Replacing
|
|
* child exercise/set rows must not delete or detach it.
|
|
*/
|
|
CHECK(
|
|
trainlog_database_list_body_metric_points(
|
|
database,
|
|
TRAINLOG_BODY_METRIC_WEIGHT,
|
|
weight_points,
|
|
4U,
|
|
&weight_count
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(weight_count == 1U);
|
|
CHECK(weight_points[0].value > 84.09);
|
|
CHECK(weight_points[0].value < 84.11);
|
|
|
|
bind_reps_exercise(
|
|
&bad[0],
|
|
rollback_sets,
|
|
1U,
|
|
95.0
|
|
);
|
|
|
|
bad[1] = bad[0];
|
|
|
|
(void)snprintf(
|
|
bad[1].exercise_id,
|
|
sizeof(bad[1].exercise_id),
|
|
"%s",
|
|
"ex_missing"
|
|
);
|
|
|
|
CHECK(
|
|
trainlog_database_replace_session_exercises(
|
|
database,
|
|
"se_edit",
|
|
bad,
|
|
2U
|
|
) == TRAINLOG_STATUS_NOT_FOUND
|
|
);
|
|
|
|
/*
|
|
* The failed replacement deleted and reinserted rows inside one
|
|
* transaction. Rollback must restore the previous 85 kg / 12,11 data.
|
|
*/
|
|
CHECK(
|
|
trainlog_database_load_session_editable(
|
|
database,
|
|
"se_edit",
|
|
&loaded_session,
|
|
loaded_exercises,
|
|
4U,
|
|
&exercise_count,
|
|
loaded_sets,
|
|
16U,
|
|
&set_count
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(exercise_count == 1U);
|
|
CHECK(set_count == 2U);
|
|
CHECK(loaded_sets[0].reps == 12);
|
|
CHECK(loaded_sets[1].reps == 11);
|
|
CHECK(loaded_sets[0].weight_kg > 84.99);
|
|
CHECK(loaded_sets[0].weight_kg < 85.01);
|
|
|
|
trainlog_database_close(database);
|
|
return true;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
CHECK(test_load_replace_and_rollback());
|
|
|
|
(void)printf(
|
|
"PASS session_edit\n"
|
|
);
|
|
|
|
return 0;
|
|
}
|