#include "web.h" #include "mytime.h" #include #include #include #include #include // @PASTE Stolen from libcurl documentation struct memory { char *body; size_t size; }; static size_t write_to_memory(char *data, size_t size, size_t nmemb, void *clientp) { size_t realsize = size * nmemb; struct memory *mem = (struct memory *)clientp; char *ptr = realloc(mem->body, mem->size + realsize + 1); if(!ptr) return 0; /* out of memory */ mem->body = ptr; memcpy(&(mem->body[mem->size]), data, realsize); mem->size += realsize; mem->body[mem->size] = 0; return realsize; } static size_t read_from_memory(char *data, size_t size, size_t nmemb, void *clientp) { size_t realsize = size * nmemb; struct memory *mem = (struct memory *)clientp; realsize = realsize > mem->size ? mem->size : realsize; memcpy(data, mem->body, realsize); mem->size -= realsize; mem->body += realsize; return realsize; } int main(int argc, char **argv) { enable_fake_time(); // @DEP: The shared-cache mode is deprecated in sqlite, but it's still // there and useful right now. struct App app = { .dbname = "file::memory:?cache=shared", }; prepare_database(&app); struct Server server = { .app = &app, }; web_begin(&server, 8080); curl_global_init(CURL_GLOBAL_ALL); CURLcode curlRes; CURL *curl = curl_easy_init(); assert(curl != NULL); { curlRes = curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:8080/"); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_memory); assert(curlRes == CURLE_OK); struct memory body = {0}; curlRes = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body); assert(curlRes == CURLE_OK); curlRes = curl_easy_perform(curl); assert(curlRes == CURLE_OK); long code; curlRes = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); assert(code == 200); char *ct; curlRes = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct); assert(curlRes == CURLE_OK); } char client_secret[CLIENT_SECRET_LEN] = {0}; { curlRes = curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:8080/api/client"); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_POST, 1L); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_from_memory); assert(curlRes == CURLE_OK); struct memory req_body = { .body = "{\"name\": \"client1\"}", .size = strlen(req_body.body), }; curlRes = curl_easy_setopt(curl, CURLOPT_READDATA, &req_body); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_memory); assert(curlRes == CURLE_OK); struct memory body = {0}; curlRes = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body); assert(curlRes == CURLE_OK); curlRes = curl_easy_perform(curl); assert(curlRes == CURLE_OK); long code; curlRes = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); assert(code == 200); char *ct; curlRes = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct); assert(curlRes == CURLE_OK); assert(ct != NULL); assert(strcmp(ct, "application/json") == 0); assert(body.size >= 0); // Parse response to extract secret cJSON *rootJson = cJSON_ParseWithLength(body.body, body.size); assert(rootJson != NULL); cJSON *idJson = cJSON_GetObjectItemCaseSensitive(rootJson, "id"); assert(cJSON_IsString(idJson)); assert(strcmp(idJson->valuestring, "1") == 0); cJSON *secretJson = cJSON_GetObjectItemCaseSensitive(rootJson, "secret"); assert(cJSON_IsString(secretJson)); assert(strlen(secretJson->valuestring) == 32); strncpy(client_secret, secretJson->valuestring, CLIENT_SECRET_LEN); cJSON_Delete(rootJson); free(body.body); body = (struct memory){0}; } { curlRes = curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:8080/api/report"); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_POST, 1L); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_from_memory); assert(curlRes == CURLE_OK); char req_buf[256]; snprintf(req_buf, sizeof(req_buf), "{\"client\": \"client1\", \"secret\": \"%s\", \"event\": \"begin_backup\"}", client_secret); struct memory req_body = { .body = req_buf, .size = strlen(req_buf), }; curlRes = curl_easy_setopt(curl, CURLOPT_READDATA, &req_body); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_memory); assert(curlRes == CURLE_OK); struct memory body = {0}; curlRes = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body); assert(curlRes == CURLE_OK); curlRes = curl_easy_perform(curl); assert(curlRes == CURLE_OK); long code; curlRes = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); assert(code == 204); char *ct; curlRes = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct); assert(curlRes == CURLE_OK); assert(ct != NULL); assert(strcmp(ct, "application/json") == 0); assert(body.size == 0); body = (struct memory){0}; } progress_time(TIME_JOIN(0, 30, 0)); { curlRes = curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:8080/api/report"); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_POST, 1L); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_from_memory); assert(curlRes == CURLE_OK); char req_buf[256]; snprintf(req_buf, sizeof(req_buf), "{\"client\": \"client1\", \"secret\": \"%s\", \"event\": \"end_backup\"}", client_secret); struct memory req_body = { .body = req_buf, .size = strlen(req_buf), }; curlRes = curl_easy_setopt(curl, CURLOPT_READDATA, &req_body); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_memory); assert(curlRes == CURLE_OK); struct memory body = {0}; curlRes = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body); assert(curlRes == CURLE_OK); curlRes = curl_easy_perform(curl); assert(curlRes == CURLE_OK); long code; curlRes = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); assert(code == 204); char *ct; curlRes = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct); assert(curlRes == CURLE_OK); assert(ct != NULL); assert(strcmp(ct, "application/json") == 0); assert(body.size == 0); body = (struct memory){0}; } // Test: wrong secret should return 401 { curlRes = curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:8080/api/report"); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_POST, 1L); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_from_memory); assert(curlRes == CURLE_OK); struct memory req_body = { .body = "{\"client\": \"client1\", \"secret\": \"wrongsecret\", \"event\": \"begin_backup\"}", .size = strlen("{\"client\": \"client1\", \"secret\": \"wrongsecret\", \"event\": \"begin_backup\"}"), }; curlRes = curl_easy_setopt(curl, CURLOPT_READDATA, &req_body); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_memory); assert(curlRes == CURLE_OK); struct memory body = {0}; curlRes = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body); assert(curlRes == CURLE_OK); curlRes = curl_easy_perform(curl); assert(curlRes == CURLE_OK); long code; curlRes = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); assert(code == 401); // Unauthorized } // Test: Starting a new backup should interrupt the previous RUNNING backup { // Begin first backup curlRes = curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:8080/api/report"); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_POST, 1L); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_from_memory); assert(curlRes == CURLE_OK); char req_buf[256]; snprintf(req_buf, sizeof(req_buf), "{\"client\": \"client1\", \"secret\": \"%s\", \"event\": \"begin_backup\"}", client_secret); struct memory req_body = { .body = req_buf, .size = strlen(req_buf) }; curlRes = curl_easy_setopt(curl, CURLOPT_READDATA, &req_body); assert(curlRes == CURLE_OK); curlRes = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_memory); assert(curlRes == CURLE_OK); struct memory body = {0}; curlRes = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body); assert(curlRes == CURLE_OK); curlRes = curl_easy_perform(curl); assert(curlRes == CURLE_OK); long code; curlRes = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); assert(code == 204); body = (struct memory){0}; progress_time(TIME_JOIN(0, 10, 0)); // Advance 10 minutes // Begin second backup (should interrupt the first) snprintf(req_buf, sizeof(req_buf), "{\"client\": \"client1\", \"secret\": \"%s\", \"event\": \"begin_backup\"}", client_secret); req_body = (struct memory){ .body = req_buf, .size = strlen(req_buf) }; curlRes = curl_easy_setopt(curl, CURLOPT_READDATA, &req_body); assert(curlRes == CURLE_OK); body = (struct memory){0}; curlRes = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body); assert(curlRes == CURLE_OK); curlRes = curl_easy_perform(curl); assert(curlRes == CURLE_OK); curlRes = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); assert(code == 204); body = (struct memory){0}; // Use internal function to verify backup statuses struct Request req = { .server = &server }; struct GetClientResult *result = malloc(sizeof(struct GetClientResult) + sizeof(struct BackupRecord) * MAX_BACKUPS_DISPLAY); int ret = get_client_with_backups(&req, 1, result); assert(ret == 0); assert(result->found == 1); // Should have at least 2 backups (the interrupted one and the running one) // Note: There may be more from earlier tests assert(result->backups_len >= 2); // Backups are ordered by started DESC, so newest first // The most recent backup should be RUNNING assert(strcmp(result->backups[0].status, "RUNNING") == 0); // The previous backup should be INTERRUPTED assert(strcmp(result->backups[1].status, "INTERRUPTED") == 0); // The interrupted backup should have a completed timestamp assert(result->backups[1].completed[0] != '\0'); free(result); } web_join(&server); }