feat(test): add new tests
This commit is contained in:
+2
-10
@@ -22,6 +22,7 @@ add_global_arguments('-DEVELOPER', language: 'c')
|
||||
add_global_arguments('-D_POSIX_C_SOURCE=200809L', language: 'c')
|
||||
|
||||
exe = executable('cws', server, include_directories: include_dirs, dependencies: deps)
|
||||
subdir('test')
|
||||
|
||||
# Commands
|
||||
|
||||
@@ -35,13 +36,4 @@ if clangformat.found()
|
||||
'clang-format',
|
||||
],
|
||||
)
|
||||
endif
|
||||
|
||||
# Test
|
||||
|
||||
test_src = files('test/server.c')
|
||||
test_curl_dep = dependency('libcurl', required: false)
|
||||
test_deps = [test_curl_dep]
|
||||
test_exec = executable('test_http', test_src, dependencies: test_deps)
|
||||
|
||||
test('index get', test_exec, args: [exe.full_path()])
|
||||
endif
|
||||
+1
-1
@@ -21,7 +21,7 @@ static int response_get_headers(hashmap_s *headers, char *out_headers, size_t le
|
||||
size_t keys_len = 0;
|
||||
char **keys = (char **)hm_get_keys(headers, &keys_len);
|
||||
if (!keys) {
|
||||
cws_log_debug("no headers??");
|
||||
cws_log_debug("%s", "no headers??");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -16,18 +16,18 @@ int main(void) {
|
||||
cws_log_init();
|
||||
|
||||
if (signal(SIGINT, cws_signal_handler) == SIG_ERR) {
|
||||
cws_log_error("signal()");
|
||||
cws_log_error("%s", "signal()");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (signal(SIGTERM, cws_signal_handler) == SIG_ERR) {
|
||||
cws_log_error("signal()");
|
||||
cws_log_error("%s", "signal()");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
cws_config_s *config = cws_config_init();
|
||||
if (!config) {
|
||||
cws_log_error("Unable to parse config");
|
||||
cws_log_error("%s", "Unable to parse config");
|
||||
cws_log_shutdown();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
@@ -51,7 +51,7 @@ int main(void) {
|
||||
cws_log_error("Unable to start web server: %s", cws_error_str(ret));
|
||||
}
|
||||
|
||||
cws_log_info("Shutting down cws");
|
||||
cws_log_info("%s", "Shutting down cws");
|
||||
cws_server_shutdown(&server);
|
||||
cws_config_free(config);
|
||||
cws_log_shutdown();
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
#include "common.h"
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
bool start_server(const char *server_path, pid_t *pid_out) {
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
execl(server_path, "cws", NULL);
|
||||
fprintf(stderr, "execl failed: %s\n", strerror(errno));
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
if (pid < 0) {
|
||||
fprintf(stderr, "fork failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
*pid_out = pid;
|
||||
fprintf(stdout, "[INFO] server pid: %d\n", pid);
|
||||
return true;
|
||||
}
|
||||
|
||||
void stop_server(pid_t pid) {
|
||||
if (pid <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
kill(pid, SIGTERM);
|
||||
waitpid(pid, NULL, 0);
|
||||
fprintf(stdout, "[INFO] server stopped (pid=%d)\n", pid);
|
||||
}
|
||||
|
||||
bool perform_request(const char *url, const char *method, http_result_s *out) {
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
fprintf(stderr, "[ERROR] curl_easy_init failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(out, 0, sizeof(*out));
|
||||
out->content_length = -1;
|
||||
out->error[0] = '\0';
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method);
|
||||
curl_easy_setopt(curl, CURLOPT_NOBODY, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, out->error);
|
||||
|
||||
out->curl_code = curl_easy_perform(curl);
|
||||
if (out->curl_code == CURLE_OK) {
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &out->code);
|
||||
curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &out->content_length);
|
||||
|
||||
char *ct = NULL;
|
||||
curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
|
||||
if (ct) {
|
||||
strncpy(out->content_type, ct, sizeof(out->content_type) - 1);
|
||||
out->content_type[sizeof(out->content_type) - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
return out->curl_code == CURLE_OK;
|
||||
}
|
||||
|
||||
bool wait_until_ready(void) {
|
||||
char url[256];
|
||||
snprintf(url, sizeof(url), "%s/", BASE_URL);
|
||||
|
||||
for (int i = 0; i < MAX_RETRIES; i++) {
|
||||
http_result_s result;
|
||||
if (perform_request(url, "GET", &result)) {
|
||||
fprintf(stdout, "[INFO] server ready after %d attempt(s), code=%ld\n", i + 1, result.code);
|
||||
return true;
|
||||
}
|
||||
|
||||
fprintf(stdout, "[INFO] server not ready (%d/%d): %s\n", i + 1, MAX_RETRIES,
|
||||
result.error[0] ? result.error : "unknown error");
|
||||
sleep(RETRY_SLEEP_SEC);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int assert_status(const char *name, const char *url, const char *method, long expected) {
|
||||
http_result_s result;
|
||||
if (!perform_request(url, method, &result)) {
|
||||
fprintf(stderr, "[FAIL] %s: request failed (%s)\n", name,
|
||||
result.error[0] ? result.error : "unknown curl error");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stdout, "[TEST] %s -> %s %s => %ld (expected %ld)\n", name, method, url, result.code, expected);
|
||||
|
||||
if (result.code != expected) {
|
||||
fprintf(stderr, "[FAIL] %s: expected HTTP %ld, got %ld\n", name, expected, result.code);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef CWS_TEST_COMMON_H
|
||||
#define CWS_TEST_COMMON_H
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#define BASE_URL "http://localhost:3030"
|
||||
#define MAX_RETRIES 15
|
||||
#define RETRY_SLEEP_SEC 1
|
||||
|
||||
typedef struct http_result {
|
||||
long code;
|
||||
long content_length;
|
||||
char content_type[256];
|
||||
char error[CURL_ERROR_SIZE];
|
||||
CURLcode curl_code;
|
||||
} http_result_s;
|
||||
|
||||
bool start_server(const char *server_path, pid_t *pid_out);
|
||||
void stop_server(pid_t pid);
|
||||
bool wait_until_ready(void);
|
||||
bool perform_request(const char *url, const char *method, http_result_s *out);
|
||||
int assert_status(const char *name, const char *url, const char *method, long expected);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,43 @@
|
||||
test_common_src = files('common.c')
|
||||
test_curl_dep = dependency('libcurl', required: false)
|
||||
test_deps = [test_curl_dep]
|
||||
|
||||
test_get_index = executable(
|
||||
'test_get_index',
|
||||
test_common_src,
|
||||
files('test_get_index.c'),
|
||||
dependencies: test_deps,
|
||||
)
|
||||
test('index get', test_get_index, args: [exe.full_path()])
|
||||
|
||||
test_get_missing = executable(
|
||||
'test_get_missing',
|
||||
test_common_src,
|
||||
files('test_get_missing.c'),
|
||||
dependencies: test_deps,
|
||||
)
|
||||
test('missing 404', test_get_missing, args: [exe.full_path()])
|
||||
|
||||
test_post = executable(
|
||||
'test_post',
|
||||
test_common_src,
|
||||
files('test_post.c'),
|
||||
dependencies: test_deps,
|
||||
)
|
||||
test('post not implemented', test_post, args: [exe.full_path()])
|
||||
|
||||
test_traversal = executable(
|
||||
'test_traversal',
|
||||
test_common_src,
|
||||
files('test_traversal.c'),
|
||||
dependencies: test_deps,
|
||||
)
|
||||
test('traversal blocked', test_traversal, args: [exe.full_path()])
|
||||
|
||||
test_content_length = executable(
|
||||
'test_content_length',
|
||||
test_common_src,
|
||||
files('test_content_length.c'),
|
||||
dependencies: test_deps,
|
||||
)
|
||||
test('content length present', test_content_length, args: [exe.full_path()])
|
||||
@@ -1,58 +0,0 @@
|
||||
#include <curl/curl.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define URL "http://localhost:3030"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *server_path = argv[1];
|
||||
fprintf(stdout, "server path: %s\n", server_path);
|
||||
|
||||
/* Run the server as child proc */
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
execl(server_path, "cws", NULL);
|
||||
fprintf(stderr, "execl failed: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (pid == -1) {
|
||||
fprintf(stderr, "fork failed: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fprintf(stdout, "pid: %d\n", pid);
|
||||
|
||||
CURL *curl = curl_easy_init();
|
||||
curl_easy_setopt(curl, CURLOPT_URL, URL);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 1L);
|
||||
|
||||
long http_code = 0;
|
||||
int max_retries = 10;
|
||||
for (int i = 0; i < max_retries; i++) {
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
if (res == CURLE_OK) {
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
break;
|
||||
}
|
||||
fprintf(stdout, "Server not ready, retry %d/%d...\n", i + 1, max_retries);
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
fprintf(stdout, "http_code: %ld\n", http_code);
|
||||
|
||||
/* Kill the server */
|
||||
kill(pid, SIGTERM);
|
||||
waitpid(pid, NULL, 0);
|
||||
|
||||
return http_code == 200 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#include "common.h"
|
||||
#include <stdio.h>
|
||||
|
||||
static int test_content_length_present_on_index(void) {
|
||||
char url[256];
|
||||
snprintf(url, sizeof(url), "%s/", BASE_URL);
|
||||
|
||||
http_result_s result;
|
||||
if (!perform_request(url, "GET", &result)) {
|
||||
fprintf(stderr, "[FAIL] content-length on index: request failed (%s)\n",
|
||||
result.error[0] ? result.error : "unknown curl error");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stdout, "[TEST] content-length on index -> code=%ld, content-length=%ld\n", result.code,
|
||||
result.content_length);
|
||||
|
||||
if (result.code != 200) {
|
||||
fprintf(stderr, "[FAIL] content-length on index: expected 200, got %ld\n", result.code);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (result.content_length < 0) {
|
||||
fprintf(stderr, "[FAIL] content-length on index: missing/invalid content length\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <server_binary_path>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
|
||||
const char *server_path = argv[1];
|
||||
fprintf(stdout, "[INFO] server path: %s\n", server_path);
|
||||
|
||||
pid_t server_pid = -1;
|
||||
if (!start_server(server_path, &server_pid)) {
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!wait_until_ready()) {
|
||||
fprintf(stderr, "[FAIL] server did not become ready in time\n");
|
||||
stop_server(server_pid);
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int failures = test_content_length_present_on_index();
|
||||
|
||||
stop_server(server_pid);
|
||||
curl_global_cleanup();
|
||||
|
||||
if (failures == 0) {
|
||||
fprintf(stdout, "[PASS] test_content_length passed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(stderr, "[FAIL] test_content_length failed\n");
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "common.h"
|
||||
#include <stdio.h>
|
||||
|
||||
static int test_get_index_ok(void) {
|
||||
char url[256];
|
||||
snprintf(url, sizeof(url), "%s/", BASE_URL);
|
||||
return assert_status("GET / returns 200", url, "GET", 200);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <server_binary_path>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
|
||||
const char *server_path = argv[1];
|
||||
fprintf(stdout, "[INFO] server path: %s\n", server_path);
|
||||
|
||||
pid_t server_pid = -1;
|
||||
if (!start_server(server_path, &server_pid)) {
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!wait_until_ready()) {
|
||||
fprintf(stderr, "[FAIL] server did not become ready in time\n");
|
||||
stop_server(server_pid);
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int failures = test_get_index_ok();
|
||||
|
||||
stop_server(server_pid);
|
||||
curl_global_cleanup();
|
||||
|
||||
if (failures == 0) {
|
||||
fprintf(stdout, "[PASS] test_get_index passed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(stderr, "[FAIL] test_get_index failed\n");
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "common.h"
|
||||
#include <stdio.h>
|
||||
|
||||
static int test_get_missing_404(void) {
|
||||
char url[256];
|
||||
snprintf(url, sizeof(url), "%s/does-not-exist-xyz", BASE_URL);
|
||||
return assert_status("GET /missing returns 404", url, "GET", 404);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <server_binary_path>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
|
||||
const char *server_path = argv[1];
|
||||
fprintf(stdout, "[INFO] server path: %s\n", server_path);
|
||||
|
||||
pid_t server_pid = -1;
|
||||
if (!start_server(server_path, &server_pid)) {
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!wait_until_ready()) {
|
||||
fprintf(stderr, "[FAIL] server did not become ready in time\n");
|
||||
stop_server(server_pid);
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int failures = test_get_missing_404();
|
||||
|
||||
stop_server(server_pid);
|
||||
curl_global_cleanup();
|
||||
|
||||
if (failures == 0) {
|
||||
fprintf(stdout, "[PASS] test_get_missing passed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(stderr, "[FAIL] test_get_missing failed\n");
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "common.h"
|
||||
#include <stdio.h>
|
||||
|
||||
static int test_post_not_implemented(void) {
|
||||
char url[256];
|
||||
snprintf(url, sizeof(url), "%s/", BASE_URL);
|
||||
return assert_status("POST / returns 501", url, "POST", 501);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <server_binary_path>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
|
||||
const char *server_path = argv[1];
|
||||
fprintf(stdout, "[INFO] server path: %s\n", server_path);
|
||||
|
||||
pid_t server_pid = -1;
|
||||
if (!start_server(server_path, &server_pid)) {
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!wait_until_ready()) {
|
||||
fprintf(stderr, "[FAIL] server did not become ready in time\n");
|
||||
stop_server(server_pid);
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int failures = test_post_not_implemented();
|
||||
|
||||
stop_server(server_pid);
|
||||
curl_global_cleanup();
|
||||
|
||||
if (failures == 0) {
|
||||
fprintf(stdout, "[PASS] test_post passed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(stderr, "[FAIL] test_post failed\n");
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "common.h"
|
||||
#include <stdio.h>
|
||||
|
||||
static int test_traversal_blocked(void) {
|
||||
char url[256];
|
||||
snprintf(url, sizeof(url), "%s/../config.toml", BASE_URL);
|
||||
|
||||
http_result_s result;
|
||||
if (!perform_request(url, "GET", &result)) {
|
||||
fprintf(stderr, "[FAIL] traversal blocked: request failed (%s)\n",
|
||||
result.error[0] ? result.error : "unknown curl error");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stdout, "[TEST] traversal blocked -> GET %s => %ld (expected 404 or 500)\n", url, result.code);
|
||||
|
||||
/* Accept either 404 (ideal) or 500 (still blocked but internal error path). */
|
||||
if (result.code != 404 && result.code != 500) {
|
||||
fprintf(stderr, "[FAIL] traversal blocked: expected 404/500, got %ld\n", result.code);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <server_binary_path>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
|
||||
const char *server_path = argv[1];
|
||||
fprintf(stdout, "[INFO] server path: %s\n", server_path);
|
||||
|
||||
pid_t server_pid = -1;
|
||||
if (!start_server(server_path, &server_pid)) {
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!wait_until_ready()) {
|
||||
fprintf(stderr, "[FAIL] server did not become ready in time\n");
|
||||
stop_server(server_pid);
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int failures = test_traversal_blocked();
|
||||
|
||||
stop_server(server_pid);
|
||||
curl_global_cleanup();
|
||||
|
||||
if (failures == 0) {
|
||||
fprintf(stdout, "[PASS] test_traversal passed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(stderr, "[FAIL] test_traversal failed\n");
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user