test(web): cover dashboard failure states
This commit is contained in:
parent
f894f744f3
commit
6f34233751
3 changed files with 53 additions and 3 deletions
|
|
@ -61,7 +61,8 @@ day, an unfinished session, continuous activity, MAX-only sessions, primary
|
||||||
zone deduplication, catalogue-without-history exclusion, 90/30-day windows,
|
zone deduplication, catalogue-without-history exclusion, 90/30-day windows,
|
||||||
recent-MAX truncation/`partial`, Footer facts and JSON escaping/overflow. The
|
recent-MAX truncation/`partial`, Footer facts and JSON escaping/overflow. The
|
||||||
HTTP lifecycle test additionally proves `GET /api/v1/dashboard`, JSON content
|
HTTP lifecycle test additionally proves `GET /api/v1/dashboard`, JSON content
|
||||||
type/version, unavailable reasons and `POST` rejection without weakening Host,
|
type/version, unavailable reasons, corrupt-timestamp `invalid_data`, Core-error
|
||||||
|
translation to bounded HTTP 500 and `POST` rejection without weakening Host,
|
||||||
CSP, CORS or unknown-route behavior. Vitest validates strict parsing, real
|
CSP, CORS or unknown-route behavior. Vitest validates strict parsing, real
|
||||||
Footer population, empty fallback, malformed snapshots and absence of invented
|
Footer population, empty fallback, malformed snapshots and absence of invented
|
||||||
tile metrics; the seven tile bodies remain placeholders.
|
tile metrics; the seven tile bodies remain placeholders.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
#include "trainlog/web_dashboard.h"
|
#include "trainlog/web_dashboard.h"
|
||||||
|
|
||||||
|
|
@ -36,4 +40,6 @@ static bool test_empty_and_real_facts(void){
|
||||||
|
|
||||||
static bool test_json_escaping_and_limit(void){TrainlogWebDashboardSnapshot s;char json[TRAINLOG_WEB_JSON_CAPACITY];char tiny[8];size_t size;(void)memset(&s,0,sizeof(s));(void)snprintf(s.user_display_name,sizeof(s.user_display_name),"A\"B\\C\nÉ");(void)snprintf(s.generated_at,sizeof(s.generated_at),"2026-09-16T12:00:00Z");s.next_session_reason="no_persisted_executable_plan";s.cardio_reason="no_cardio_data_source";CHECK(trainlog_web_dashboard_serialize(&s,json,sizeof(json),&size)==TRAINLOG_STATUS_OK&&size>0U);CHECK(strstr(json,"A\\\"B\\\\C\\nÉ")!=NULL);CHECK(trainlog_web_dashboard_serialize(&s,tiny,sizeof(tiny),&size)==TRAINLOG_STATUS_SYSTEM_ERROR);s.user_display_name[0]=(char)0xc3;s.user_display_name[1]='\0';CHECK(trainlog_web_dashboard_serialize(&s,json,sizeof(json),&size)==TRAINLOG_STATUS_SYSTEM_ERROR);return true;}
|
static bool test_json_escaping_and_limit(void){TrainlogWebDashboardSnapshot s;char json[TRAINLOG_WEB_JSON_CAPACITY];char tiny[8];size_t size;(void)memset(&s,0,sizeof(s));(void)snprintf(s.user_display_name,sizeof(s.user_display_name),"A\"B\\C\nÉ");(void)snprintf(s.generated_at,sizeof(s.generated_at),"2026-09-16T12:00:00Z");s.next_session_reason="no_persisted_executable_plan";s.cardio_reason="no_cardio_data_source";CHECK(trainlog_web_dashboard_serialize(&s,json,sizeof(json),&size)==TRAINLOG_STATUS_OK&&size>0U);CHECK(strstr(json,"A\\\"B\\\\C\\nÉ")!=NULL);CHECK(trainlog_web_dashboard_serialize(&s,tiny,sizeof(tiny),&size)==TRAINLOG_STATUS_SYSTEM_ERROR);s.user_display_name[0]=(char)0xc3;s.user_display_name[1]='\0';CHECK(trainlog_web_dashboard_serialize(&s,json,sizeof(json),&size)==TRAINLOG_STATUS_SYSTEM_ERROR);return true;}
|
||||||
|
|
||||||
int main(void){return test_empty_and_real_facts()&&test_json_escaping_and_limit()?0:1;}
|
static bool test_corrupt_timestamp_sets_invalid_data(void){char path[]="/tmp/trainlog-web-dashboard-XXXXXX";int fd=mkstemp(path);TrainlogDatabase*d=NULL;sqlite3*raw=NULL;TrainlogWebDashboardSnapshot s;TrainlogWebDashboardQuery q={INT64_C(1789560000)};CHECK(fd>=0);CHECK(close(fd)==0);CHECK(trainlog_database_open(path,&d)==TRAINLOG_STATUS_OK);CHECK(insert_fact(d,"se_92000000-0000-4000-8000-000000000001","en_92000000-0000-4000-8000-000000000001","2026-09-15T10:00:00+02:00",0)==false);CHECK(trainlog_database_insert_exercise_profiled(d,"ex_90000000-0000-4000-8000-000000000001","Corruptible","corruptible",TRAINLOG_TRACKING_REPS,TRAINLOG_RECORDING_SETS,(TrainlogExerciseDataFields)0)==TRAINLOG_STATUS_OK);CHECK(insert_fact(d,"se_92000000-0000-4000-8000-000000000001","en_92000000-0000-4000-8000-000000000001","2026-09-15T10:00:00+02:00",0));trainlog_database_close(d);d=NULL;CHECK(sqlite3_open(path,&raw)==SQLITE_OK);CHECK(sqlite3_exec(raw,"UPDATE sessions SET started_at='corrupt';",NULL,NULL,NULL)==SQLITE_OK);CHECK(sqlite3_close(raw)==SQLITE_OK);raw=NULL;CHECK(trainlog_database_open(path,&d)==TRAINLOG_STATUS_OK);CHECK(trainlog_web_dashboard_load(d,&q,&s)==TRAINLOG_STATUS_OK);CHECK(s.invalid_data);trainlog_database_close(d);CHECK(unlink(path)==0);return true;}
|
||||||
|
|
||||||
|
int main(void){return test_empty_and_real_facts()&&test_json_escaping_and_limit()&&test_corrupt_timestamp_sets_invalid_data()?0:1;}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
#include "trainlog/database.h"
|
#include "trainlog/database.h"
|
||||||
#include "trainlog/web_server.h"
|
#include "trainlog/web_server.h"
|
||||||
#include "web_assets.h"
|
#include "web_assets.h"
|
||||||
|
|
@ -252,6 +254,46 @@ static bool test_port_in_use(TrainlogDatabase *database)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool test_dashboard_core_error_translation(void)
|
||||||
|
{
|
||||||
|
char path[] = "/tmp/trainlog-web-error-XXXXXX";
|
||||||
|
char response[4096];
|
||||||
|
TrainlogDatabase *database = NULL;
|
||||||
|
sqlite3 *raw = NULL;
|
||||||
|
uint16_t port;
|
||||||
|
pid_t child;
|
||||||
|
int fd = mkstemp(path);
|
||||||
|
int status;
|
||||||
|
CHECK(fd >= 0 && close(fd) == 0);
|
||||||
|
CHECK(trainlog_database_open(path, &database) == TRAINLOG_STATUS_OK);
|
||||||
|
trainlog_database_close(database);
|
||||||
|
database = NULL;
|
||||||
|
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
|
||||||
|
CHECK(sqlite3_exec(raw, "DROP TABLE sessions;", NULL, NULL, NULL) == SQLITE_OK);
|
||||||
|
CHECK(sqlite3_close(raw) == SQLITE_OK);
|
||||||
|
raw = NULL;
|
||||||
|
CHECK(trainlog_database_open(path, &database) == TRAINLOG_STATUS_OK);
|
||||||
|
CHECK(reserve_port(&port, 0) == -2);
|
||||||
|
child = fork();
|
||||||
|
CHECK(child >= 0);
|
||||||
|
if (child == 0) {
|
||||||
|
volatile sig_atomic_t stop = 0;
|
||||||
|
char diagnostic[256];
|
||||||
|
_exit(trainlog_web_server_run(database, port, &stop, diagnostic,
|
||||||
|
sizeof(diagnostic)) == 0 ? 0 : 1);
|
||||||
|
}
|
||||||
|
CHECK(exchange(port,
|
||||||
|
"GET /api/v1/dashboard HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n",
|
||||||
|
response, sizeof(response)));
|
||||||
|
CHECK(strstr(response, "HTTP/1.1 500") != NULL);
|
||||||
|
CHECK(strstr(response, "\"dashboard_unavailable\"") != NULL);
|
||||||
|
CHECK(kill(child, SIGTERM) == 0);
|
||||||
|
CHECK(waitpid(child, &status, 0) == child);
|
||||||
|
trainlog_database_close(database);
|
||||||
|
CHECK(unlink(path) == 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
TrainlogDatabase *database = NULL;
|
TrainlogDatabase *database = NULL;
|
||||||
|
|
@ -264,7 +306,8 @@ int main(void)
|
||||||
passed = trainlog_web_server_run(database, 8080U, &stop, diagnostic,
|
passed = trainlog_web_server_run(database, 8080U, &stop, diagnostic,
|
||||||
sizeof(diagnostic)) != 0 && strstr(diagnostic, "frontend") != NULL;
|
sizeof(diagnostic)) != 0 && strstr(diagnostic, "frontend") != NULL;
|
||||||
} else {
|
} else {
|
||||||
passed = test_http_contract(database) && test_port_in_use(database);
|
passed = test_http_contract(database) && test_port_in_use(database) &&
|
||||||
|
test_dashboard_core_error_translation();
|
||||||
}
|
}
|
||||||
trainlog_database_close(database);
|
trainlog_database_close(database);
|
||||||
return passed ? 0 : 1;
|
return passed ? 0 : 1;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue