Mercurial
view mrjunejune/test/integration_test.c @ 134:75e7aac67fa2 websocket-blog
Closing blog branch since it is written
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Fri, 09 Jan 2026 09:30:30 -0800 |
| parents | 96628cf126a0 |
| children | e7899c93da77 |
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) #ifndef SNAPSHOT_DIR // TODO: Make it as current directory /snapshots... #define SNAPSHOT_DIR "mrjunejune/test/snapshots" #endif // 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: Send POST request with file data int send_post_file(Seobeo_Handle *client, const char *path, const char *file_data, size_t file_size) { char request_buffer[8192]; int header_len = snprintf( request_buffer, sizeof(request_buffer), "POST %s HTTP/1.1\r\n" "Host: %s\r\n" "Content-Type: application/octet-stream\r\n" "Content-Length: %zu\r\n" "Connection: close\r\n" "\r\n", path, TEST_HOST, file_size ); if (header_len < 0 || header_len >= sizeof(request_buffer)) { fprintf(stderr, "Request header too large\n"); return -1; } // Send headers Seobeo_Handle_Queue(client, (uint8*)request_buffer, (uint32)header_len); // Send file data in chunks if needed size_t remaining = file_size; const char *ptr = file_data; while (remaining > 0) { size_t chunk_size = remaining > 4096 ? 4096 : remaining; Seobeo_Handle_Queue(client, (uint8*)ptr, (uint32)chunk_size); ptr += chunk_size; remaining -= chunk_size; } return Seobeo_Handle_Flush(client); } // Helper: Extract JSON field value from response body char* extract_json_field(const char *json, const char *field, char *buffer, size_t buffer_size) { char search_pattern[256]; snprintf(search_pattern, sizeof(search_pattern), "\"%s\":\"", field); const char *start = strstr(json, search_pattern); if (!start) { return NULL; } start += strlen(search_pattern); const char *end = strchr(start, '"'); if (!end) { return NULL; } size_t len = end - start; if (len >= buffer_size) { len = buffer_size - 1; } memcpy(buffer, start, len); buffer[len] = '\0'; return buffer; } // Helper: Test POST file conversion int test_file_conversion(const char *endpoint, const char *test_file_path, const char *expected_format, pid_t server_pid) { printf(" Testing: POST %s\n", endpoint); // Read test file size_t file_size; char *file_data = read_file(test_file_path, &file_size); if (!file_data) { printf(" ✗ Failed to read test file: %s\n", test_file_path); return -1; } printf(" → Loaded test file (%zu bytes)\n", file_size); // Create client and send request Seobeo_Handle *client = create_test_client(); if (!client) { printf(" ✗ Failed to create client connection\n"); free(file_data); return -1; } if (send_post_file(client, endpoint, file_data, file_size) < 0) { printf(" ✗ Failed to send POST request\n"); free(file_data); Seobeo_Handle_Destroy(client); return -1; } free(file_data); // Read response 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; } Seobeo_Handle_Destroy(client); // Parse status int status = parse_http_status(response); if (status != 200) { printf(" ✗ Conversion failed with status: %d\n", status); printf(" Response: %s\n", response); free(response); return -1; } printf(" ✓ Status code: 200\n"); // Extract download URL from JSON response const char *body = strstr(response, "\r\n\r\n"); if (!body) { printf(" ✗ No response body found\n"); free(response); return -1; } body += 4; char download_url[512]; if (!extract_json_field(body, "download_url", download_url, sizeof(download_url))) { printf(" ✗ Failed to extract download_url from response\n"); printf(" Response body: %s\n", body); free(response); return -1; } printf(" ✓ Conversion succeeded\n"); printf(" ✓ Download URL: %s\n", download_url); free(response); // Test downloading the converted file printf(" → Testing download: GET %s\n", download_url); client = create_test_client(); if (!client) { printf(" ✗ Failed to create client for download\n"); return -1; } if (send_http_request(client, download_url, NULL) < 0) { printf(" ✗ Failed to send download request\n"); Seobeo_Handle_Destroy(client); return -1; } response = NULL; response_len = 0; if (read_http_response(client, &response, &response_len) < 0) { printf(" ✗ Failed to read download response\n"); Seobeo_Handle_Destroy(client); return -1; } Seobeo_Handle_Destroy(client); status = parse_http_status(response); if (status != 200) { printf(" ✗ Download failed with status: %d\n", status); free(response); return -1; } // Find body in download response body = strstr(response, "\r\n\r\n"); if (!body) { printf(" ✗ No file data in download response\n"); free(response); return -1; } body += 4; size_t downloaded_size = response_len - (body - response); // Verify content type in response headers const char *content_type = strstr(response, "Content-Type: "); if (!content_type) { printf(" ✗ No Content-Type header in download\n"); free(response); return -1; } if (strstr(content_type, expected_format) == NULL) { printf(" ✗ Wrong content type (expected %s)\n", expected_format); free(response); return -1; } printf(" ✓ Downloaded converted file (%zu bytes)\n", downloaded_size); printf(" ✓ Content-Type: %s\n", expected_format); free(response); 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}, {"/resume", 200, NULL, NULL, NULL, 0}, {"/tools", 200, NULL, NULL, NULL, 0}, {"/tools/markdown_to_html", 200, NULL, NULL, NULL, 0}, {"/tools/file_converter", 200, NULL, NULL, NULL, 0}, }; int num_success_tests = sizeof(success_tests) / sizeof(success_tests[0]); // Define test cases - paths that should redirect (301) TestCase redirect_tests[] = { {"/index.html", 301, NULL, NULL, NULL, 0}, {"/resume/index.html", 301, NULL, NULL, NULL, 0}, {"/tools/index.html", 301, NULL, NULL, NULL, 0}, {"/tools/markdown_to_html/index.html", 301, NULL, NULL, NULL, 0}, {"/tools/file_converter/index.html", 301, NULL, NULL, NULL, 0}, }; int num_redirect_tests = sizeof(redirect_tests) / sizeof(redirect_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_redirect_tests; i++) { init_test_case(&redirect_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 redirect tests printf("Running tests for paths that should redirect:\n"); for (int i = 0; i < num_redirect_tests; i++) { if (execute_test_case(&redirect_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++; } printf("\n"); // Test POST endpoints printf("Running tests for POST conversion endpoints:\n"); // Test image-to-webp conversion if (test_file_conversion("/api/convert/image-to-webp", "mrjunejune/test/shiba.webp", "image/webp", server_pid) == 0) { passed_tests++; } else { failed_tests++; } printf("\n"); // Test video-to-mp4 conversion if (test_file_conversion("/api/convert/video-to-mp4", "mrjunejune/test/test_avi.avi", "video/mp4", 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_redirect_tests; i++) { cleanup_test_case(&redirect_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; }