diff mrjunejune/test/integration_test.c @ 67:6626ec933933

[Seobeo] Separated out Client Server logic. Created test tools.
author June Park <parkjune1995@gmail.com>
date Wed, 24 Dec 2025 09:15:55 -0800
parents
children 092afa595764
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mrjunejune/test/integration_test.c	Wed Dec 24 09:15:55 2025 -0800
@@ -0,0 +1,616 @@
+#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;
+}