Mercurial
view mrjunejune/test/integration_test.c @ 279:b3b547563ec7
Add Google connector service and agent wiki
Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code.
Co-authored-by: Copilot <[email protected]>
Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 17 Aug 2026 22:22:36 -0700 |
| parents | 1f9877b637e9 |
| children |
line wrap: on
line source
#include "mrjunejune/test/test.h" // 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; void debug_diff(const char *expected, const char *actual) { printf("\n--- DIFF (Expected vs Actual) ---\n"); // Create copies to use with strtok (strtok modifies the string) char *exp_copy = strdup(expected); char *act_copy = strdup(actual); char *exp_line = strtok(exp_copy, "\n"); char *act_line = strtok(act_copy, "\n"); int line_num = 1; while (exp_line != NULL || act_line != NULL) { if (exp_line && act_line && strcmp(exp_line, act_line) == 0) { // Lines match - optional: print nothing or a dot } else { printf("Line %d mismatch:\n", line_num); printf(" EXP: [%s]\n", exp_line ? exp_line : "(end of string)"); printf(" ACT: [%s]\n", act_line ? act_line : "(end of string)"); printf(" -----------------------------------\n"); } exp_line = strtok(NULL, "\n"); act_line = strtok(NULL, "\n"); line_num++; } free(exp_copy); free(act_copy); } // 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; } int execute_test_case(TestCase *test, pid_t server_pid) { printf(" Testing: GET %s (expecting %d)\n", test->path, test->expected_status); int32 max_url_length = 1024*3; char *url = malloc(sizeof(char)*max_url_length); snprintf(url, max_url_length, "%s%s", TEST_URL, test->path); Seobeo_Client_Request *p_req = Seobeo_Client_Request_Create(url); if (!p_req) { printf("Can't create requests"); return -1; } Seobeo_Client_Response *p_resp = Seobeo_Client_Request_Execute(p_req); if (!p_resp) { printf("No response"); return -1; } if (p_resp->status_code != test->expected_status) { printf(" ✗ Status mismatch: expected %d, got %d\n", test->expected_status, p_resp->status_code); Seobeo_Client_Response_Destroy(p_resp); Seobeo_Client_Request_Destroy(p_req); return -1; } printf(" ✓ Status code: %d\n", p_resp->status_code); if (p_resp->status_code == 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_Client_Response_Destroy(p_resp); Seobeo_Client_Request_Destroy(p_req); return -1; } if (strcmp(p_resp->body, test->expected_content) != 0) { printf(" ✗ Response does not match expected snapshot\n"); printf(" Expected file: %s\n", test->expected_file_path); debug_diff(p_resp->body, test->expected_content); Seobeo_Client_Response_Destroy(p_resp); Seobeo_Client_Request_Destroy(p_req); return -1; } printf(" ✓ Response matches snapshot (%zu bytes)\n", p_resp->body_length); } Seobeo_Client_Response_Destroy(p_resp); Seobeo_Client_Request_Destroy(p_req); return 0; } int send_post_file(Seobeo_Handle *p_req, 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(p_req, (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(p_req, (uint8*)ptr, (uint32)chunk_size); ptr += chunk_size; remaining -= chunk_size; } return Seobeo_Handle_Flush(p_req); } 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; } 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; } char url[1024]; snprintf(url, sizeof(url), "%s%s", TEST_URL, endpoint); Seobeo_Client_Request *p_req = Seobeo_Client_Request_Create(url); Seobeo_Client_Request_Set_Method(p_req, "POST"); Seobeo_Client_Request_Set_Body(p_req, (uint8*)file_data, (uint32)file_size); Seobeo_Client_Request_Add_Header_Array(p_req, "Content-Type: application/octet-stream"); Seobeo_Client_Response *p_resp = Seobeo_Client_Request_Execute(p_req); free(file_data); if (!p_resp || p_resp->status_code != 200) { printf(" ✗ Conversion failed with status: %d, %s\n", p_resp ? p_resp->status_code : 0, p_resp->body); if (p_resp) Seobeo_Client_Response_Destroy(p_resp); Seobeo_Client_Request_Destroy(p_req); return -1; } // Extract download URL from JSON body char download_url_path[512]; if (!extract_json_field((char*)p_resp->body, "download_url", download_url_path, sizeof(download_url_path))) { printf(" ✗ Failed to extract download_url\n"); Seobeo_Client_Response_Destroy(p_resp); Seobeo_Client_Request_Destroy(p_req); return -1; } printf(" ✓ Conversion succeeded. Download URL: %s\n", download_url_path); Seobeo_Client_Response_Destroy(p_resp); Seobeo_Client_Request_Destroy(p_req); usleep(100000); printf(" → Testing download: GET %s\n", download_url_path); char full_download_url[1024]; snprintf(full_download_url, sizeof(full_download_url), "%s%s", TEST_URL, download_url_path); p_req = Seobeo_Client_Request_Create(full_download_url); p_resp = Seobeo_Client_Request_Execute(p_req); if (!p_resp || p_resp->status_code != 200) { printf(" ✗ Download failed\n"); printf("%s\n", p_resp->body); if (p_resp) Seobeo_Client_Response_Destroy(p_resp); Seobeo_Client_Request_Destroy(p_req); return -1; } printf(" ✓ Downloaded converted file (%u bytes)\n", p_resp->body_length); Seobeo_Client_Response_Destroy(p_resp); Seobeo_Client_Request_Destroy(p_req); return 0; } // 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-p_req 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; 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}, {"/tools/hls_player", 200, NULL, NULL, NULL, 0}, {"/tools/latex_editor", 200, NULL, NULL, NULL, 0}, {"/talk", 200, NULL, NULL, NULL, 0}, {"/jrpg", 200, NULL, NULL, NULL, 0}, }; int num_success_tests = sizeof(success_tests) / sizeof(success_tests[0]); 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}, {"/tools/hls_player/index.html", 301, NULL, NULL, NULL, 0}, {"/tools/latex_editor/index.html", 301, NULL, NULL, NULL, 0}, {"/jrpg/index.html", 301, NULL, NULL, NULL, 0}, }; int num_redirect_tests = sizeof(redirect_tests) / sizeof(redirect_tests[0]); TestCase failure_tests[] = { {"/nonexistent", 404, NULL, NULL, NULL, 0}, {"/does/not/exist", 404, NULL, NULL, NULL, 0}, {"/missing.html", 404, NULL, NULL, NULL, 0}, {"/api/inference/health", 503, NULL, NULL, NULL, 0}, }; int num_failure_tests = sizeof(failure_tests) / sizeof(failure_tests[0]); 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]); 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"); 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"); 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"); printf("Running tests for POST conversion endpoints:\n"); 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"); if (test_file_conversion("/api/convert/video-to-mp4", "mrjunejune/test/test_avi.avi", "video/mp4", server_pid) == 0) passed_tests++; else failed_tests++; 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; }