230 lines
12 KiB
C
230 lines
12 KiB
C
#include <sqlite3.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "trainlog/body_zone_catalog.h"
|
|
#include "trainlog/catalog.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 1; } } while (0)
|
|
|
|
static int taxonomy_contract(void)
|
|
{
|
|
size_t index;
|
|
CHECK(trainlog_body_zone_catalog_count() == 11U);
|
|
for (index = 0U; index < trainlog_body_zone_catalog_count(); ++index) {
|
|
const TrainlogBodyZone *zone = trainlog_body_zone_catalog_at(index);
|
|
const TrainlogBodyZone *ancestors[11];
|
|
size_t other;
|
|
CHECK(zone != NULL);
|
|
CHECK(trainlog_body_zone_catalog_lookup(zone->zone_id) == zone);
|
|
CHECK(trainlog_body_zone_catalog_ancestors(zone->zone_id, ancestors, 11U) <= 2U);
|
|
if (zone->parent_zone_id != NULL)
|
|
CHECK(trainlog_body_zone_catalog_lookup(zone->parent_zone_id) != NULL);
|
|
for (other = index + 1U; other < trainlog_body_zone_catalog_count(); ++other) {
|
|
const TrainlogBodyZone *candidate = trainlog_body_zone_catalog_at(other);
|
|
CHECK(candidate != NULL);
|
|
CHECK(strcmp(zone->zone_id, candidate->zone_id) != 0);
|
|
CHECK(zone->sort_order != candidate->sort_order);
|
|
}
|
|
}
|
|
CHECK(trainlog_body_zone_catalog_lookup("full_body")->parent_zone_id == NULL);
|
|
CHECK(trainlog_body_zone_catalog_lookup("upper_body")->is_group);
|
|
CHECK(trainlog_body_zone_catalog_lookup("lower_body")->is_group);
|
|
CHECK(trainlog_body_zone_catalog_is_descendant("chest", "upper_body"));
|
|
CHECK(!trainlog_body_zone_catalog_is_descendant("core", "upper_body"));
|
|
return 0;
|
|
}
|
|
|
|
static int relation_and_filter_contract(void)
|
|
{
|
|
char path[] = "/tmp/trainlog-body-zones-XXXXXX";
|
|
int fd = mkstemp(path);
|
|
sqlite3 *raw = NULL;
|
|
TrainlogDatabase *database = NULL;
|
|
TrainlogExercise created;
|
|
TrainlogExercise lower;
|
|
TrainlogExercise results[8];
|
|
TrainlogExerciseBodyZone relations[4];
|
|
const char *secondary[] = {"shoulders", "arms"};
|
|
const char *duplicate[] = {"arms", "arms"};
|
|
const char *same_as_primary[] = {"chest"};
|
|
const char *orphan_secondary[] = {"arms"};
|
|
size_t count = 0U;
|
|
CHECK(fd >= 0);
|
|
CHECK(close(fd) == 0);
|
|
CHECK(trainlog_database_open(path, &database) == TRAINLOG_STATUS_OK);
|
|
CHECK(trainlog_catalog_create_exercise_profiled_with_zones(database,
|
|
"Chest custom", TRAINLOG_TRACKING_REPS, TRAINLOG_RECORDING_SETS, 0U,
|
|
"chest", secondary, 2U, &created) == TRAINLOG_STATUS_OK);
|
|
CHECK(trainlog_database_list_exercise_body_zones(database, created.exercise_id,
|
|
relations, 4U, &count) == TRAINLOG_STATUS_OK);
|
|
CHECK(count == 3U);
|
|
CHECK(trainlog_database_list_exercise_body_zones(database, "ex_missing",
|
|
relations, 4U, &count) == TRAINLOG_STATUS_NOT_FOUND);
|
|
CHECK(relations[0].role == TRAINLOG_BODY_ZONE_PRIMARY);
|
|
CHECK(strcmp(relations[0].zone_id, "chest") == 0);
|
|
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
|
|
CHECK(sqlite3_exec(raw,
|
|
"INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) "
|
|
"SELECT id,'back','primary' FROM exercises WHERE exercise_id LIKE 'ex_%';",
|
|
NULL, NULL, NULL) == SQLITE_CONSTRAINT);
|
|
CHECK(sqlite3_close(raw) == SQLITE_OK);
|
|
raw = NULL;
|
|
CHECK(trainlog_database_replace_exercise_body_zones(database, created.exercise_id,
|
|
"chest", same_as_primary, 1U) == TRAINLOG_STATUS_INVALID_ARGUMENT);
|
|
CHECK(trainlog_database_replace_exercise_body_zones(database, created.exercise_id,
|
|
"chest", duplicate, 2U) == TRAINLOG_STATUS_INVALID_ARGUMENT);
|
|
CHECK(trainlog_database_replace_exercise_body_zones(database, created.exercise_id,
|
|
NULL, orphan_secondary, 1U) == TRAINLOG_STATUS_INVALID_ARGUMENT);
|
|
CHECK(trainlog_database_replace_exercise_body_zones(database, created.exercise_id,
|
|
"upper_body", NULL, 0U) == TRAINLOG_STATUS_INVALID_ARGUMENT);
|
|
CHECK(trainlog_database_replace_exercise_body_zones(database, created.exercise_id,
|
|
"unknown", NULL, 0U) == TRAINLOG_STATUS_INVALID_ARGUMENT);
|
|
CHECK(trainlog_database_list_exercises_filtered(database, "chest", "upper_body",
|
|
true, false, false, results, 8U, &count) == TRAINLOG_STATUS_OK);
|
|
CHECK(count == 1U && strcmp(results[0].exercise_id, created.exercise_id) == 0);
|
|
CHECK(trainlog_database_list_exercises_filtered(database, "", "chest",
|
|
false, false, false, results, 8U, &count) == TRAINLOG_STATUS_OK && count == 1U);
|
|
CHECK(trainlog_database_list_exercises_filtered(database, "", "arms",
|
|
false, false, false, results, 8U, &count) == TRAINLOG_STATUS_OK && count == 1U);
|
|
CHECK(trainlog_database_list_exercises_filtered(database, "", "arms",
|
|
false, true, false, results, 8U, &count) == TRAINLOG_STATUS_OK && count == 0U);
|
|
CHECK(trainlog_database_list_exercises_filtered(database, "chest", "back",
|
|
true, false, false, results, 8U, &count) == TRAINLOG_STATUS_OK && count == 0U);
|
|
CHECK(trainlog_database_list_exercises_filtered(database, "", "back",
|
|
true, false, true, results, 8U, &count) == TRAINLOG_STATUS_INVALID_ARGUMENT);
|
|
CHECK(trainlog_catalog_create_exercise_profiled_with_zones(database,
|
|
"Glute custom", TRAINLOG_TRACKING_REPS, TRAINLOG_RECORDING_SETS, 0U,
|
|
"glutes", NULL, 0U, &lower) == TRAINLOG_STATUS_OK);
|
|
CHECK(trainlog_catalog_create_exercise_profiled_with_zones(database,
|
|
"Thigh custom", TRAINLOG_TRACKING_REPS, TRAINLOG_RECORDING_SETS, 0U,
|
|
"thighs", NULL, 0U, &lower) == TRAINLOG_STATUS_OK);
|
|
CHECK(trainlog_catalog_create_exercise_profiled_with_zones(database,
|
|
"Calf custom", TRAINLOG_TRACKING_REPS, TRAINLOG_RECORDING_SETS, 0U,
|
|
"calves", NULL, 0U, &lower) == TRAINLOG_STATUS_OK);
|
|
CHECK(trainlog_database_list_exercises_filtered(database, "", "lower_body",
|
|
true, false, false, results, 8U, &count) == TRAINLOG_STATUS_OK && count == 3U);
|
|
CHECK(trainlog_database_update_exercise_profiled(database, created.exercise_id,
|
|
"Chest custom renamed", "chest custom renamed", TRAINLOG_TRACKING_REPS,
|
|
TRAINLOG_RECORDING_SETS, 0U, "back", NULL, 0U) ==
|
|
TRAINLOG_STATUS_OK);
|
|
CHECK(trainlog_database_list_exercise_body_zones(database, created.exercise_id,
|
|
relations, 4U, &count) == TRAINLOG_STATUS_OK && count == 1U &&
|
|
strcmp(relations[0].zone_id, "back") == 0);
|
|
CHECK(trainlog_database_replace_exercise_body_zones(database, created.exercise_id,
|
|
NULL, NULL, 0U) == TRAINLOG_STATUS_OK);
|
|
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
|
|
/* Inject against the real row with a prepared statement so the corruption
|
|
* fixture never embeds its generated UUID. */
|
|
{
|
|
sqlite3_stmt *corrupt = NULL;
|
|
CHECK(sqlite3_prepare_v2(raw,
|
|
"INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) "
|
|
"SELECT id,'arms','secondary' FROM exercises WHERE exercise_id=?1;",
|
|
-1, &corrupt, NULL) == SQLITE_OK);
|
|
CHECK(sqlite3_bind_text(corrupt, 1, created.exercise_id, -1,
|
|
SQLITE_TRANSIENT) == SQLITE_OK);
|
|
CHECK(sqlite3_step(corrupt) == SQLITE_DONE);
|
|
CHECK(sqlite3_finalize(corrupt) == SQLITE_OK);
|
|
}
|
|
CHECK(sqlite3_close(raw) == SQLITE_OK);
|
|
raw = NULL;
|
|
CHECK(trainlog_database_list_exercise_body_zones(database, created.exercise_id,
|
|
relations, 4U, &count) == TRAINLOG_STATUS_DATABASE_ERROR);
|
|
CHECK(trainlog_database_list_exercises_filtered(database, "", "chest",
|
|
false, false, false, results, 8U, &count) == TRAINLOG_STATUS_DATABASE_ERROR);
|
|
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
|
|
CHECK(sqlite3_exec(raw, "DELETE FROM exercise_body_zones WHERE role='secondary';",
|
|
NULL, NULL, NULL) == SQLITE_OK);
|
|
CHECK(sqlite3_exec(raw,
|
|
"INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) "
|
|
"SELECT id,'upper_body','primary' FROM exercises "
|
|
"WHERE normalized_name='chest custom renamed';",
|
|
NULL, NULL, NULL) == SQLITE_OK);
|
|
CHECK(sqlite3_close(raw) == SQLITE_OK);
|
|
raw = NULL;
|
|
CHECK(trainlog_database_list_exercise_body_zones(database, created.exercise_id,
|
|
relations, 4U, &count) == TRAINLOG_STATUS_DATABASE_ERROR);
|
|
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
|
|
CHECK(sqlite3_exec(raw, "DELETE FROM exercise_body_zones WHERE zone_id='upper_body';",
|
|
NULL, NULL, NULL) == SQLITE_OK);
|
|
CHECK(sqlite3_close(raw) == SQLITE_OK);
|
|
raw = NULL;
|
|
CHECK(trainlog_database_list_exercises_filtered(database, "", NULL,
|
|
true, false, true, results, 8U, &count) == TRAINLOG_STATUS_OK && count == 1U);
|
|
trainlog_database_close(database);
|
|
database = NULL;
|
|
CHECK(trainlog_database_open(path, &database) == TRAINLOG_STATUS_OK);
|
|
CHECK(trainlog_database_list_exercise_body_zones(database, created.exercise_id,
|
|
relations, 4U, &count) == TRAINLOG_STATUS_OK && count == 0U);
|
|
trainlog_database_close(database);
|
|
CHECK(unlink(path) == 0);
|
|
return 0;
|
|
}
|
|
|
|
static int migration_preserves_identity_and_history(void)
|
|
{
|
|
char path[] = "/tmp/trainlog-body-zones-v10-XXXXXX";
|
|
int fd = mkstemp(path);
|
|
sqlite3 *raw = NULL;
|
|
TrainlogDatabase *database = NULL;
|
|
TrainlogExerciseBodyZone relations[4];
|
|
size_t count = 0U;
|
|
int version = 0;
|
|
CHECK(fd >= 0);
|
|
CHECK(close(fd) == 0);
|
|
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
|
|
CHECK(sqlite3_exec(raw,
|
|
"PRAGMA foreign_keys=ON;"
|
|
"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,"
|
|
"recording_mode TEXT NOT NULL,data_fields INTEGER NOT NULL);"
|
|
"CREATE TABLE sessions(id INTEGER PRIMARY KEY,session_id TEXT NOT NULL UNIQUE);"
|
|
"INSERT INTO exercises VALUES(7,'ex_b432623f-bfe9-4daf-a653-60ec7fdffbde',"
|
|
"'Leg press','leg press','reps','sets',0);"
|
|
"INSERT INTO sessions VALUES(3,'se_preserved');"
|
|
"PRAGMA user_version=10;", 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 && version == 12);
|
|
CHECK(trainlog_database_list_exercise_body_zones(database,
|
|
"ex_b432623f-bfe9-4daf-a653-60ec7fdffbde", relations, 4U, &count) == TRAINLOG_STATUS_OK);
|
|
CHECK(count == 2U);
|
|
trainlog_database_close(database);
|
|
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
|
|
{
|
|
sqlite3_stmt *statement = NULL;
|
|
CHECK(sqlite3_prepare_v2(raw,
|
|
"SELECT (SELECT COUNT(*) FROM exercises WHERE id=7 AND "
|
|
"exercise_id='ex_b432623f-bfe9-4daf-a653-60ec7fdffbde'),"
|
|
"(SELECT COUNT(*) FROM sessions WHERE id=3 AND session_id='se_preserved');",
|
|
-1, &statement, NULL) == SQLITE_OK);
|
|
CHECK(sqlite3_step(statement) == SQLITE_ROW);
|
|
CHECK(sqlite3_column_int(statement, 0) == 1);
|
|
CHECK(sqlite3_column_int(statement, 1) == 1);
|
|
CHECK(sqlite3_finalize(statement) == SQLITE_OK);
|
|
statement = NULL;
|
|
CHECK(sqlite3_prepare_v2(raw, "PRAGMA foreign_key_check;", -1,
|
|
&statement, NULL) == SQLITE_OK);
|
|
CHECK(sqlite3_step(statement) == SQLITE_DONE);
|
|
CHECK(sqlite3_finalize(statement) == SQLITE_OK);
|
|
}
|
|
CHECK(sqlite3_close(raw) == SQLITE_OK);
|
|
CHECK(unlink(path) == 0);
|
|
return 0;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
CHECK(taxonomy_contract() == 0);
|
|
CHECK(relation_and_filter_contract() == 0);
|
|
CHECK(migration_preserves_identity_and_history() == 0);
|
|
return 0;
|
|
}
|