496 lines
25 KiB
C
496 lines
25 KiB
C
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <sqlite3.h>
|
|
|
|
#include "database_internal.h"
|
|
#include "trainlog/database.h"
|
|
#include "trainlog/web_sessions.h"
|
|
|
|
#define CHECK(expression) \
|
|
do { \
|
|
if (!(expression)) { \
|
|
(void)fprintf(stderr, "CHECK failed at %s:%d: %s\n", __FILE__, __LINE__, #expression); \
|
|
return false; \
|
|
} \
|
|
} while (0)
|
|
|
|
static int scalar(TrainlogDatabase *database, const char *sql) {
|
|
sqlite3_stmt *statement = NULL;
|
|
int result = -1;
|
|
|
|
if (sqlite3_prepare_v2(database->connection, sql, -1, &statement, NULL) == SQLITE_OK &&
|
|
sqlite3_step(statement) == SQLITE_ROW) {
|
|
result = sqlite3_column_int(statement, 0);
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
return result;
|
|
}
|
|
|
|
static bool json_string(const char *json, const char *key, char *output, size_t capacity) {
|
|
char marker[96];
|
|
const char *start;
|
|
const char *end;
|
|
size_t length;
|
|
|
|
if (snprintf(marker, sizeof(marker), "\"%s\":\"", key) < 0) {
|
|
return false;
|
|
}
|
|
start = strstr(json, marker);
|
|
if (start == NULL) {
|
|
return false;
|
|
}
|
|
start += strlen(marker);
|
|
end = strchr(start, '"');
|
|
if (end == NULL) {
|
|
return false;
|
|
}
|
|
length = (size_t)(end - start);
|
|
if (length == 0U || length >= capacity) {
|
|
return false;
|
|
}
|
|
(void)memcpy(output, start, length);
|
|
output[length] = '\0';
|
|
return true;
|
|
}
|
|
|
|
static bool creation_replay_revision_and_pagination(void) {
|
|
static const char CREATE_BODY[] =
|
|
"{\"title\":\"Séance échappée \\\"A\\\"\",\"session_type\":\"training\","
|
|
"\"planned_for\":\"2026-09-20\",\"notes\":\"Dos & épaules\","
|
|
"\"editing_state\":\"ready\",\"occurrences\":["
|
|
"{\"exercise_id\":\"ex_aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa\","
|
|
"\"load_mode\":\"external\",\"rest_seconds\":90,\"target_sets\":3,"
|
|
"\"target_reps\":8,\"target_duration_seconds\":null,\"target_weight_kg\":42.5},"
|
|
"{\"exercise_id\":\"ex_aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa\","
|
|
"\"load_mode\":\"none\",\"rest_seconds\":0,\"target_sets\":1,"
|
|
"\"target_reps\":12,\"target_duration_seconds\":null,\"target_weight_kg\":null}]}";
|
|
static const char UPDATE_BODY[] =
|
|
"{\"title\":\"Réordonnée\",\"session_type\":\"training\","
|
|
"\"planned_for\":null,\"notes\":null,\"editing_state\":\"draft\","
|
|
"\"occurrences\":[]}";
|
|
static const char DERIVED_BODY[] =
|
|
"{\"title\":\"Derived\",\"session_type\":\"training\","
|
|
"\"planned_for\":\"2026-09-17\",\"notes\":null,\"editing_state\":\"ready\","
|
|
"\"occurrences\":[{\"exercise_id\":\"ex_aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa\","
|
|
"\"equipment_id\":null,\"load_mode\":\"external\",\"rest_seconds\":90,"
|
|
"\"target_sets\":4,\"target_reps\":10,\"target_duration_seconds\":null,"
|
|
"\"target_weight_kg\":39,\"notes\":null}],\"source_proposal_id\":"
|
|
"\"aid_11111111-1111-4111-8111-111111111111\","
|
|
"\"source_payload_sha256\":"
|
|
"\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}";
|
|
TrainlogDatabase *database = NULL;
|
|
TrainlogWebSessionsPageQuery query = {TRAINLOG_WEB_SESSION_PREPARATIONS, 0U, 1U, "échappée"};
|
|
char *created = NULL;
|
|
char *replayed = NULL;
|
|
char *detail = NULL;
|
|
char *page = NULL;
|
|
char *updated = NULL;
|
|
size_t created_size = 0U;
|
|
size_t replayed_size = 0U;
|
|
size_t detail_size = 0U;
|
|
size_t page_size = 0U;
|
|
size_t updated_size = 0U;
|
|
char preparation_id[128];
|
|
char revision_id[128];
|
|
const char *preparation_start;
|
|
const char *revision_start;
|
|
const char *end;
|
|
|
|
CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK);
|
|
CHECK(trainlog_database_insert_exercise_profiled(database,
|
|
"ex_aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
"Développé test",
|
|
"developpe test",
|
|
TRAINLOG_TRACKING_REPS,
|
|
TRAINLOG_RECORDING_SETS,
|
|
0U) == TRAINLOG_STATUS_OK);
|
|
CHECK(sqlite3_exec(database->connection,
|
|
"INSERT INTO ai_session_drafts(draft_id,created_at,session_type,title) "
|
|
"VALUES('aid_11111111-1111-4111-8111-111111111111','2026-09-18T00:00:00Z',"
|
|
"'training','Source');"
|
|
"INSERT INTO ai_session_draft_imports VALUES("
|
|
"last_insert_rowid(),'aid_11111111-1111-4111-8111-111111111111',"
|
|
"'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',"
|
|
"'2026-09-18T00:00:00Z');",
|
|
NULL,
|
|
NULL,
|
|
NULL) == SQLITE_OK);
|
|
CHECK(trainlog_web_sessions_save_json(database,
|
|
NULL,
|
|
"",
|
|
"request-create-1",
|
|
CREATE_BODY,
|
|
strlen(CREATE_BODY),
|
|
&created,
|
|
&created_size) == TRAINLOG_STATUS_OK);
|
|
CHECK(created_size > 0U);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM session_preparations") == 1);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM session_preparation_entries") == 2);
|
|
CHECK(trainlog_web_sessions_save_json(database,
|
|
NULL,
|
|
"",
|
|
"request-create-1",
|
|
CREATE_BODY,
|
|
strlen(CREATE_BODY),
|
|
&replayed,
|
|
&replayed_size) == TRAINLOG_STATUS_OK);
|
|
CHECK(created_size == replayed_size && strcmp(created, replayed) == 0);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM session_preparations") == 1);
|
|
preparation_start = strstr(created, "\"preparation_id\":\"");
|
|
revision_start = strstr(created, "\"revision_id\":\"");
|
|
CHECK(preparation_start != NULL && revision_start != NULL);
|
|
preparation_start += strlen("\"preparation_id\":\"");
|
|
end = strchr(preparation_start, '"');
|
|
CHECK(end != NULL && (size_t)(end - preparation_start) < sizeof(preparation_id));
|
|
(void)memcpy(preparation_id, preparation_start, (size_t)(end - preparation_start));
|
|
preparation_id[end - preparation_start] = '\0';
|
|
revision_start += strlen("\"revision_id\":\"");
|
|
end = strchr(revision_start, '"');
|
|
CHECK(end != NULL && (size_t)(end - revision_start) < sizeof(revision_id));
|
|
(void)memcpy(revision_id, revision_start, (size_t)(end - revision_start));
|
|
revision_id[end - revision_start] = '\0';
|
|
CHECK(trainlog_web_sessions_detail_json(
|
|
database, "preparation", preparation_id, &detail, &detail_size) ==
|
|
TRAINLOG_STATUS_OK);
|
|
CHECK(strstr(detail, "Séance échappée") != NULL);
|
|
CHECK(strstr(detail, "Développé test") != NULL);
|
|
CHECK(trainlog_web_sessions_list_json(database, &query, &page, &page_size) ==
|
|
TRAINLOG_STATUS_OK);
|
|
CHECK(strstr(page, preparation_id) != NULL);
|
|
CHECK(trainlog_web_sessions_save_json(database,
|
|
preparation_id,
|
|
revision_id,
|
|
"request-update-1",
|
|
UPDATE_BODY,
|
|
strlen(UPDATE_BODY),
|
|
&updated,
|
|
&updated_size) == TRAINLOG_STATUS_OK);
|
|
CHECK(updated_size > 0U);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM session_preparation_revisions") == 2);
|
|
free(detail);
|
|
detail = NULL;
|
|
detail_size = 0U;
|
|
CHECK(trainlog_web_sessions_save_json(database,
|
|
NULL,
|
|
"",
|
|
"request-derived-1",
|
|
DERIVED_BODY,
|
|
strlen(DERIVED_BODY),
|
|
&detail,
|
|
&detail_size) == TRAINLOG_STATUS_OK);
|
|
CHECK(scalar(database,
|
|
"SELECT COUNT(*) FROM session_preparations WHERE source_proposal_id="
|
|
"'aid_11111111-1111-4111-8111-111111111111'") == 1);
|
|
free(detail);
|
|
detail = NULL;
|
|
detail_size = 0U;
|
|
CHECK(trainlog_web_sessions_save_json(database,
|
|
preparation_id,
|
|
revision_id,
|
|
"request-stale",
|
|
UPDATE_BODY,
|
|
strlen(UPDATE_BODY),
|
|
&detail,
|
|
&detail_size) == TRAINLOG_STATUS_CONFLICT);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM session_preparation_revisions") == 3);
|
|
free(created);
|
|
free(replayed);
|
|
free(detail);
|
|
free(page);
|
|
free(updated);
|
|
trainlog_database_close(database);
|
|
return true;
|
|
}
|
|
|
|
static bool withdrawal_is_durable_and_preserves_evidence(void) {
|
|
static const char READY_BODY[] =
|
|
"{\"title\":\"Préparation à retirer\",\"session_type\":\"training\","
|
|
"\"planned_for\":\"2026-09-20\",\"notes\":null,\"editing_state\":\"ready\","
|
|
"\"occurrences\":[{\"exercise_id\":\"ex_aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa\","
|
|
"\"equipment_id\":null,\"load_mode\":\"none\",\"rest_seconds\":60,"
|
|
"\"target_sets\":2,\"target_reps\":8,\"target_duration_seconds\":null,"
|
|
"\"target_weight_kg\":null,\"notes\":null}],\"source_proposal_id\":"
|
|
"\"aid_11111111-1111-4111-8111-111111111111\",\"source_payload_sha256\":"
|
|
"\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}";
|
|
static const char EDIT_BODY[] =
|
|
"{\"title\":\"Modification interdite\",\"session_type\":\"training\","
|
|
"\"planned_for\":null,\"notes\":null,\"editing_state\":\"draft\","
|
|
"\"occurrences\":[]}";
|
|
static const char LOCAL_READY_BODY[] =
|
|
"{\"title\":\"Préparation locale\",\"session_type\":\"training\","
|
|
"\"planned_for\":null,\"notes\":null,\"editing_state\":\"ready\","
|
|
"\"occurrences\":[{\"exercise_id\":\"ex_aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa\","
|
|
"\"equipment_id\":null,\"load_mode\":\"none\",\"rest_seconds\":0,"
|
|
"\"target_sets\":1,\"target_reps\":1,\"target_duration_seconds\":null,"
|
|
"\"target_weight_kg\":null,\"notes\":null}]}";
|
|
TrainlogDatabase *database = NULL;
|
|
TrainlogWebSessionsPageQuery query = {TRAINLOG_WEB_SESSION_PREPARATIONS, 0U, 64U, ""};
|
|
char *created = NULL;
|
|
char *delivery = NULL;
|
|
char *withdrawal = NULL;
|
|
char *replayed = NULL;
|
|
char *detail = NULL;
|
|
char *page = NULL;
|
|
size_t created_size = 0U;
|
|
size_t delivery_size = 0U;
|
|
size_t withdrawal_size = 0U;
|
|
size_t replayed_size = 0U;
|
|
size_t detail_size = 0U;
|
|
size_t page_size = 0U;
|
|
char preparation_id[128];
|
|
char revision_id[128];
|
|
char local_preparation_id[128];
|
|
char local_revision_id[128];
|
|
char execution_session_id[128];
|
|
|
|
CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK);
|
|
CHECK(trainlog_database_insert_exercise_profiled(database,
|
|
"ex_aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
"Exercice retrait",
|
|
"exercice retrait",
|
|
TRAINLOG_TRACKING_REPS,
|
|
TRAINLOG_RECORDING_SETS,
|
|
0U) == TRAINLOG_STATUS_OK);
|
|
CHECK(sqlite3_exec(database->connection,
|
|
"INSERT INTO ai_session_drafts(draft_id,created_at,session_type,title) "
|
|
"VALUES('aid_11111111-1111-4111-8111-111111111111',"
|
|
"'2026-09-18T00:00:00Z','training','Proposition conservée');"
|
|
"INSERT INTO ai_session_draft_imports VALUES(last_insert_rowid(),"
|
|
"'aid_11111111-1111-4111-8111-111111111111',"
|
|
"'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',"
|
|
"'2026-09-18T00:00:00Z');",
|
|
NULL,
|
|
NULL,
|
|
NULL) == SQLITE_OK);
|
|
CHECK(trainlog_web_sessions_save_json(database,
|
|
NULL,
|
|
"",
|
|
"request-withdraw-create",
|
|
READY_BODY,
|
|
strlen(READY_BODY),
|
|
&created,
|
|
&created_size) == TRAINLOG_STATUS_OK);
|
|
CHECK(json_string(created, "preparation_id", preparation_id, sizeof(preparation_id)));
|
|
CHECK(json_string(created, "revision_id", revision_id, sizeof(revision_id)));
|
|
CHECK(trainlog_web_sessions_withdraw_json(database,
|
|
preparation_id,
|
|
"spr_ffffffff-ffff-4fff-8fff-ffffffffffff",
|
|
"request-withdraw-stale",
|
|
&withdrawal,
|
|
&withdrawal_size) == TRAINLOG_STATUS_CONFLICT);
|
|
CHECK(withdrawal == NULL && withdrawal_size == 0U);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM session_preparation_withdrawals") == 0);
|
|
CHECK(trainlog_web_sessions_deliver_json(database,
|
|
preparation_id,
|
|
revision_id,
|
|
"request-withdraw-deliver",
|
|
&delivery,
|
|
&delivery_size) == TRAINLOG_STATUS_OK);
|
|
CHECK(trainlog_web_sessions_deliver_json(database,
|
|
preparation_id,
|
|
revision_id,
|
|
"request-withdraw-deliver-duplicate",
|
|
&replayed,
|
|
&replayed_size) == TRAINLOG_STATUS_CONFLICT);
|
|
CHECK(replayed == NULL && replayed_size == 0U);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM session_preparation_deliveries") == 1);
|
|
CHECK(trainlog_web_sessions_withdraw_json(database,
|
|
preparation_id,
|
|
revision_id,
|
|
"request-withdraw-exact",
|
|
&withdrawal,
|
|
&withdrawal_size) == TRAINLOG_STATUS_OK);
|
|
CHECK(strstr(withdrawal, "\"android_cancellation\":\"pending\"") != NULL);
|
|
CHECK(trainlog_web_sessions_withdraw_json(database,
|
|
preparation_id,
|
|
revision_id,
|
|
"request-withdraw-exact",
|
|
&replayed,
|
|
&replayed_size) == TRAINLOG_STATUS_OK);
|
|
CHECK(withdrawal_size == replayed_size && strcmp(withdrawal, replayed) == 0);
|
|
free(replayed);
|
|
replayed = NULL;
|
|
replayed_size = 0U;
|
|
CHECK(trainlog_web_sessions_withdraw_json(database,
|
|
"sp_ffffffff-ffff-4fff-8fff-ffffffffffff",
|
|
revision_id,
|
|
"request-withdraw-exact",
|
|
&replayed,
|
|
&replayed_size) == TRAINLOG_STATUS_CONFLICT);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM session_preparation_withdrawals") == 1);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM session_preparation_deliveries") == 1);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM session_preparation_revisions") == 1);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM ai_session_drafts") == 1);
|
|
CHECK(scalar(database,
|
|
"SELECT COUNT(*) FROM session_preparation_deliveries WHERE state='pending'") == 1);
|
|
CHECK(trainlog_web_sessions_list_json(database, &query, &page, &page_size) ==
|
|
TRAINLOG_STATUS_OK);
|
|
CHECK(strstr(page, preparation_id) == NULL);
|
|
CHECK(trainlog_web_sessions_detail_json(
|
|
database, "preparation", preparation_id, &detail, &detail_size) ==
|
|
TRAINLOG_STATUS_OK);
|
|
CHECK(strstr(detail, "\"state\":\"withdrawn\"") != NULL);
|
|
CHECK(strstr(detail, "\"source_proposal_title\":\"Proposition conservée\"") != NULL);
|
|
CHECK(strstr(detail, "\"withdrawal_id\":\"spw_") != NULL);
|
|
CHECK(trainlog_web_sessions_save_json(database,
|
|
preparation_id,
|
|
revision_id,
|
|
"request-after-withdraw-save",
|
|
EDIT_BODY,
|
|
strlen(EDIT_BODY),
|
|
&replayed,
|
|
&replayed_size) == TRAINLOG_STATUS_NOT_FOUND);
|
|
CHECK(trainlog_web_sessions_deliver_json(database,
|
|
preparation_id,
|
|
revision_id,
|
|
"request-after-withdraw-deliver",
|
|
&replayed,
|
|
&replayed_size) == TRAINLOG_STATUS_CONFLICT);
|
|
free(replayed);
|
|
replayed = NULL;
|
|
replayed_size = 0U;
|
|
CHECK(trainlog_web_sessions_save_json(database,
|
|
NULL,
|
|
"",
|
|
"request-local-create",
|
|
LOCAL_READY_BODY,
|
|
strlen(LOCAL_READY_BODY),
|
|
&replayed,
|
|
&replayed_size) == TRAINLOG_STATUS_OK);
|
|
CHECK(json_string(
|
|
replayed, "preparation_id", local_preparation_id, sizeof(local_preparation_id)));
|
|
CHECK(json_string(replayed, "revision_id", local_revision_id, sizeof(local_revision_id)));
|
|
free(replayed);
|
|
replayed = NULL;
|
|
replayed_size = 0U;
|
|
CHECK(trainlog_web_sessions_withdraw_json(database,
|
|
local_preparation_id,
|
|
local_revision_id,
|
|
"request-local-withdraw",
|
|
&replayed,
|
|
&replayed_size) == TRAINLOG_STATUS_OK);
|
|
CHECK(strstr(replayed, "\"android_cancellation\":\"not_required\"") != NULL);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM session_preparation_withdrawals") == 2);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM session_preparation_deliveries") == 1);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM ai_session_drafts") == 1);
|
|
|
|
/* A completed occurrence hides the preparation by its reserved stable
|
|
* execution identity while retaining every preparation/delivery row. */
|
|
free(replayed);
|
|
replayed = NULL;
|
|
replayed_size = 0U;
|
|
CHECK(trainlog_web_sessions_save_json(database,
|
|
NULL,
|
|
"",
|
|
"request-completed-create",
|
|
LOCAL_READY_BODY,
|
|
strlen(LOCAL_READY_BODY),
|
|
&replayed,
|
|
&replayed_size) == TRAINLOG_STATUS_OK);
|
|
CHECK(json_string(
|
|
replayed, "preparation_id", local_preparation_id, sizeof(local_preparation_id)));
|
|
CHECK(json_string(replayed, "revision_id", local_revision_id, sizeof(local_revision_id)));
|
|
free(replayed);
|
|
replayed = NULL;
|
|
replayed_size = 0U;
|
|
CHECK(trainlog_web_sessions_deliver_json(database,
|
|
local_preparation_id,
|
|
local_revision_id,
|
|
"request-completed-deliver",
|
|
&replayed,
|
|
&replayed_size) == TRAINLOG_STATUS_OK);
|
|
CHECK(json_string(
|
|
replayed, "execution_session_id", execution_session_id, sizeof(execution_session_id)));
|
|
free(page);
|
|
page = NULL;
|
|
page_size = 0U;
|
|
CHECK(trainlog_web_sessions_list_json(database, &query, &page, &page_size) ==
|
|
TRAINLOG_STATUS_OK);
|
|
CHECK(strstr(page, local_preparation_id) != NULL);
|
|
{
|
|
char *sql =
|
|
sqlite3_mprintf("INSERT INTO sessions(session_id,started_at,ended_at,session_type) "
|
|
"VALUES(%Q,'2026-09-20T10:00:00Z','2026-09-20T11:00:00Z','training')",
|
|
execution_session_id);
|
|
CHECK(sql != NULL);
|
|
CHECK(sqlite3_exec(database->connection, sql, NULL, NULL, NULL) == SQLITE_OK);
|
|
sqlite3_free(sql);
|
|
}
|
|
free(page);
|
|
page = NULL;
|
|
page_size = 0U;
|
|
CHECK(trainlog_web_sessions_list_json(database, &query, &page, &page_size) ==
|
|
TRAINLOG_STATUS_OK);
|
|
CHECK(strstr(page, local_preparation_id) == NULL);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM session_preparation_deliveries") == 2);
|
|
CHECK(scalar(database, "SELECT COUNT(*) FROM session_preparations") == 3);
|
|
|
|
free(created);
|
|
free(delivery);
|
|
free(withdrawal);
|
|
free(replayed);
|
|
free(detail);
|
|
free(page);
|
|
trainlog_database_close(database);
|
|
return true;
|
|
}
|
|
|
|
static bool sport_history_intersection_and_pagination(void) {
|
|
TrainlogDatabase *database = NULL;
|
|
char *page = NULL;
|
|
size_t size = 0U;
|
|
const char *start = "2026-10-01T00:00:00Z";
|
|
const char *end = "2026-10-02T00:00:00Z";
|
|
|
|
CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK);
|
|
CHECK(sqlite3_exec(database->connection,
|
|
"INSERT INTO sessions(session_id,session_type,started_at,ended_at) VALUES"
|
|
"('se_before','training','2026-09-30T23:00:00Z','2026-10-01T01:00:00Z'),"
|
|
"('se_inside','training','2026-10-01T04:00:00Z','2026-10-01T05:00:00Z'),"
|
|
"('se_point','max_test','2026-10-01T08:00:00Z',NULL),"
|
|
"('se_after','training','2026-10-02T00:00:00Z','2026-10-02T01:00:00Z');",
|
|
NULL,
|
|
NULL,
|
|
NULL) == SQLITE_OK);
|
|
CHECK(trainlog_web_sessions_sport_json(database, start, end, 0U, 1U, &page, &size) ==
|
|
TRAINLOG_STATUS_OK);
|
|
CHECK(strstr(page, "se_before") != NULL);
|
|
CHECK(strstr(page, "se_inside") == NULL);
|
|
CHECK(strstr(page, "\"more\":true") != NULL);
|
|
free(page);
|
|
page = NULL;
|
|
CHECK(trainlog_web_sessions_sport_json(database, start, end, 1U, 2U, &page, &size) ==
|
|
TRAINLOG_STATUS_OK);
|
|
CHECK(strstr(page, "se_inside") != NULL);
|
|
CHECK(strstr(page, "se_point") != NULL);
|
|
CHECK(strstr(page, "se_after") == NULL);
|
|
CHECK(strstr(page, "\"more\":false") != NULL);
|
|
free(page);
|
|
page = NULL;
|
|
CHECK(sqlite3_exec(database->connection,
|
|
"DELETE FROM sessions WHERE session_id='se_inside'",
|
|
NULL,
|
|
NULL,
|
|
NULL) == SQLITE_OK);
|
|
CHECK(trainlog_web_sessions_sport_json(database, start, end, 0U, 64U, &page, &size) ==
|
|
TRAINLOG_STATUS_OK);
|
|
CHECK(strstr(page, "se_inside") == NULL);
|
|
CHECK(strstr(page, "se_before") != NULL);
|
|
free(page);
|
|
page = NULL;
|
|
CHECK(trainlog_web_sessions_sport_json(database, "invalid", end, 0U, 64U, &page, &size) ==
|
|
TRAINLOG_STATUS_INVALID_ARGUMENT);
|
|
trainlog_database_close(database);
|
|
return true;
|
|
}
|
|
|
|
int main(void) {
|
|
return creation_replay_revision_and_pagination() &&
|
|
withdrawal_is_durable_and_preserves_evidence() &&
|
|
sport_history_intersection_and_pagination()
|
|
? 0
|
|
: 1;
|
|
}
|