Mercurial
view mrjunejune/test/integration_test.c @ 71:75de5903355c
Giagantic changes that update Dowa library to be more align with stb style array and hashmap. Updated Seobeo to be caching on server side instead of file level caching. Deleted bunch of things I don't really use.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Sun, 28 Dec 2025 20:34:22 -0800 |
| parents | 6626ec933933 |
| children | 092afa595764 |
line wrap: on
line source
#include "seobeo/seobeo.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #include <signal.h> #include <assert.h> #define TEST_PORT "6969" #define TEST_HOST "127.0.0.1" #define MAX_RESPONSE_SIZE (1024 * 1024) #define SNAPSHOT_DIR "mrjunejune/test/snapshots" // Test case structure typedef struct { const char *path; int expected_status; const char *expected_content; // Loaded from file char *expected_file_path; // Path to expected snapshot file char *actual_response; size_t response_len; } TestCase; // Helper: Convert URL path to filename // "/" -> "root.snapshot" // "/index.html" -> "index.html.snapshot" // "/api/users" -> "api_users.snapshot" void path_to_filename(const char *path, char *filename, size_t max_len) { if (strcmp(path, "/") == 0) { snprintf(filename, max_len, "root.snapshot"); return; } // Remove leading slash and convert remaining slashes to underscores const char *p = path; if (*p == '/') { p++; } char *out = filename; size_t remaining = max_len - 1; while (*p && remaining > 0) { if (*p == '/') { *out++ = '_'; remaining--; } else { *out++ = *p; remaining--; } p++; } // Add .snapshot extension snprintf(out, remaining, ".snapshot"); } // Helper: Read file contents into buffer char* read_file(const char *filepath, size_t *size_out) { FILE *f = fopen(filepath, "rb"); if (!f) { return NULL; } fseek(f, 0, SEEK_END); long fsize = ftell(f); fseek(f, 0, SEEK_SET); char *buffer = malloc(fsize + 1); if (!buffer) { fclose(f); return NULL; } size_t read_size = fread(buffer, 1, fsize, f); buffer[read_size] = '\0'; fclose(f); if (size_out) { *size_out = read_size; } return buffer; } // Helper: Load expected content for a test case int load_expected_content(TestCase *test) { if (!test->expected_file_path) { return -1; } size_t size; test->expected_content = read_file(test->expected_file_path, &size); if (!test->expected_content) { return -1; } return 0; } // Helper: Create test client Seobeo_Handle* create_test_client() { Seobeo_Handle *client = Seobeo_Stream_Handle_Client_Create(TEST_HOST, TEST_PORT, FALSE); if (!client || client->socket < 0) { if (client) { Seobeo_Handle_Destroy(client); } return NULL; } return client; } // Helper: Generate default HTTP GET request int generate_http_get_request(char *buffer, size_t buffer_size, const char *path) { return snprintf( buffer, buffer_size, "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "Connection: close\r\n" "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" "User-Agent: SeobeoTestClient/1.0\r\n" "\r\n", path, TEST_HOST ); } // Helper: Send HTTP request int send_http_request(Seobeo_Handle *client, const char *path, const char *custom_request) { char request_buffer[4096]; int request_len; if (custom_request) { request_len = snprintf(request_buffer, sizeof(request_buffer), "%s", custom_request); } else { request_len = generate_http_get_request(request_buffer, sizeof(request_buffer), path); } if (request_len < 0 || request_len >= sizeof(request_buffer)) { fprintf(stderr, "Request buffer too small\n"); return -1; } Seobeo_Handle_Queue(client, (uint8*)request_buffer, (uint32)request_len); return Seobeo_Handle_Flush(client); } // Helper: Read HTTP response int read_http_response(Seobeo_Handle *client, char **response_out, size_t *response_len_out) { char *response = malloc(MAX_RESPONSE_SIZE); if (!response) { return -1; } size_t total_bytes = 0; int attempts = 0; const int max_attempts = 100; while (attempts++ < max_attempts && total_bytes < MAX_RESPONSE_SIZE - 1) { int bytes_read = Seobeo_Handle_Read(client); if (bytes_read > 0) { size_t to_copy = client->read_buffer_len; if (total_bytes + to_copy > MAX_RESPONSE_SIZE - 1) { to_copy = MAX_RESPONSE_SIZE - 1 - total_bytes; } memcpy(response + total_bytes, client->read_buffer, to_copy); total_bytes += to_copy; Seobeo_Handle_Consume(client, (uint32)to_copy); } else if (bytes_read == -2) { // Connection closed break; } else if (bytes_read == 0) { // Would block usleep(10000); continue; } else { free(response); return -1; } } response[total_bytes] = '\0'; *response_out = response; *response_len_out = total_bytes; return (total_bytes > 0) ? 0 : -1; } // Helper: Parse HTTP status code int parse_http_status(const char *response) { if (!response || strlen(response) < 12) { return -1; } const char *status_start = strstr(response, "HTTP/1.1 "); if (!status_start) { status_start = strstr(response, "HTTP/1.0 "); } if (!status_start) { return -1; } int status_code; if (sscanf(status_start + 9, "%d", &status_code) == 1) { return status_code; } return -1; } // Helper: Check if status is a redirect int is_redirect_status(int status) { return (status >= 300 && status < 400); } // Helper: Execute a test case int execute_test_case(TestCase *test, pid_t server_pid) { printf(" Testing: GET %s (expecting %d)\n", test->path, test->expected_status); Seobeo_Handle *client = create_test_client(); if (!client) { printf(" ✗ Failed to create client connection\n"); return -1; } if (send_http_request(client, test->path, NULL) < 0) { printf(" ✗ Failed to send request\n"); Seobeo_Handle_Destroy(client); return -1; } char *response = NULL; size_t response_len = 0; if (read_http_response(client, &response, &response_len) < 0) { printf(" ✗ Failed to read response\n"); Seobeo_Handle_Destroy(client); return -1; } test->actual_response = response; test->response_len = response_len; int actual_status = parse_http_status(response); if (actual_status != test->expected_status) { printf(" ✗ Status mismatch: expected %d, got %d\n", test->expected_status, actual_status); Seobeo_Handle_Destroy(client); return -1; } printf(" ✓ Status code: %d\n", actual_status); // For redirects, skip content comparison if (is_redirect_status(actual_status)) { printf(" ⚠ Redirect status - skipping content comparison\n"); Seobeo_Handle_Destroy(client); return 0; } // Only verify 200 OK responses against snapshots if (actual_status == 200) { if (!test->expected_content) { printf(" ✗ No expected snapshot found: %s\n", test->expected_file_path); printf(" → Run: bazel run //mrjunejune:create_snapshots\n"); Seobeo_Handle_Destroy(client); return -1; } if (strcmp(response, test->expected_content) != 0) { printf(" ✗ Response does not match expected snapshot\n"); printf(" Expected file: %s\n", test->expected_file_path); Seobeo_Handle_Destroy(client); return -1; } printf(" ✓ Response matches snapshot (%zu bytes)\n", response_len); } Seobeo_Handle_Destroy(client); return 0; } // Helper: Execute custom request test int execute_custom_request_test(const char *name, const char *custom_request, int expected_status, pid_t server_pid) { printf(" Testing: %s (expecting %d)\n", name, expected_status); Seobeo_Handle *client = create_test_client(); if (!client) { printf(" ✗ Failed to create client connection\n"); return -1; } if (send_http_request(client, NULL, custom_request) < 0) { printf(" ✗ Failed to send request\n"); Seobeo_Handle_Destroy(client); return -1; } char *response = NULL; size_t response_len = 0; if (read_http_response(client, &response, &response_len) < 0) { printf(" ✗ Failed to read response\n"); Seobeo_Handle_Destroy(client); return -1; } int actual_status = parse_http_status(response); if (actual_status != expected_status) { printf(" ✗ Status mismatch: expected %d, got %d\n", expected_status, actual_status); free(response); Seobeo_Handle_Destroy(client); return -1; } printf(" ✓ Status code: %d\n", actual_status); printf(" ✓ Response received (%zu bytes)\n", response_len); free(response); Seobeo_Handle_Destroy(client); return 0; } // Helper: Start test server pid_t start_test_server(const char *server_binary) { pid_t server_pid = fork(); if (server_pid < 0) { perror("fork"); return -1; } if (server_pid == 0) { printf("Starting server on port %s...\n", TEST_PORT); execl(server_binary, server_binary, NULL); perror("execl failed"); exit(1); } printf("Server started (PID: %d)\n", server_pid); usleep(100000); int status; pid_t result = waitpid(server_pid, &status, WNOHANG); if (result != 0) { if (WIFEXITED(status)) { fprintf(stderr, "Server exited immediately with code: %d\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { fprintf(stderr, "Server was killed by signal: %d\n", WTERMSIG(status)); } return -1; } sleep(2); printf("Server ready\n\n"); return server_pid; } // Helper: Stop test server void stop_test_server(pid_t server_pid) { if (server_pid > 0) { printf("\nStopping server (PID: %d)...\n", server_pid); kill(server_pid, SIGTERM); waitpid(server_pid, NULL, 0); printf("Server stopped\n"); } } // Helper: Initialize test case with snapshot file void init_test_case(TestCase *test) { char filename[256]; path_to_filename(test->path, filename, sizeof(filename)); test->expected_file_path = malloc(512); snprintf(test->expected_file_path, 512, "%s/%s", SNAPSHOT_DIR, filename); // Load expected content from snapshot file load_expected_content(test); } // Helper: Cleanup test case void cleanup_test_case(TestCase *test) { if (test->actual_response) { free(test->actual_response); } if (test->expected_file_path) { free(test->expected_file_path); } if (test->expected_content) { free((void*)test->expected_content); } } // Main integration test int test_server_client_integration(const char *server_binary) { printf("=== Server-Client Integration Test ===\n"); printf("MODE: Verifying Against Snapshots\n\n"); char cwd[1024]; if (getcwd(cwd, sizeof(cwd)) != NULL) { printf("Working directory: %s\n", cwd); } if (access(server_binary, X_OK) != 0) { printf("Server binary not found: %s\n", server_binary); perror("access"); return -1; } printf("Server binary: %s\n", server_binary); printf("Snapshot directory: %s\n\n", SNAPSHOT_DIR); pid_t server_pid = start_test_server(server_binary); if (server_pid < 0) { return -1; } int failed_tests = 0; int passed_tests = 0; // Define test cases - paths that should succeed (200 OK) TestCase success_tests[] = { {"/", 200, NULL, NULL, NULL, 0}, {"/index.html", 200, NULL, NULL, NULL, 0}, }; int num_success_tests = sizeof(success_tests) / sizeof(success_tests[0]); // Define test cases - paths that should fail (404) TestCase failure_tests[] = { {"/nonexistent", 404, NULL, NULL, NULL, 0}, {"/does/not/exist", 404, NULL, NULL, NULL, 0}, {"/missing.html", 404, NULL, NULL, NULL, 0}, }; int num_failure_tests = sizeof(failure_tests) / sizeof(failure_tests[0]); // Initialize all test cases for (int i = 0; i < num_success_tests; i++) { init_test_case(&success_tests[i]); } for (int i = 0; i < num_failure_tests; i++) { init_test_case(&failure_tests[i]); } // Run success tests printf("Running tests for paths that should succeed:\n"); for (int i = 0; i < num_success_tests; i++) { if (execute_test_case(&success_tests[i], server_pid) == 0) { passed_tests++; } else { failed_tests++; } } printf("\n"); // Run failure tests printf("Running tests for paths that should fail:\n"); for (int i = 0; i < num_failure_tests; i++) { if (execute_test_case(&failure_tests[i], server_pid) == 0) { passed_tests++; } else { failed_tests++; } } printf("\n"); // Test with custom request printf("Running tests with custom requests:\n"); char custom_request[4096]; snprintf(custom_request, sizeof(custom_request), "GET / HTTP/1.1\r\n" "Host: %s\r\n" "Connection: close\r\n" "X-Custom-Header: TestValue\r\n" "\r\n", TEST_HOST); if (execute_custom_request_test("Custom headers GET /", custom_request, 200, server_pid) == 0) { passed_tests++; } else { failed_tests++; } // Cleanup test cases for (int i = 0; i < num_success_tests; i++) { cleanup_test_case(&success_tests[i]); } for (int i = 0; i < num_failure_tests; i++) { cleanup_test_case(&failure_tests[i]); } stop_test_server(server_pid); printf("\n=== Test Summary ===\n"); printf("Passed: %d\n", passed_tests); printf("Failed: %d\n", failed_tests); return (failed_tests == 0) ? 0 : -1; } int main(int argc, char *argv[]) { printf("=== Seobeo Integration Tests ===\n\n"); const char *server_binary = "./mrjunejune_server"; if (argc > 1) { server_binary = argv[1]; } int result = test_server_client_integration(server_binary); if (result == 0) { printf("\n✓ All tests passed!\n"); } else { printf("\n✗ Some tests failed\n"); } return result; }