trainlog/tui/tests/test_exercise_profile_schema.c

288 lines
7.9 KiB
C

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sqlite3.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 test_profile_roundtrip(void)
{
TrainlogDatabase *database = NULL;
TrainlogExercise exercises[4];
size_t count = 0U;
size_t index;
bool found_walk = false;
bool found_legacy = false;
CHECK(
trainlog_database_open(
":memory:",
&database
) == TRAINLOG_STATUS_OK
);
CHECK(
trainlog_database_insert_exercise(
database,
"ex_legacy",
"Presse",
"presse",
TRAINLOG_TRACKING_REPS
) == TRAINLOG_STATUS_OK
);
CHECK(
trainlog_database_insert_exercise_profiled(
database,
"ex_walk",
"Marche",
"marche",
TRAINLOG_TRACKING_DURATION,
TRAINLOG_RECORDING_CONTINUOUS,
TRAINLOG_EXERCISE_DATA_SPEED_KMH
) == TRAINLOG_STATUS_OK
);
CHECK(
trainlog_database_insert_exercise_profiled(
database,
"ex_invalid",
"Invalid",
"invalid",
TRAINLOG_TRACKING_REPS,
TRAINLOG_RECORDING_CONTINUOUS,
0U
) == TRAINLOG_STATUS_INVALID_ARGUMENT
);
CHECK(
trainlog_database_insert_exercise_profiled(
database,
"ex_unknown",
"Unknown",
"unknown",
TRAINLOG_TRACKING_DURATION,
TRAINLOG_RECORDING_CONTINUOUS,
UINT32_C(8)
) == TRAINLOG_STATUS_INVALID_ARGUMENT
);
CHECK(
trainlog_database_list_exercises(
database,
exercises,
4U,
&count
) == TRAINLOG_STATUS_OK
);
CHECK(count == 2U);
for (index = 0U; index < count; ++index) {
if (strcmp(exercises[index].exercise_id, "ex_walk") == 0) {
CHECK(
exercises[index].recording_mode ==
TRAINLOG_RECORDING_CONTINUOUS
);
CHECK(
exercises[index].tracking_mode ==
TRAINLOG_TRACKING_DURATION
);
CHECK(
exercises[index].data_fields ==
TRAINLOG_EXERCISE_DATA_SPEED_KMH
);
found_walk = true;
}
if (strcmp(exercises[index].exercise_id, "ex_legacy") == 0) {
CHECK(
exercises[index].recording_mode ==
TRAINLOG_RECORDING_SETS
);
CHECK(exercises[index].data_fields == 0U);
found_legacy = true;
}
}
CHECK(found_walk);
CHECK(found_legacy);
trainlog_database_close(database);
return true;
}
static bool test_v2_to_v3_migration(void)
{
char path[] =
"/tmp/trainlog-schema-v2-profile-XXXXXX";
static const char *const V2_SQL =
"CREATE TABLE exercises ("
"id INTEGER PRIMARY KEY,"
"exercise_id TEXT NOT NULL UNIQUE,"
"name TEXT NOT NULL,"
"normalized_name TEXT NOT NULL UNIQUE,"
"tracking_mode TEXT NOT NULL"
");"
"CREATE TABLE sessions ("
"id INTEGER PRIMARY KEY,"
"session_id TEXT NOT NULL UNIQUE,"
"started_at TEXT NOT NULL,"
"ended_at TEXT,"
"session_type TEXT NOT NULL DEFAULT 'training',"
"notes TEXT"
");"
"CREATE TABLE session_exercises ("
"id INTEGER PRIMARY KEY,"
"session_row_id INTEGER NOT NULL REFERENCES sessions(id),"
"exercise_row_id INTEGER NOT NULL REFERENCES exercises(id),"
"position INTEGER NOT NULL,"
"load_mode TEXT NOT NULL,"
"rest_seconds INTEGER NOT NULL,"
"target_sets INTEGER NOT NULL,"
"target_reps INTEGER,"
"target_duration_seconds INTEGER,"
"target_weight_kg REAL,"
"notes TEXT"
");"
"INSERT INTO exercises("
"exercise_id, name, normalized_name, tracking_mode"
") VALUES('ex_old', 'Ancien', 'ancien', 'duration');"
"INSERT INTO sessions("
"session_id, started_at, session_type"
") VALUES('se_old', '2026-09-01T10:00:00+02:00', 'training');"
"INSERT INTO session_exercises("
"session_row_id, exercise_row_id, position, load_mode,"
"rest_seconds, target_sets, target_duration_seconds"
") VALUES(1, 1, 0, 'none', 0, 1, 60);"
"PRAGMA user_version = 2;";
sqlite3 *raw = NULL;
sqlite3_stmt *statement = NULL;
TrainlogDatabase *database = NULL;
int fd;
int version = 0;
fd = mkstemp(path);
CHECK(fd >= 0);
CHECK(close(fd) == 0);
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
CHECK(
sqlite3_exec(raw, V2_SQL, NULL, NULL, NULL) ==
SQLITE_OK
);
CHECK(sqlite3_close(raw) == SQLITE_OK);
raw = NULL;
CHECK(
trainlog_database_open(path, &database) ==
TRAINLOG_STATUS_OK
);
CHECK(
trainlog_database_schema_version(
database,
&version
) == TRAINLOG_STATUS_OK
);
CHECK(version == 3);
trainlog_database_close(database);
database = NULL;
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
CHECK(
sqlite3_prepare_v2(
raw,
"SELECT recording_mode, data_fields "
"FROM exercises WHERE exercise_id='ex_old';",
-1,
&statement,
NULL
) == SQLITE_OK
);
CHECK(sqlite3_step(statement) == SQLITE_ROW);
CHECK(
strcmp(
(const char *)sqlite3_column_text(statement, 0),
"sets"
) == 0
);
CHECK(sqlite3_column_int64(statement, 1) == 0);
CHECK(sqlite3_finalize(statement) == SQLITE_OK);
statement = NULL;
CHECK(
sqlite3_prepare_v2(
raw,
"SELECT recording_mode, data_fields "
"FROM session_exercises WHERE id=1;",
-1,
&statement,
NULL
) == SQLITE_OK
);
CHECK(sqlite3_step(statement) == SQLITE_ROW);
CHECK(
strcmp(
(const char *)sqlite3_column_text(statement, 0),
"sets"
) == 0
);
CHECK(sqlite3_column_int64(statement, 1) == 0);
CHECK(sqlite3_finalize(statement) == SQLITE_OK);
statement = NULL;
CHECK(
sqlite3_prepare_v2(
raw,
"SELECT name FROM sqlite_master "
"WHERE type='table' AND name='continuous_activity';",
-1,
&statement,
NULL
) == SQLITE_OK
);
CHECK(sqlite3_step(statement) == SQLITE_ROW);
CHECK(sqlite3_finalize(statement) == SQLITE_OK);
CHECK(sqlite3_close(raw) == SQLITE_OK);
raw = NULL;
CHECK(unlink(path) == 0);
return true;
}
int main(void)
{
CHECK(test_profile_roundtrip());
CHECK(test_v2_to_v3_migration());
(void)printf("PASS exercise_profile_schema\n");
return 0;
}