cocoon/tests/unit/test_acme_config.c
xfy911 2c5538bea1
Some checks failed
CI / build (push) Failing after 17s
fix: 修复带宽限速合并后遗留的编译与测试问题 + 新增 Docker 多阶段构建
- Makefile: 4 个单元测试规则缺失 throttle.c(test_websocket/test_grpc/test_http3/test_acme)
- test_acme_config.c: config_merge 调用缺少 has_throttle_conn_rate / has_throttle_global_rate
- test_config.c: 5 处 config_merge 调用同步补齐 throttle 参数
- 新增 Dockerfile: debian bookworm-slim 多阶段构建,最终镜像约 50MB
- 新增 .dockerignore: 排除构建产物和开发文件
- 编译验证:零警告(仅外部 coco 库 GNU-stack linker 提示)
- 单元测试:480 个全部通过 ✓(1 个 Ignored 为预期行为)
2026-06-16 18:26:06 +08:00

274 lines
7.9 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* test_acme_config.c - ACME 配置集成单元测试
*
* 测试 ACME 配置字段解析、校验、合并和证书过期检查。
*
* @author xfy
*/
#include "unity.h"
#include "acme.h"
#include "config.h"
#include "cocoon.h"
#include "log.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void setUp(void) {
log_set_level(LOG_LEVEL_ERROR);
}
void tearDown(void) {
}
/**
* test_acme_config_parse - 测试 ACME 配置 JSON 解析
*/
static void test_acme_config_parse(void) {
cocoon_config_t config = {0};
config.port = 8080;
config.log_level = LOG_LEVEL_INFO;
config.gzip_enabled = true;
config.brotli_enabled = true;
const char *json = "{"
"\"root_dir\":\"/var/www\","
"\"acme\":{"
" \"enabled\":true,"
" \"directory_url\":\"https://acme-staging-v02.api.letsencrypt.org/directory\","
" \"email\":\"admin@example.com\","
" \"domains\":[\"example.com\",\"www.example.com\"],"
" \"cert_path\":\"/etc/cocoon/cert.pem\","
" \"key_path\":\"/etc/cocoon/key.pem\","
" \"renew_days\":14"
"}"
"}";
FILE *fp = fopen("/tmp/test_acme_config.json", "w");
TEST_ASSERT_NOT_NULL(fp);
fwrite(json, 1, strlen(json), fp);
fclose(fp);
bool ok = config_load_from_file("/tmp/test_acme_config.json", &config);
TEST_ASSERT_TRUE(ok);
TEST_ASSERT_TRUE(config.acme_enabled == true);
TEST_ASSERT_EQUAL_STRING("https://acme-staging-v02.api.letsencrypt.org/directory", config.acme_directory_url);
TEST_ASSERT_EQUAL_STRING("admin@example.com", config.acme_email);
TEST_ASSERT_EQUAL_size_t(2, config.acme_num_domains);
TEST_ASSERT_EQUAL_STRING("example.com", config.acme_domains[0]);
TEST_ASSERT_EQUAL_STRING("www.example.com", config.acme_domains[1]);
TEST_ASSERT_EQUAL_STRING("/etc/cocoon/cert.pem", config.acme_cert_path);
TEST_ASSERT_EQUAL_STRING("/etc/cocoon/key.pem", config.acme_key_path);
TEST_ASSERT_EQUAL_UINT(14, config.acme_renew_days);
/* 校验 */
char err_buf[256] = {0};
ok = config_validate(&config, err_buf, sizeof(err_buf));
TEST_ASSERT_TRUE(ok);
free((void*)config.root_dir);
}
/**
* test_acme_config_validation - 测试 ACME 配置校验
*/
static void test_acme_config_validation(void) {
cocoon_config_t config = {0};
config.port = 8080;
config.log_level = LOG_LEVEL_INFO;
config.gzip_enabled = true;
config.brotli_enabled = true;
config.root_dir = strdup("/var/www");
config.acme_enabled = true;
char err_buf[256] = {0};
bool ok;
/* 缺少邮箱 */
ok = config_validate(&config, err_buf, sizeof(err_buf));
TEST_ASSERT_FALSE(ok);
TEST_ASSERT_NOT_NULL(strstr(err_buf, "邮箱"));
/* 缺少域名 */
strcpy(config.acme_email, "admin@example.com");
ok = config_validate(&config, err_buf, sizeof(err_buf));
TEST_ASSERT_FALSE(ok);
TEST_ASSERT_NOT_NULL(strstr(err_buf, "域名"));
/* 缺少证书路径 */
strcpy(config.acme_domains[0], "example.com");
config.acme_num_domains = 1;
ok = config_validate(&config, err_buf, sizeof(err_buf));
TEST_ASSERT_FALSE(ok);
TEST_ASSERT_NOT_NULL(strstr(err_buf, "证书"));
/* 缺少私钥路径 */
strcpy(config.acme_cert_path, "/etc/cocoon/cert.pem");
ok = config_validate(&config, err_buf, sizeof(err_buf));
TEST_ASSERT_FALSE(ok);
TEST_ASSERT_NOT_NULL(strstr(err_buf, "私钥"));
/* renew_days 越界 */
strcpy(config.acme_key_path, "/etc/cocoon/key.pem");
config.acme_renew_days = 0;
ok = config_validate(&config, err_buf, sizeof(err_buf));
TEST_ASSERT_FALSE(ok);
TEST_ASSERT_NOT_NULL(strstr(err_buf, "renew_days"));
config.acme_renew_days = 91;
ok = config_validate(&config, err_buf, sizeof(err_buf));
TEST_ASSERT_FALSE(ok);
/* 完整配置通过校验 */
config.acme_renew_days = 30;
ok = config_validate(&config, err_buf, sizeof(err_buf));
TEST_ASSERT_TRUE(ok);
free((void*)config.root_dir);
}
/**
* test_acme_config_defaults - 测试 ACME 配置默认值
*/
static void test_acme_config_defaults(void) {
cocoon_config_t config = {0};
config.port = 8080;
config.log_level = LOG_LEVEL_INFO;
config.gzip_enabled = true;
config.brotli_enabled = true;
config.root_dir = strdup("/var/www");
char err_buf[256] = {0};
bool ok = config_validate(&config, err_buf, sizeof(err_buf));
/* 未启用 ACME不应校验 ACME 字段 */
TEST_ASSERT_TRUE(ok);
free((void*)config.root_dir);
}
/**
* test_acme_cert_expiry - 测试证书过期检查
*/
static void test_acme_cert_expiry(void) {
/* 不存在的证书 */
int days = acme_cert_days_until_expiry("/nonexistent/cert.pem");
TEST_ASSERT_TRUE(days < 0);
/* 生成一个自签名证书用于测试 */
const char *cert_path = "/tmp/test_acme_cert.pem";
const char *key_path = "/tmp/test_acme_key.pem";
FILE *fp = fopen(cert_path, "w");
if (!fp) {
/* 无法创建测试证书,跳过 */
return;
}
fclose(fp);
/* 空文件应该返回 -1 */
days = acme_cert_days_until_expiry(cert_path);
TEST_ASSERT_TRUE(days < 0);
/* 使用 OpenSSL 生成一个有效期 90 天的自签名证书 */
char cmd[512];
snprintf(cmd, sizeof(cmd),
"openssl req -x509 -newkey rsa:2048 -keyout %s -out %s "
"-days 90 -nodes -subj '/CN=test.example.com' 2>/dev/null",
key_path, cert_path);
int ret = system(cmd);
if (ret != 0) {
/* openssl 不可用,跳过 */
remove(cert_path);
return;
}
days = acme_cert_days_until_expiry(cert_path);
/* 应该接近 90 天 */
TEST_ASSERT_TRUE(days >= 85 && days <= 90);
remove(cert_path);
remove(key_path);
}
/**
* test_acme_save_certificate - 测试证书保存
*/
static void test_acme_save_certificate(void) {
const char *cert_pem = "-----BEGIN CERTIFICATE-----\n"
"MIIBkTCB+wIJAKHBfpE";
const char *key_pem = "-----BEGIN PRIVATE KEY-----\n"
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg";
const char *cert_path = "/tmp/test_save_cert.pem";
const char *key_path = "/tmp/test_save_key.pem";
int ret = acme_save_certificate(cert_pem, key_pem, cert_path, key_path);
TEST_ASSERT_EQUAL_INT(0, ret);
/* 验证文件存在且内容正确 */
FILE *fp = fopen(cert_path, "r");
TEST_ASSERT_NOT_NULL(fp);
char buf[256];
size_t n = fread(buf, 1, sizeof(buf), fp);
fclose(fp);
TEST_ASSERT_TRUE(n > 0);
TEST_ASSERT_EQUAL_INT(0, strncmp(buf, cert_pem, strlen(cert_pem)));
fp = fopen(key_path, "r");
TEST_ASSERT_NOT_NULL(fp);
n = fread(buf, 1, sizeof(buf), fp);
fclose(fp);
TEST_ASSERT_TRUE(n > 0);
TEST_ASSERT_EQUAL_INT(0, strncmp(buf, key_pem, strlen(key_pem)));
remove(cert_path);
remove(key_path);
}
/**
* test_acme_config_merge - 测试 ACME 配置合并
*/
static void test_acme_config_merge(void) {
cocoon_config_t base = {0};
base.port = 8080;
base.log_level = LOG_LEVEL_INFO;
base.gzip_enabled = true;
base.brotli_enabled = true;
base.root_dir = strdup("/var/www");
cocoon_config_t cmdline = {0};
cmdline.acme_enabled = true;
config_merge(&base, &cmdline,
false, false, false, false, false, false,
false, false, false, false, false,
false, false, false, false,
false,
false, false,
false,
false, false, false, false,
true); /* has_acme_enabled */
TEST_ASSERT_TRUE(base.acme_enabled == true);
free((void*)base.root_dir);
}
/**
* main - 测试入口
*/
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_acme_config_parse);
RUN_TEST(test_acme_config_validation);
RUN_TEST(test_acme_config_defaults);
RUN_TEST(test_acme_cert_expiry);
RUN_TEST(test_acme_save_certificate);
RUN_TEST(test_acme_config_merge);
return UNITY_END();
}