comparison 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
comparison
equal deleted inserted replaced
66:a0f0ad5e42eb 67:6626ec933933
1 #include "seobeo/seobeo.h"
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <sys/wait.h>
7 #include <signal.h>
8 #include <assert.h>
9
10 #define TEST_PORT "6969"
11 #define TEST_HOST "127.0.0.1"
12 #define MAX_RESPONSE_SIZE (1024 * 1024)
13 #define SNAPSHOT_DIR "mrjunejune/test/snapshots"
14
15 // Test case structure
16 typedef struct {
17 const char *path;
18 int expected_status;
19 const char *expected_content; // Loaded from file
20 char *expected_file_path; // Path to expected snapshot file
21 char *actual_response;
22 size_t response_len;
23 } TestCase;
24
25 // Helper: Convert URL path to filename
26 // "/" -> "root.snapshot"
27 // "/index.html" -> "index.html.snapshot"
28 // "/api/users" -> "api_users.snapshot"
29 void path_to_filename(const char *path, char *filename, size_t max_len)
30 {
31 if (strcmp(path, "/") == 0)
32 {
33 snprintf(filename, max_len, "root.snapshot");
34 return;
35 }
36
37 // Remove leading slash and convert remaining slashes to underscores
38 const char *p = path;
39 if (*p == '/')
40 {
41 p++;
42 }
43
44 char *out = filename;
45 size_t remaining = max_len - 1;
46
47 while (*p && remaining > 0)
48 {
49 if (*p == '/')
50 {
51 *out++ = '_';
52 remaining--;
53 }
54 else
55 {
56 *out++ = *p;
57 remaining--;
58 }
59 p++;
60 }
61
62 // Add .snapshot extension
63 snprintf(out, remaining, ".snapshot");
64 }
65
66 // Helper: Read file contents into buffer
67 char* read_file(const char *filepath, size_t *size_out)
68 {
69 FILE *f = fopen(filepath, "rb");
70 if (!f)
71 {
72 return NULL;
73 }
74
75 fseek(f, 0, SEEK_END);
76 long fsize = ftell(f);
77 fseek(f, 0, SEEK_SET);
78
79 char *buffer = malloc(fsize + 1);
80 if (!buffer)
81 {
82 fclose(f);
83 return NULL;
84 }
85
86 size_t read_size = fread(buffer, 1, fsize, f);
87 buffer[read_size] = '\0';
88 fclose(f);
89
90 if (size_out)
91 {
92 *size_out = read_size;
93 }
94
95 return buffer;
96 }
97
98 // Helper: Load expected content for a test case
99 int load_expected_content(TestCase *test)
100 {
101 if (!test->expected_file_path)
102 {
103 return -1;
104 }
105
106 size_t size;
107 test->expected_content = read_file(test->expected_file_path, &size);
108
109 if (!test->expected_content)
110 {
111 return -1;
112 }
113
114 return 0;
115 }
116
117 // Helper: Create test client
118 Seobeo_Handle* create_test_client()
119 {
120 Seobeo_Handle *client = Seobeo_Stream_Handle_Client_Create(TEST_HOST, TEST_PORT, FALSE);
121 if (!client || client->socket < 0)
122 {
123 if (client)
124 {
125 Seobeo_Handle_Destroy(client);
126 }
127 return NULL;
128 }
129 return client;
130 }
131
132 // Helper: Generate default HTTP GET request
133 int generate_http_get_request(char *buffer, size_t buffer_size, const char *path)
134 {
135 return snprintf(
136 buffer, buffer_size,
137 "GET %s HTTP/1.1\r\n"
138 "Host: %s\r\n"
139 "Connection: close\r\n"
140 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
141 "User-Agent: SeobeoTestClient/1.0\r\n"
142 "\r\n",
143 path, TEST_HOST
144 );
145 }
146
147 // Helper: Send HTTP request
148 int send_http_request(Seobeo_Handle *client, const char *path, const char *custom_request)
149 {
150 char request_buffer[4096];
151 int request_len;
152
153 if (custom_request)
154 {
155 request_len = snprintf(request_buffer, sizeof(request_buffer), "%s", custom_request);
156 }
157 else
158 {
159 request_len = generate_http_get_request(request_buffer, sizeof(request_buffer), path);
160 }
161
162 if (request_len < 0 || request_len >= sizeof(request_buffer))
163 {
164 fprintf(stderr, "Request buffer too small\n");
165 return -1;
166 }
167
168 Seobeo_Handle_Queue(client, (uint8*)request_buffer, (uint32)request_len);
169 return Seobeo_Handle_Flush(client);
170 }
171
172 // Helper: Read HTTP response
173 int read_http_response(Seobeo_Handle *client, char **response_out, size_t *response_len_out)
174 {
175 char *response = malloc(MAX_RESPONSE_SIZE);
176 if (!response)
177 {
178 return -1;
179 }
180
181 size_t total_bytes = 0;
182 int attempts = 0;
183 const int max_attempts = 100;
184
185 while (attempts++ < max_attempts && total_bytes < MAX_RESPONSE_SIZE - 1)
186 {
187 int bytes_read = Seobeo_Handle_Read(client);
188
189 if (bytes_read > 0)
190 {
191 size_t to_copy = client->read_buffer_len;
192 if (total_bytes + to_copy > MAX_RESPONSE_SIZE - 1)
193 {
194 to_copy = MAX_RESPONSE_SIZE - 1 - total_bytes;
195 }
196
197 memcpy(response + total_bytes, client->read_buffer, to_copy);
198 total_bytes += to_copy;
199 Seobeo_Handle_Consume(client, (uint32)to_copy);
200 }
201 else if (bytes_read == -2)
202 {
203 // Connection closed
204 break;
205 }
206 else if (bytes_read == 0)
207 {
208 // Would block
209 usleep(10000);
210 continue;
211 }
212 else
213 {
214 free(response);
215 return -1;
216 }
217 }
218
219 response[total_bytes] = '\0';
220 *response_out = response;
221 *response_len_out = total_bytes;
222
223 return (total_bytes > 0) ? 0 : -1;
224 }
225
226 // Helper: Parse HTTP status code
227 int parse_http_status(const char *response)
228 {
229 if (!response || strlen(response) < 12)
230 {
231 return -1;
232 }
233
234 const char *status_start = strstr(response, "HTTP/1.1 ");
235 if (!status_start)
236 {
237 status_start = strstr(response, "HTTP/1.0 ");
238 }
239
240 if (!status_start)
241 {
242 return -1;
243 }
244
245 int status_code;
246 if (sscanf(status_start + 9, "%d", &status_code) == 1)
247 {
248 return status_code;
249 }
250
251 return -1;
252 }
253
254 // Helper: Check if status is a redirect
255 int is_redirect_status(int status)
256 {
257 return (status >= 300 && status < 400);
258 }
259
260 // Helper: Execute a test case
261 int execute_test_case(TestCase *test, pid_t server_pid)
262 {
263 printf(" Testing: GET %s (expecting %d)\n", test->path, test->expected_status);
264
265 Seobeo_Handle *client = create_test_client();
266 if (!client)
267 {
268 printf(" ✗ Failed to create client connection\n");
269 return -1;
270 }
271
272 if (send_http_request(client, test->path, NULL) < 0)
273 {
274 printf(" ✗ Failed to send request\n");
275 Seobeo_Handle_Destroy(client);
276 return -1;
277 }
278
279 char *response = NULL;
280 size_t response_len = 0;
281 if (read_http_response(client, &response, &response_len) < 0)
282 {
283 printf(" ✗ Failed to read response\n");
284 Seobeo_Handle_Destroy(client);
285 return -1;
286 }
287
288 test->actual_response = response;
289 test->response_len = response_len;
290
291 int actual_status = parse_http_status(response);
292 if (actual_status != test->expected_status)
293 {
294 printf(" ✗ Status mismatch: expected %d, got %d\n",
295 test->expected_status, actual_status);
296 Seobeo_Handle_Destroy(client);
297 return -1;
298 }
299
300 printf(" ✓ Status code: %d\n", actual_status);
301
302 // For redirects, skip content comparison
303 if (is_redirect_status(actual_status))
304 {
305 printf(" ⚠ Redirect status - skipping content comparison\n");
306 Seobeo_Handle_Destroy(client);
307 return 0;
308 }
309
310 // Only verify 200 OK responses against snapshots
311 if (actual_status == 200)
312 {
313 if (!test->expected_content)
314 {
315 printf(" ✗ No expected snapshot found: %s\n", test->expected_file_path);
316 printf(" → Run: bazel run //mrjunejune:create_snapshots\n");
317 Seobeo_Handle_Destroy(client);
318 return -1;
319 }
320
321 if (strcmp(response, test->expected_content) != 0)
322 {
323 printf(" ✗ Response does not match expected snapshot\n");
324 printf(" Expected file: %s\n", test->expected_file_path);
325 Seobeo_Handle_Destroy(client);
326 return -1;
327 }
328
329 printf(" ✓ Response matches snapshot (%zu bytes)\n", response_len);
330 }
331
332 Seobeo_Handle_Destroy(client);
333 return 0;
334 }
335
336 // Helper: Execute custom request test
337 int execute_custom_request_test(const char *name, const char *custom_request,
338 int expected_status, pid_t server_pid)
339 {
340 printf(" Testing: %s (expecting %d)\n", name, expected_status);
341
342 Seobeo_Handle *client = create_test_client();
343 if (!client)
344 {
345 printf(" ✗ Failed to create client connection\n");
346 return -1;
347 }
348
349 if (send_http_request(client, NULL, custom_request) < 0)
350 {
351 printf(" ✗ Failed to send request\n");
352 Seobeo_Handle_Destroy(client);
353 return -1;
354 }
355
356 char *response = NULL;
357 size_t response_len = 0;
358 if (read_http_response(client, &response, &response_len) < 0)
359 {
360 printf(" ✗ Failed to read response\n");
361 Seobeo_Handle_Destroy(client);
362 return -1;
363 }
364
365 int actual_status = parse_http_status(response);
366 if (actual_status != expected_status)
367 {
368 printf(" ✗ Status mismatch: expected %d, got %d\n",
369 expected_status, actual_status);
370 free(response);
371 Seobeo_Handle_Destroy(client);
372 return -1;
373 }
374
375 printf(" ✓ Status code: %d\n", actual_status);
376 printf(" ✓ Response received (%zu bytes)\n", response_len);
377
378 free(response);
379 Seobeo_Handle_Destroy(client);
380 return 0;
381 }
382
383 // Helper: Start test server
384 pid_t start_test_server(const char *server_binary)
385 {
386 pid_t server_pid = fork();
387
388 if (server_pid < 0)
389 {
390 perror("fork");
391 return -1;
392 }
393
394 if (server_pid == 0)
395 {
396 printf("Starting server on port %s...\n", TEST_PORT);
397 execl(server_binary, server_binary, NULL);
398 perror("execl failed");
399 exit(1);
400 }
401
402 printf("Server started (PID: %d)\n", server_pid);
403
404 usleep(100000);
405 int status;
406 pid_t result = waitpid(server_pid, &status, WNOHANG);
407 if (result != 0)
408 {
409 if (WIFEXITED(status))
410 {
411 fprintf(stderr, "Server exited immediately with code: %d\n", WEXITSTATUS(status));
412 }
413 else if (WIFSIGNALED(status))
414 {
415 fprintf(stderr, "Server was killed by signal: %d\n", WTERMSIG(status));
416 }
417 return -1;
418 }
419
420 sleep(2);
421 printf("Server ready\n\n");
422
423 return server_pid;
424 }
425
426 // Helper: Stop test server
427 void stop_test_server(pid_t server_pid)
428 {
429 if (server_pid > 0)
430 {
431 printf("\nStopping server (PID: %d)...\n", server_pid);
432 kill(server_pid, SIGTERM);
433 waitpid(server_pid, NULL, 0);
434 printf("Server stopped\n");
435 }
436 }
437
438 // Helper: Initialize test case with snapshot file
439 void init_test_case(TestCase *test)
440 {
441 char filename[256];
442 path_to_filename(test->path, filename, sizeof(filename));
443
444 test->expected_file_path = malloc(512);
445 snprintf(test->expected_file_path, 512, "%s/%s", SNAPSHOT_DIR, filename);
446
447 // Load expected content from snapshot file
448 load_expected_content(test);
449 }
450
451 // Helper: Cleanup test case
452 void cleanup_test_case(TestCase *test)
453 {
454 if (test->actual_response)
455 {
456 free(test->actual_response);
457 }
458 if (test->expected_file_path)
459 {
460 free(test->expected_file_path);
461 }
462 if (test->expected_content)
463 {
464 free((void*)test->expected_content);
465 }
466 }
467
468 // Main integration test
469 int test_server_client_integration(const char *server_binary)
470 {
471 printf("=== Server-Client Integration Test ===\n");
472 printf("MODE: Verifying Against Snapshots\n\n");
473
474 char cwd[1024];
475 if (getcwd(cwd, sizeof(cwd)) != NULL)
476 {
477 printf("Working directory: %s\n", cwd);
478 }
479
480 if (access(server_binary, X_OK) != 0)
481 {
482 printf("Server binary not found: %s\n", server_binary);
483 perror("access");
484 return -1;
485 }
486 printf("Server binary: %s\n", server_binary);
487 printf("Snapshot directory: %s\n\n", SNAPSHOT_DIR);
488
489 pid_t server_pid = start_test_server(server_binary);
490 if (server_pid < 0)
491 {
492 return -1;
493 }
494
495 int failed_tests = 0;
496 int passed_tests = 0;
497
498 // Define test cases - paths that should succeed (200 OK)
499 TestCase success_tests[] = {
500 {"/", 200, NULL, NULL, NULL, 0},
501 {"/index.html", 200, NULL, NULL, NULL, 0},
502 };
503 int num_success_tests = sizeof(success_tests) / sizeof(success_tests[0]);
504
505 // Define test cases - paths that should fail (404)
506 TestCase failure_tests[] = {
507 {"/nonexistent", 404, NULL, NULL, NULL, 0},
508 {"/does/not/exist", 404, NULL, NULL, NULL, 0},
509 {"/missing.html", 404, NULL, NULL, NULL, 0},
510 };
511 int num_failure_tests = sizeof(failure_tests) / sizeof(failure_tests[0]);
512
513 // Initialize all test cases
514 for (int i = 0; i < num_success_tests; i++)
515 {
516 init_test_case(&success_tests[i]);
517 }
518 for (int i = 0; i < num_failure_tests; i++)
519 {
520 init_test_case(&failure_tests[i]);
521 }
522
523 // Run success tests
524 printf("Running tests for paths that should succeed:\n");
525 for (int i = 0; i < num_success_tests; i++)
526 {
527 if (execute_test_case(&success_tests[i], server_pid) == 0)
528 {
529 passed_tests++;
530 }
531 else
532 {
533 failed_tests++;
534 }
535 }
536
537 printf("\n");
538
539 // Run failure tests
540 printf("Running tests for paths that should fail:\n");
541 for (int i = 0; i < num_failure_tests; i++)
542 {
543 if (execute_test_case(&failure_tests[i], server_pid) == 0)
544 {
545 passed_tests++;
546 }
547 else
548 {
549 failed_tests++;
550 }
551 }
552
553 printf("\n");
554
555 // Test with custom request
556 printf("Running tests with custom requests:\n");
557 char custom_request[4096];
558 snprintf(custom_request, sizeof(custom_request),
559 "GET / HTTP/1.1\r\n"
560 "Host: %s\r\n"
561 "Connection: close\r\n"
562 "X-Custom-Header: TestValue\r\n"
563 "\r\n",
564 TEST_HOST);
565
566 if (execute_custom_request_test("Custom headers GET /", custom_request, 200, server_pid) == 0)
567 {
568 passed_tests++;
569 }
570 else
571 {
572 failed_tests++;
573 }
574
575 // Cleanup test cases
576 for (int i = 0; i < num_success_tests; i++)
577 {
578 cleanup_test_case(&success_tests[i]);
579 }
580 for (int i = 0; i < num_failure_tests; i++)
581 {
582 cleanup_test_case(&failure_tests[i]);
583 }
584
585 stop_test_server(server_pid);
586
587 printf("\n=== Test Summary ===\n");
588 printf("Passed: %d\n", passed_tests);
589 printf("Failed: %d\n", failed_tests);
590
591 return (failed_tests == 0) ? 0 : -1;
592 }
593
594 int main(int argc, char *argv[])
595 {
596 printf("=== Seobeo Integration Tests ===\n\n");
597
598 const char *server_binary = "./mrjunejune_server";
599 if (argc > 1)
600 {
601 server_binary = argv[1];
602 }
603
604 int result = test_server_client_integration(server_binary);
605
606 if (result == 0)
607 {
608 printf("\n✓ All tests passed!\n");
609 }
610 else
611 {
612 printf("\n✗ Some tests failed\n");
613 }
614
615 return result;
616 }