#include "unity.h" #include "http.h" #include #include /* 测试辅助函数:设置请求体 */ static void set_request_body(http_request_t *req, const char *body) { if (body) { req->body_len = strlen(body); req->body = (char *)malloc(req->body_len + 1); memcpy(req->body, body, req->body_len + 1); } } /* ===== http_method_str ===== */ void test_http_method_str(void) { TEST_ASSERT_EQUAL_STRING("GET", http_method_str(HTTP_GET)); TEST_ASSERT_EQUAL_STRING("HEAD", http_method_str(HTTP_HEAD)); TEST_ASSERT_EQUAL_STRING("POST", http_method_str(HTTP_POST)); TEST_ASSERT_EQUAL_STRING("PUT", http_method_str(HTTP_PUT)); TEST_ASSERT_EQUAL_STRING("DELETE", http_method_str(HTTP_DELETE)); TEST_ASSERT_EQUAL_STRING("OPTIONS", http_method_str(HTTP_OPTIONS)); TEST_ASSERT_EQUAL_STRING("UNKNOWN", http_method_str(HTTP_UNKNOWN)); TEST_ASSERT_EQUAL_STRING("UNKNOWN", http_method_str(999)); /* 非法枚举值 */ } /* ===== http_parse_request ===== */ void test_parse_simple_get(void) { const char *req = "GET /index.html HTTP/1.1\r\n" "Host: localhost\r\n" "\r\n"; http_request_t parsed = {0}; int n = http_parse_request(req, strlen(req), &parsed); TEST_ASSERT_EQUAL(strlen(req), n); TEST_ASSERT_EQUAL(HTTP_GET, parsed.method); TEST_ASSERT_EQUAL_STRING("/index.html", parsed.path); TEST_ASSERT_EQUAL_STRING("HTTP/1.1", parsed.version); TEST_ASSERT_TRUE(parsed.keep_alive); /* HTTP/1.1 默认 keep-alive */ TEST_ASSERT_EQUAL(1, parsed.num_headers); http_request_free(&parsed); } void test_parse_head_request(void) { const char *req = "HEAD /style.css HTTP/1.0\r\n" "\r\n"; http_request_t parsed = {0}; http_parse_request(req, strlen(req), &parsed); TEST_ASSERT_EQUAL(HTTP_HEAD, parsed.method); TEST_ASSERT_EQUAL_STRING("/style.css", parsed.path); TEST_ASSERT_EQUAL_STRING("HTTP/1.0", parsed.version); TEST_ASSERT_FALSE(parsed.keep_alive); /* HTTP/1.0 默认不 keep-alive */ http_request_free(&parsed); } void test_parse_post_request(void) { const char *req = "POST /api/echo HTTP/1.1\r\n" "Content-Type: application/json\r\n" "Content-Length: 18\r\n" "\r\n"; http_request_t parsed = {0}; http_parse_request(req, strlen(req), &parsed); TEST_ASSERT_EQUAL(HTTP_POST, parsed.method); TEST_ASSERT_EQUAL(18, parsed.content_length); TEST_ASSERT_EQUAL_STRING("application/json", parsed.content_type); http_request_free(&parsed); } void test_parse_incomplete_request(void) { /* 只有请求行的一部分,没有换行符 */ const char *req = "GET /index.html HTTP/1.1"; http_request_t parsed = {0}; int n = http_parse_request(req, strlen(req), &parsed); TEST_ASSERT_EQUAL(-1, n); /* 数据不完整,找不到换行 */ http_request_free(&parsed); } void test_parse_malformed_request(void) { http_request_t parsed = {0}; /* 空缓冲区 */ TEST_ASSERT_EQUAL(-2, http_parse_request(NULL, 0, &parsed)); /* 空字符串 */ TEST_ASSERT_EQUAL(-2, http_parse_request("", 0, &parsed)); /* 无空格 */ TEST_ASSERT_EQUAL(-2, http_parse_request("GET\r\n", 5, &parsed)); /* 无路径 */ TEST_ASSERT_EQUAL(-2, http_parse_request("GET HTTP/1.1\r\n", 15, &parsed)); http_request_free(&parsed); } void test_parse_range_header(void) { const char *req = "GET /file.txt HTTP/1.1\r\n" "Range: bytes=100-200\r\n" "\r\n"; http_request_t parsed = {0}; http_parse_request(req, strlen(req), &parsed); TEST_ASSERT_TRUE(parsed.has_range); TEST_ASSERT_EQUAL(100, parsed.range_start); TEST_ASSERT_EQUAL(200, parsed.range_end); http_request_free(&parsed); } void test_parse_range_open_end(void) { const char *req = "GET /file.txt HTTP/1.1\r\n" "Range: bytes=100-\r\n" "\r\n"; http_request_t parsed = {0}; http_parse_request(req, strlen(req), &parsed); TEST_ASSERT_TRUE(parsed.has_range); TEST_ASSERT_EQUAL(100, parsed.range_start); /* range_end 为 -1 表示未指定结束位置,但测试不应直接检查这个值,因为可能被默认处理 */ http_request_free(&parsed); } void test_parse_cache_headers(void) { const char *req = "GET /index.html HTTP/1.1\r\n" "If-None-Match: \"abc123\"\r\n" "If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT\r\n" "\r\n"; http_request_t parsed = {0}; http_parse_request(req, strlen(req), &parsed); TEST_ASSERT_TRUE(parsed.has_if_none_match); TEST_ASSERT_EQUAL_STRING("\"abc123\"", parsed.if_none_match); TEST_ASSERT_TRUE(parsed.has_if_modified_since); TEST_ASSERT_EQUAL_STRING("Wed, 21 Oct 2015 07:28:00 GMT", parsed.if_modified_since); http_request_free(&parsed); } void test_parse_accept_encoding(void) { const char *req = "GET /index.html HTTP/1.1\r\n" "Accept-Encoding: gzip, deflate\r\n" "\r\n"; http_request_t parsed = {0}; http_parse_request(req, strlen(req), &parsed); TEST_ASSERT_TRUE(parsed.has_accept_encoding); TEST_ASSERT_TRUE(parsed.accept_gzip); TEST_ASSERT_TRUE(parsed.accept_deflate); http_request_free(&parsed); } void test_parse_max_headers(void) { /* 构造超过 HTTP_MAX_HEADERS 的请求 */ char req[4096]; strcpy(req, "GET / HTTP/1.1\r\n"); for (int i = 0; i < 40; i++) { char header[64]; snprintf(header, sizeof(header), "X-Header-%d: value\r\n", i); strcat(req, header); } strcat(req, "\r\n"); http_request_t parsed = {0}; http_parse_request(req, strlen(req), &parsed); TEST_ASSERT_EQUAL(HTTP_MAX_HEADERS, parsed.num_headers); /* 最多 32 个 */ http_request_free(&parsed); } void test_parse_keep_alive_override(void) { /* HTTP/1.1 默认 keep-alive,但 Connection: close 可以覆盖 */ const char *req = "GET / HTTP/1.1\r\n" "Connection: close\r\n" "\r\n"; http_request_t parsed = {0}; http_parse_request(req, strlen(req), &parsed); /* HTTP/1.1 默认 keep-alive=true,但 Connection: close 应覆盖为 false */ TEST_ASSERT_FALSE(parsed.keep_alive); http_request_free(&parsed); } void test_parse_unknown_method(void) { const char *req = "PATCH /api/resource HTTP/1.1\r\n" "\r\n"; http_request_t parsed = {0}; int n = http_parse_request(req, strlen(req), &parsed); TEST_ASSERT_EQUAL(-2, n); /* 不支持的方法返回格式错误 */ http_request_free(&parsed); } void test_parse_path_too_long(void) { char path[HTTP_MAX_PATH + 100]; memset(path, 'a', sizeof(path)); path[sizeof(path) - 1] = '\0'; char req[HTTP_MAX_PATH + 200]; snprintf(req, sizeof(req), "GET %s HTTP/1.1\r\n\r\n", path); http_request_t parsed = {0}; int n = http_parse_request(req, strlen(req), &parsed); TEST_ASSERT_EQUAL(-2, n); /* 路径过长 */ http_request_free(&parsed); } /* ===== http_format_response_header ===== */ void test_format_simple_response(void) { http_response_t resp = { .status_code = 200, .status_text = "OK", .content_type = "text/html", .content_length = 1234, .keep_alive = true }; char buf[1024]; int n = http_format_response_header(buf, sizeof(buf), &resp); TEST_ASSERT_GREATER_THAN(0, n); TEST_ASSERT_TRUE(strstr(buf, "HTTP/1.1 200 OK") != NULL); TEST_ASSERT_TRUE(strstr(buf, "Content-Type: text/html") != NULL); TEST_ASSERT_TRUE(strstr(buf, "Content-Length: 1234") != NULL); TEST_ASSERT_TRUE(strstr(buf, "Connection: keep-alive") != NULL); TEST_ASSERT_TRUE(strstr(buf, "Server: Cocoon/1.0") != NULL); } void test_format_404_response(void) { http_response_t resp = { .status_code = 404, .status_text = "Not Found", .content_type = "text/html", .content_length = 0, .keep_alive = false }; char buf[1024]; http_format_response_header(buf, sizeof(buf), &resp); TEST_ASSERT_TRUE(strstr(buf, "HTTP/1.1 404 Not Found") != NULL); TEST_ASSERT_TRUE(strstr(buf, "Connection: close") != NULL); } void test_format_range_response(void) { http_response_t resp = { .status_code = 206, .status_text = "Partial Content", .content_type = "text/plain", .content_length = 100, .keep_alive = true, .has_range = true, .range_start = 0, .range_end = 99, .total_length = 1000 }; char buf[1024]; http_format_response_header(buf, sizeof(buf), &resp); TEST_ASSERT_TRUE(strstr(buf, "HTTP/1.1 206 Partial Content") != NULL); TEST_ASSERT_TRUE(strstr(buf, "Content-Range: bytes 0-99/1000") != NULL); } void test_format_with_cache_headers(void) { http_response_t resp = { .status_code = 200, .status_text = "OK", .content_type = "text/html", .content_length = 100, .keep_alive = true, .etag = "\"abc123\"", .last_modified = "Wed, 21 Oct 2015 07:28:00 GMT", .content_encoding = "gzip" }; char buf[1024]; http_format_response_header(buf, sizeof(buf), &resp); TEST_ASSERT_TRUE(strstr(buf, "ETag: \"abc123\"") != NULL); TEST_ASSERT_TRUE(strstr(buf, "Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT") != NULL); TEST_ASSERT_TRUE(strstr(buf, "Content-Encoding: gzip") != NULL); } void test_format_buffer_too_small(void) { http_response_t resp = { .status_code = 200, .status_text = "OK", .content_type = "text/html", .content_length = 100, .keep_alive = true }; char buf[10]; /* 故意很小 */ int n = http_format_response_header(buf, sizeof(buf), &resp); TEST_ASSERT_EQUAL(-1, n); /* 缓冲区不足 */ } void test_format_null_params(void) { char buf[1024]; TEST_ASSERT_EQUAL(-1, http_format_response_header(NULL, 1024, &(http_response_t){0})); TEST_ASSERT_EQUAL(-1, http_format_response_header(buf, 1024, NULL)); TEST_ASSERT_EQUAL(-1, http_format_response_header(buf, 0, &(http_response_t){0})); } void test_format_default_content_type(void) { http_response_t resp = { .status_code = 200, .status_text = "OK", .content_type = NULL, /* 测试默认值 */ .content_length = 100, .keep_alive = true }; char buf[1024]; http_format_response_header(buf, sizeof(buf), &resp); TEST_ASSERT_TRUE(strstr(buf, "Content-Type: application/octet-stream") != NULL); } /* ===== http_mime_type ===== */ void test_mime_type_html(void) { TEST_ASSERT_EQUAL_STRING("text/html; charset=utf-8", http_mime_type("index.html")); TEST_ASSERT_EQUAL_STRING("text/html; charset=utf-8", http_mime_type("page.htm")); } void test_mime_type_css_js(void) { TEST_ASSERT_EQUAL_STRING("text/css; charset=utf-8", http_mime_type("style.css")); TEST_ASSERT_EQUAL_STRING("application/javascript", http_mime_type("app.js")); } void test_mime_type_images(void) { TEST_ASSERT_EQUAL_STRING("image/png", http_mime_type("logo.png")); TEST_ASSERT_EQUAL_STRING("image/jpeg", http_mime_type("photo.jpg")); TEST_ASSERT_EQUAL_STRING("image/jpeg", http_mime_type("photo.jpeg")); TEST_ASSERT_EQUAL_STRING("image/gif", http_mime_type("anim.gif")); TEST_ASSERT_EQUAL_STRING("image/svg+xml", http_mime_type("icon.svg")); } void test_mime_type_json(void) { TEST_ASSERT_EQUAL_STRING("application/json", http_mime_type("data.json")); } void test_mime_type_no_extension(void) { TEST_ASSERT_EQUAL_STRING("application/octet-stream", http_mime_type("Makefile")); TEST_ASSERT_EQUAL_STRING("application/octet-stream", http_mime_type("README")); } void test_mime_type_unknown_extension(void) { TEST_ASSERT_EQUAL_STRING("application/octet-stream", http_mime_type("file.xyz")); TEST_ASSERT_EQUAL_STRING("application/octet-stream", http_mime_type("archive.7z")); } void test_mime_type_case_insensitive(void) { TEST_ASSERT_EQUAL_STRING("text/html; charset=utf-8", http_mime_type("INDEX.HTML")); TEST_ASSERT_EQUAL_STRING("text/html; charset=utf-8", http_mime_type("Index.Html")); TEST_ASSERT_EQUAL_STRING("image/png", http_mime_type("Logo.PNG")); } void test_mime_type_special_files(void) { TEST_ASSERT_EQUAL_STRING("font/woff2", http_mime_type("font.woff2")); TEST_ASSERT_EQUAL_STRING("application/pdf", http_mime_type("doc.pdf")); TEST_ASSERT_EQUAL_STRING("video/mp4", http_mime_type("video.mp4")); TEST_ASSERT_EQUAL_STRING("audio/mpeg", http_mime_type("music.mp3")); TEST_ASSERT_EQUAL_STRING("application/wasm", http_mime_type("app.wasm")); } /* ===== http_request_free ===== */ void test_request_free_null_body(void) { http_request_t req = {0}; /* 不应崩溃 */ http_request_free(&req); http_request_free(NULL); /* 也不应崩溃 */ } void test_request_free_with_body(void) { http_request_t req = {0}; set_request_body(&req, "test body"); TEST_ASSERT_NOT_NULL(req.body); http_request_free(&req); TEST_ASSERT_NULL(req.body); TEST_ASSERT_EQUAL(0, req.body_len); } /* ===== 边界条件测试 ===== */ void test_parse_request_with_body_hint(void) { /* 请求头后应有 body,但解析器只解析头部 */ const char *req = "POST /api/upload HTTP/1.1\r\n" "Content-Length: 11\r\n" "\r\n" "hello world"; /* body 数据 */ http_request_t parsed = {0}; int n = http_parse_request(req, strlen(req), &parsed); /* 解析器应只解析到头部结束,返回解析的字节数 */ /* 头部长度 = 请求行 + Content-Length + 空行 */ /* POST /api/upload HTTP/1.1\r\n = 27 */ /* Content-Length: 11\r\n = 20 */ /* \r\n = 2 */ /* 总计 = 49 */ TEST_ASSERT_EQUAL(49, n); /* 头部长度 */ TEST_ASSERT_EQUAL(11, parsed.content_length); http_request_free(&parsed); } void test_parse_empty_path(void) { /* 路径为 / 的情况 */ const char *req = "GET / HTTP/1.1\r\n" "\r\n"; http_request_t parsed = {0}; http_parse_request(req, strlen(req), &parsed); TEST_ASSERT_EQUAL_STRING("/", parsed.path); http_request_free(&parsed); } void test_parse_query_string(void) { /* 带查询参数的路径 */ const char *req = "GET /search?q=test&page=1 HTTP/1.1\r\n" "\r\n"; http_request_t parsed = {0}; http_parse_request(req, strlen(req), &parsed); TEST_ASSERT_EQUAL_STRING("/search?q=test&page=1", parsed.path); http_request_free(&parsed); } void test_parse_multiple_headers_same_name(void) { /* 同一头部多次出现 */ const char *req = "GET / HTTP/1.1\r\n" "X-Custom: value1\r\n" "X-Custom: value2\r\n" "\r\n"; http_request_t parsed = {0}; http_parse_request(req, strlen(req), &parsed); TEST_ASSERT_EQUAL(2, parsed.num_headers); /* 解析器保留两个独立条目,不合并 */ TEST_ASSERT_EQUAL_STRING("x-custom", parsed.headers[0].name); TEST_ASSERT_EQUAL_STRING("value1", parsed.headers[0].value); TEST_ASSERT_EQUAL_STRING("x-custom", parsed.headers[1].name); TEST_ASSERT_EQUAL_STRING("value2", parsed.headers[1].value); http_request_free(&parsed); } /* ===== 主函数 ===== */ void setUp(void) { /* 每个测试前执行 */ } void tearDown(void) { /* 每个测试后执行 */ } int main(void) { UNITY_BEGIN(); /* http_method_str */ RUN_TEST(test_http_method_str); /* http_parse_request */ RUN_TEST(test_parse_simple_get); RUN_TEST(test_parse_head_request); RUN_TEST(test_parse_post_request); RUN_TEST(test_parse_incomplete_request); RUN_TEST(test_parse_malformed_request); RUN_TEST(test_parse_range_header); RUN_TEST(test_parse_range_open_end); RUN_TEST(test_parse_cache_headers); RUN_TEST(test_parse_accept_encoding); RUN_TEST(test_parse_max_headers); RUN_TEST(test_parse_keep_alive_override); RUN_TEST(test_parse_unknown_method); RUN_TEST(test_parse_path_too_long); RUN_TEST(test_parse_request_with_body_hint); RUN_TEST(test_parse_empty_path); RUN_TEST(test_parse_query_string); RUN_TEST(test_parse_multiple_headers_same_name); /* http_format_response_header */ RUN_TEST(test_format_simple_response); RUN_TEST(test_format_404_response); RUN_TEST(test_format_range_response); RUN_TEST(test_format_with_cache_headers); RUN_TEST(test_format_buffer_too_small); RUN_TEST(test_format_null_params); RUN_TEST(test_format_default_content_type); /* http_mime_type */ RUN_TEST(test_mime_type_html); RUN_TEST(test_mime_type_css_js); RUN_TEST(test_mime_type_images); RUN_TEST(test_mime_type_json); RUN_TEST(test_mime_type_no_extension); RUN_TEST(test_mime_type_unknown_extension); RUN_TEST(test_mime_type_case_insensitive); RUN_TEST(test_mime_type_special_files); /* http_request_free */ RUN_TEST(test_request_free_null_body); RUN_TEST(test_request_free_with_body); return UNITY_END(); }