110 lines
5.4 KiB
C
110 lines
5.4 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <sqlite3.h>
|
|
|
|
#include "trainlog/database.h"
|
|
#include "trainlog/web_prepared_items.h"
|
|
#include "database_internal.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 execute(TrainlogDatabase *database, const char *sql) {
|
|
char *error = NULL;
|
|
int status = sqlite3_exec(database->connection, sql, NULL, NULL, &error);
|
|
if (status != SQLITE_OK) {
|
|
(void)fprintf(stderr, "SQLite fixture failed: %s\n", error == NULL ? "unknown" : error);
|
|
}
|
|
sqlite3_free(error);
|
|
return status == SQLITE_OK;
|
|
}
|
|
|
|
static bool test_distinct_durable_items(void) {
|
|
TrainlogDatabase *database = NULL;
|
|
TrainlogWebPreparedItems items;
|
|
char json[TRAINLOG_WEB_PREPARED_ITEMS_JSON_CAPACITY];
|
|
size_t json_size = 0U;
|
|
|
|
CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK);
|
|
CHECK(execute(database,
|
|
"INSERT INTO exercises(exercise_id,name,normalized_name,tracking_mode,recording_mode,"
|
|
"data_fields) VALUES('ex_00000000-0000-4000-8000-000000000001','Test','test','reps',"
|
|
"'sets',0);"
|
|
"INSERT INTO ai_session_drafts(draft_id,created_at,planned_for,session_type,title,notes,"
|
|
"published_at) VALUES('aid_00000000-0000-4000-8000-000000000001',"
|
|
"'2026-09-16T10:00:00Z','2026-09-17','training','Prepared title',NULL,"
|
|
"'2026-09-16T11:00:00Z');"
|
|
"INSERT INTO ai_session_draft_entries(draft_row_id,entry_id,position,exercise_row_id,"
|
|
"source_exercise_id,recording_mode,tracking_mode,data_fields,equipment_id,load_mode,"
|
|
"target_sets,target_reps,target_duration_seconds,target_weight_kg,rest_seconds) "
|
|
"SELECT d.id,'sxe_00000000-0000-4000-8000-000000000001',0,e.id,e.exercise_id,'sets',"
|
|
"'reps',0,NULL,'none',2,8,NULL,NULL,60 FROM ai_session_drafts d,exercises e WHERE "
|
|
"e.exercise_id='ex_00000000-0000-4000-8000-000000000001';"
|
|
"INSERT INTO execution_drafts(session_id,session_type,source_session_id,started_at,"
|
|
"revision_id,parent_revision_id,state,payload_json) VALUES("
|
|
"'se_00000000-0000-4000-8000-000000000001','training',NULL,'2026-09-17T12:00:00Z',"
|
|
"'dr_one',NULL,'active','{\"exercises\":[{},{}]}');"));
|
|
{
|
|
TrainlogStatus status = trainlog_web_prepared_items_load(database, &items);
|
|
if (status != TRAINLOG_STATUS_OK) {
|
|
(void)fprintf(stderr, "prepared load failed: %d sqlite=%s\n", (int)status,
|
|
sqlite3_errmsg(database->connection));
|
|
}
|
|
CHECK(status == TRAINLOG_STATUS_OK);
|
|
}
|
|
CHECK(items.item_count == 2U);
|
|
CHECK(items.items[0].kind == TRAINLOG_WEB_PREPARED_AI_PROPOSAL);
|
|
CHECK(strcmp(items.items[0].state, "published") == 0);
|
|
CHECK(strcmp(items.items[0].title, "Prepared title") == 0);
|
|
CHECK(items.items[0].occurrence_count == 1U);
|
|
CHECK(items.items[1].kind == TRAINLOG_WEB_PREPARED_EXECUTION_DRAFT);
|
|
CHECK(strcmp(items.items[1].state, "active") == 0);
|
|
CHECK(items.items[1].occurrence_count == 2U);
|
|
CHECK(trainlog_web_prepared_items_serialize(
|
|
&items, json, sizeof(json), &json_size) == TRAINLOG_STATUS_OK);
|
|
CHECK(json_size > 0U);
|
|
CHECK(strstr(json, "\"kind\":\"ai_proposal\"") != NULL);
|
|
CHECK(strstr(json, "\"kind\":\"execution_draft\"") != NULL);
|
|
items.items[0].title[0] = (char)0xc3;
|
|
items.items[0].title[1] = '\0';
|
|
CHECK(trainlog_web_prepared_items_serialize(
|
|
&items, json, sizeof(json), &json_size) == TRAINLOG_STATUS_SYSTEM_ERROR);
|
|
CHECK(json_size == 0U && json[0] == '\0');
|
|
trainlog_database_close(database);
|
|
return true;
|
|
}
|
|
|
|
static bool test_finalized_and_deleted_drafts_are_hidden(void) {
|
|
TrainlogDatabase *database = NULL;
|
|
TrainlogWebPreparedItems items;
|
|
|
|
CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK);
|
|
CHECK(execute(database,
|
|
"INSERT INTO execution_drafts(session_id,session_type,revision_id,state,payload_json) "
|
|
"VALUES('se_00000000-0000-4000-8000-000000000002','training','dr_two','pending',"
|
|
"'{\"exercises\":[]}'),('se_00000000-0000-4000-8000-000000000003','training',"
|
|
"'dr_three','pending','{\"exercises\":[]}');"
|
|
"INSERT INTO execution_draft_finalizations(session_id,final_revision_id,finalized_at) "
|
|
"VALUES('se_00000000-0000-4000-8000-000000000002','dr_final',"
|
|
"'2026-09-17T12:00:00Z');"
|
|
"INSERT INTO sync_causal_state(target_kind,target_id,current_revision_id,deleted) "
|
|
"VALUES('execution_draft','se_00000000-0000-4000-8000-000000000003','dr_delete',1);"));
|
|
CHECK(trainlog_web_prepared_items_load(database, &items) == TRAINLOG_STATUS_OK);
|
|
CHECK(items.item_count == 0U);
|
|
trainlog_database_close(database);
|
|
return true;
|
|
}
|
|
|
|
int main(void) {
|
|
if (!test_distinct_durable_items() || !test_finalized_and_deleted_drafts_are_hidden()) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
return EXIT_SUCCESS;
|
|
}
|