Add unit tests for OpenAI plugin and establish coding standards

- Introduced comprehensive unit tests for the OpenAI plugin, covering SSE parsing, sentinel matching, delta extraction, request building, and more.
- Created a new markdown file detailing coding and naming conventions for the dstalk project, including guidelines for comments, naming rules, code organization, and memory management practices.
This commit is contained in:
2026-05-31 00:51:59 +08:00
parent f2da0f2ed4
commit f6cb51b40a
21 changed files with 343 additions and 131 deletions

View File

@@ -152,31 +152,31 @@ target_link_libraries(dstalk-anthropic-plugin-test
add_test(NAME dstalk-anthropic-plugin-test COMMAND dstalk-anthropic-plugin-test)
# ============================================================
# dstalk-deepseek-plugin-test — DeepSeek AI 插件单元测试
# dstalk-openai-plugin-test — OpenAI 兼容 AI 插件单元测试
# W21.6 (qa-wang): 通过 #include source 访问 static 函数
# ============================================================
add_executable(dstalk-deepseek-plugin-test
deepseek_plugin_test.cpp
add_executable(dstalk-openai-plugin-test
openai_plugin_test.cpp
)
target_include_directories(dstalk-deepseek-plugin-test
target_include_directories(dstalk-openai-plugin-test
PRIVATE ${CMAKE_SOURCE_DIR}/dstalk-core/include
)
target_compile_definitions(dstalk-deepseek-plugin-test
target_compile_definitions(dstalk-openai-plugin-test
PRIVATE
BOOST_JSON_HEADER_ONLY
BOOST_ALL_NO_LIB
)
target_link_libraries(dstalk-deepseek-plugin-test
target_link_libraries(dstalk-openai-plugin-test
PRIVATE
dstalk
boost::boost
)
add_test(NAME dstalk-deepseek-plugin-test COMMAND dstalk-deepseek-plugin-test)
add_test(NAME dstalk-openai-plugin-test COMMAND dstalk-openai-plugin-test)
# ============================================================
# dstalk-network-plugin-test — Network 插件单元测试

View File

@@ -40,10 +40,10 @@ int main()
{
std::ofstream config(config_path);
config << "[api]\n"
<< "provider = \"deepseek\"\n"
<< "base_url = \"https://api.deepseek.com/v1\"\n"
<< "provider = \"openai\"\n"
<< "base_url = \"https://api.openai.com/v1\"\n"
<< "api_key = \"test-key\"\n"
<< "model = \"deepseek-v4-pro\"\n";
<< "model = \"gpt-4o\"\n";
}
if (dstalk_init(config_path.string().c_str()) != 0) {

View File

@@ -1,6 +1,6 @@
/*
* @file deepseek_plugin_test.cpp
* @brief DeepSeek AI plugin unit tests: SSE parsing (parse_sse_line edge cases),
* @file openai_plugin_test.cpp
* @brief OpenAI-compatible AI plugin unit tests: SSE parsing (parse_sse_line edge cases),
* [DONE] sentinel matching, tool_calls delta extraction, request building,
* append_history, extract_host_port, secure_zero, and null-safety.
* DeepSeek AI SSE parse_sse_line [DONE]
@@ -9,7 +9,7 @@
*/
#define BOOST_JSON_HEADER_ONLY
#define BOOST_ALL_NO_LIB
#include "../plugins/deepseek/src/deepseek_plugin.cpp"
#include "../plugins/openai/src/openai_plugin.cpp"
#include <cstring>
#include <iostream>
@@ -30,10 +30,10 @@ static int g_failures = 0;
// Test helper: populate g_cfg with valid deepseek defaults before build_* tests
// 测试辅助函数:为 build_* 测试填充 g_cfg 的有效 deepseek 默认值
static void setup_config() {
g_cfg.provider = "deepseek";
g_cfg.base_url = "https://api.deepseek.com/v1";
g_cfg.provider = "openai";
g_cfg.base_url = "https://api.openai.com/v1";
g_cfg.api_key = "sk-ds-test-key-67890";
g_cfg.model = "deepseek-v4-pro";
g_cfg.model = "gpt-4o";
g_cfg.max_tokens = 4096;
g_cfg.temperature = 0.7;
}
@@ -243,7 +243,7 @@ int main()
"data: {\"id\":\"chatcmpl-xxx\","
"\"object\":\"chat.completion.chunk\","
"\"created\":1712345678,"
"\"model\":\"deepseek-v4-pro\","
"\"model\":\"gpt-4o\","
"\"choices\":[{\"index\":0,"
"\"delta\":{\"content\":\" World\"},"
"\"finish_reason\":null}]}";
@@ -368,7 +368,7 @@ int main()
"T5.4: contains user input");
CHECK(json.find("\"stream\":false") != std::string::npos,
"T5.5: stream=false present");
CHECK(json.find("\"model\":\"deepseek-v4-pro\"")
CHECK(json.find("\"model\":\"gpt-4o\"")
!= std::string::npos,
"T5.6: model field present");
CHECK(json.find("\"max_tokens\":4096") != std::string::npos,
@@ -521,11 +521,11 @@ int main()
{
std::string scheme, host, port, target;
bool ret = extract_host_port(
"https://api.deepseek.com/v1/chat/completions",
"https://api.openai.com/v1/chat/completions",
scheme, host, port, target);
CHECK(ret, "T8.1: valid HTTPS URL returns true");
CHECK(scheme == "https", "T8.2: scheme is 'https'");
CHECK(host == "api.deepseek.com", "T8.3: host extracted");
CHECK(host == "api.openai.com", "T8.3: host extracted");
CHECK(port == "443", "T8.4: default HTTPS port 443");
CHECK(target == "/v1/chat/completions", "T8.5: target path extracted");
}
@@ -552,7 +552,7 @@ int main()
{
std::string scheme, host, port, target;
bool ret = extract_host_port(
"https://api.deepseek.com",
"https://api.openai.com",
scheme, host, port, target);
CHECK(ret, "T8.11: URL without path returns true");
CHECK(target == "/", "T8.12: target defaults to '/'");
@@ -683,10 +683,10 @@ int main()
{
int ret = my_configure(
"deepseek", "https://api.deepseek.com/v1",
"sk-key", "deepseek-v4", 2048, 0.5);
"openai", "https://api.openai.com/v1",
"sk-key", "gpt-4o", 2048, 0.5);
CHECK(ret == 0, "T12.1: my_configure returns 0 with null host");
CHECK(g_cfg.provider == "deepseek", "T12.2: provider stored");
CHECK(g_cfg.provider == "openai", "T12.2: provider stored");
CHECK(g_cfg.max_tokens == 2048, "T12.3: max_tokens stored");
CHECK(g_cfg.temperature == 0.5, "T12.4: temperature stored");
}
@@ -701,7 +701,7 @@ int main()
// ================================================================
std::cout << "\n";
if (g_failures == 0) {
std::cout << "=== All deepseek plugin tests passed ===\n";
std::cout << "=== All openai plugin tests passed ===\n";
return 0;
} else {
std::cerr << "=== " << g_failures << " test(s) FAILED ===\n";

View File

@@ -52,10 +52,10 @@ int main()
{
std::ofstream config(config_path);
config << "[api]\n"
<< "provider = \"deepseek\"\n"
<< "base_url = \"https://api.deepseek.com/v1\"\n"
<< "provider = \"openai\"\n"
<< "base_url = \"https://api.openai.com/v1\"\n"
<< "api_key = \"test-key\"\n"
<< "model = \"deepseek-v4-pro\"\n";
<< "model = \"gpt-4o\"\n";
}
// 初始化主机(会自动扫描 plugins/ 加载插件)/ Init host (auto-scans plugins/ to load plugins)
@@ -186,7 +186,7 @@ int main()
// 测试服务查询: ai可能因为没有真实 API key 而失败,但服务应存在)
// Test service query: ai (may fail without real API key, but service should exist)
const char* ai_provider = dstalk_config_get("ai.provider");
if (!ai_provider) ai_provider = "ai.deepseek";
if (!ai_provider) ai_provider = "ai.openai";
auto* ai = static_cast<const dstalk_ai_service_t*>(
dstalk_service_query(ai_provider, 1));
if (ai) {
@@ -598,12 +598,12 @@ int main()
std::cout << "[OK] R3: http error path, no response body (connection refused)\n";
}
} else {
// 回退:测 AI 服务 (ai.deepseek) 错误路径
// Fallback: test AI service (ai.deepseek) error path
// 回退:测 AI 服务 (ai.openai) 错误路径
// Fallback: test AI service (ai.openai) error path
auto* ai_svc = static_cast<const dstalk_ai_service_t*>(
dstalk_service_query("ai.deepseek", 1));
dstalk_service_query("ai.openai", 1));
if (ai_svc) {
std::cout << "[OK] R3: ai.deepseek service found (http fallback)\n";
std::cout << "[OK] R3: ai.openai service found (http fallback)\n";
dstalk_message_t msg = {"user", "hi", nullptr, nullptr};
dstalk_chat_result_t r = ai_svc->chat(&msg, 1, "", nullptr);
// api_key="test-key" 为无效 key应返回 error result 而非崩溃
@@ -748,10 +748,10 @@ int main()
{
std::ofstream c(config_path);
c << "[api]\n"
<< "provider = \"deepseek\"\n"
<< "base_url = \"https://api.deepseek.com/v1\"\n"
<< "provider = \"openai\"\n"
<< "base_url = \"https://api.openai.com/v1\"\n"
<< "api_key = \"test-key\"\n"
<< "model = \"deepseek-v4-pro\"\n";
<< "model = \"gpt-4o\"\n";
}
std::cout << "[R4] cycle " << (i + 1) << "/" << cycles << "\n";