Add metadata validation script and module documentation
- Introduced a new Python script `check_agents_metadata.py` for validating agent metadata, including YAML parsing, rating ranges, and cross-references. - Added usage instructions and exit codes for the script. - Created a new markdown file `模块目录和功能说明.md` to outline the directory structure and functionality of the modules. - Added a text file `说明此文件不可AI修改.txt` to specify that certain files should not be modified by AI, including important information about the `dstalk` framework and its modules.
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
// ============================================================================
|
||||
// anthropic_plugin_test.cpp — Anthropic AI 插件单元测试
|
||||
// W21.6 (qa-wang): 覆盖 SSE 解析 / JSON 请求构建 / URL 解析 / 安全擦除
|
||||
// 通过 #include plugin source 访问 file-scope static 函数
|
||||
// ============================================================================
|
||||
/*
|
||||
* @file anthropic_plugin_test.cpp
|
||||
* @brief Anthropic AI plugin unit tests: SSE parsing (parse_sse_data edge cases),
|
||||
* request building (build_request_json), header construction, URL parsing
|
||||
* (extract_host_port), secure_zero, and null-safety for free_result/configure.
|
||||
* Anthropic AI 插件单元测试:SSE 解析(parse_sse_data 边界情况)、请求构建(build_request_json)、
|
||||
* 头部构造、URL 解析(extract_host_port)、secure_zero、free_result/configure 空指针安全。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
#define BOOST_JSON_HEADER_ONLY
|
||||
#define BOOST_ALL_NO_LIB
|
||||
#include "../plugins/anthropic/src/anthropic_plugin.cpp"
|
||||
@@ -12,6 +16,7 @@
|
||||
#include <string>
|
||||
|
||||
static int g_failures = 0;
|
||||
// Lightweight assertion macro: increments g_failures counter on failure
|
||||
#define CHECK(cond, msg) do { \
|
||||
if (cond) { \
|
||||
std::cout << "[OK] " << (msg) << "\n"; \
|
||||
@@ -22,6 +27,8 @@ static int g_failures = 0;
|
||||
} while (0)
|
||||
|
||||
// Test helper: populate g_cfg for build functions
|
||||
// Test helper: populate g_cfg with valid anthropic defaults before build_* tests
|
||||
// 测试辅助函数:为 build_* 测试填充 g_cfg 的有效 anthropic 默认值
|
||||
static void setup_config() {
|
||||
g_cfg.provider = "anthropic";
|
||||
g_cfg.base_url = "https://api.anthropic.com";
|
||||
@@ -31,10 +38,18 @@ static void setup_config() {
|
||||
g_cfg.temperature = 0.7;
|
||||
}
|
||||
|
||||
// Anthropic 插件测试 (W21.6):parse_sse_data 畸形/无效 JSON、content_block_delta 文本提取、
|
||||
// message_stop/忽略类型、深层/边界结构、build_request_json 基础+边界、build_headers_json、
|
||||
// extract_host_port、secure_zero、my_free_result 空指针安全、my_configure 空指针安全。
|
||||
// Anthropic plugin tests (W21.6): parse_sse_data for malformed/invalid JSON,
|
||||
// content_block_delta text extraction, message_stop/ignored types, deep/edge structures,
|
||||
// build_request_json basics+edges, build_headers_json, extract_host_port,
|
||||
// secure_zero, my_free_result null-safety, and my_configure null-safety.
|
||||
int main()
|
||||
{
|
||||
// ================================================================
|
||||
// Test Block 1: parse_sse_data — invalid/malformed inputs
|
||||
// 测试块 1:parse_sse_data — 无效/畸形输入
|
||||
// ================================================================
|
||||
std::cout << "\n--- Block 1: parse_sse_data invalid/malformed ---\n";
|
||||
|
||||
@@ -69,14 +84,14 @@ int main()
|
||||
}
|
||||
|
||||
{
|
||||
// Malformed JSON: unclosed brace
|
||||
// Malformed JSON: unclosed brace / 畸形 JSON:未闭合的花括号
|
||||
std::string token;
|
||||
bool ret = parse_sse_data("{\"type\":\"ping\"", token, nullptr);
|
||||
CHECK(!ret, "T1.6: malformed JSON (unclosed brace) returns false (no crash)");
|
||||
}
|
||||
|
||||
{
|
||||
// Random garbage bytes
|
||||
// Random garbage bytes / 随机垃圾字节
|
||||
std::string token;
|
||||
bool ret = parse_sse_data("\x00\x01\xFF\xFE", token, nullptr);
|
||||
CHECK(!ret, "T1.7: binary garbage returns false (no crash)");
|
||||
@@ -84,6 +99,7 @@ int main()
|
||||
|
||||
// ================================================================
|
||||
// Test Block 2: parse_sse_data — content_block_delta
|
||||
// 测试块 2:parse_sse_data — content_block_delta
|
||||
// ================================================================
|
||||
std::cout << "\n--- Block 2: parse_sse_data content_block_delta ---\n";
|
||||
|
||||
@@ -146,6 +162,7 @@ int main()
|
||||
|
||||
// ================================================================
|
||||
// Test Block 3: parse_sse_data — message_stop / ignored types
|
||||
// 测试块 3:parse_sse_data — message_stop / 忽略的类型
|
||||
// ================================================================
|
||||
std::cout << "\n--- Block 3: parse_sse_data message_stop / ignored types ---\n";
|
||||
|
||||
@@ -194,11 +211,12 @@ int main()
|
||||
|
||||
// ================================================================
|
||||
// Test Block 4: parse_sse_data — deeply nested / edge structures
|
||||
// 测试块 4:parse_sse_data — 深层嵌套 / 边界结构
|
||||
// ================================================================
|
||||
std::cout << "\n--- Block 4: parse_sse_data deep/edge structures ---\n";
|
||||
|
||||
{
|
||||
// Unrecognized event type should just be ignored
|
||||
// Unrecognized event type should just be ignored / 未识别的事件类型应被忽略
|
||||
std::string token;
|
||||
const char* json = "{\"type\":\"some_unknown_future_type\"}";
|
||||
bool ret = parse_sse_data(json, token, nullptr);
|
||||
@@ -206,7 +224,7 @@ int main()
|
||||
}
|
||||
|
||||
{
|
||||
// text_delta with unicode content (Japanese)
|
||||
// text_delta with unicode content (Japanese) / 含 unicode 内容的 text_delta(日语)
|
||||
std::string token;
|
||||
const char* json =
|
||||
"{\"type\":\"content_block_delta\","
|
||||
@@ -221,6 +239,7 @@ int main()
|
||||
|
||||
{
|
||||
// Realistic Anthropic SSE chunk (content_block_delta + text_delta)
|
||||
// 真实的 Anthropic SSE 数据块(content_block_delta + text_delta)
|
||||
std::string token;
|
||||
const char* json =
|
||||
"{\"type\":\"content_block_delta\","
|
||||
@@ -233,12 +252,13 @@ int main()
|
||||
|
||||
// ================================================================
|
||||
// Test Block 5: build_request_json — basic cases
|
||||
// 测试块 5:build_request_json — 基础用例
|
||||
// ================================================================
|
||||
setup_config();
|
||||
std::cout << "\n--- Block 5: build_request_json basic ---\n";
|
||||
|
||||
{
|
||||
// Single user input, no history, stream=false
|
||||
// Single user input, no history, stream=false / 单一用户输入,无历史,stream=false
|
||||
std::string json = build_request_json(nullptr, 0, "Hello", "", false);
|
||||
CHECK(!json.empty(), "T5.1: non-empty JSON produced");
|
||||
CHECK(json.find("\"messages\"") != std::string::npos,
|
||||
@@ -257,7 +277,7 @@ int main()
|
||||
}
|
||||
|
||||
{
|
||||
// With system message in history
|
||||
// With system message in history / 历史中包含系统消息
|
||||
dstalk_message_t msgs[1] = {
|
||||
{"system", "You are a helpful assistant", nullptr, nullptr}
|
||||
};
|
||||
@@ -268,6 +288,7 @@ int main()
|
||||
"T5.9: system prompt content present");
|
||||
// messages should NOT contain the system role
|
||||
// (since system messages are stripped from messages[] and put in system field)
|
||||
// messages 不应包含 system 角色(系统消息从 messages[] 中提取出来,放入 system 字段)
|
||||
// Actually, the code puts non-system into msgs. Let me check if system is in messages...
|
||||
// The loop skips system: `if (m.role && strcmp(m.role, "system")==0) { ... continue; }`
|
||||
// So system should NOT be in the messages array.
|
||||
@@ -276,7 +297,7 @@ int main()
|
||||
}
|
||||
|
||||
{
|
||||
// With user+assistant history
|
||||
// With user+assistant history / 包含 user+assistant 历史
|
||||
dstalk_message_t msgs[2] = {
|
||||
{"user", "What is 2+2?", nullptr, nullptr},
|
||||
{"assistant", "It is 4.", nullptr, nullptr}
|
||||
@@ -292,11 +313,12 @@ int main()
|
||||
|
||||
// ================================================================
|
||||
// Test Block 6: build_request_json — edge cases
|
||||
// 测试块 6:build_request_json — 边界情况
|
||||
// ================================================================
|
||||
std::cout << "\n--- Block 6: build_request_json edge cases ---\n";
|
||||
|
||||
{
|
||||
// Empty user input
|
||||
// Empty user input / 空用户输入
|
||||
std::string json = build_request_json(nullptr, 0, "", "", false);
|
||||
CHECK(!json.empty(), "T6.1: empty user input produces valid JSON");
|
||||
CHECK(json.find("\"role\":\"user\"") != std::string::npos,
|
||||
@@ -314,7 +336,7 @@ int main()
|
||||
}
|
||||
|
||||
{
|
||||
// Temperature in valid range -> should be included
|
||||
// Temperature in valid range -> should be included / 有效范围内的 temperature -> 应包含
|
||||
g_cfg.temperature = 1.0;
|
||||
std::string json = build_request_json(nullptr, 0, "Hi", "", false);
|
||||
CHECK(json.find("\"temperature\"") != std::string::npos,
|
||||
@@ -323,7 +345,7 @@ int main()
|
||||
}
|
||||
|
||||
{
|
||||
// Temperature out of range -> should NOT be included
|
||||
// Temperature out of range -> should NOT be included / 超出范围的 temperature -> 不应包含
|
||||
g_cfg.temperature = 1.5;
|
||||
std::string json = build_request_json(nullptr, 0, "Hi", "", false);
|
||||
CHECK(json.find("\"temperature\"") == std::string::npos,
|
||||
@@ -336,7 +358,7 @@ int main()
|
||||
}
|
||||
|
||||
{
|
||||
// History with null role (should default to "")
|
||||
// History with null role (should default to "") / null 角色的历史(应默认为 "")
|
||||
dstalk_message_t msgs[1] = {
|
||||
{nullptr, "some content", nullptr, nullptr}
|
||||
};
|
||||
@@ -345,7 +367,7 @@ int main()
|
||||
}
|
||||
|
||||
{
|
||||
// History with null content
|
||||
// History with null content / null 内容的历史
|
||||
dstalk_message_t msgs[1] = {
|
||||
{"user", nullptr, nullptr, nullptr}
|
||||
};
|
||||
@@ -355,6 +377,7 @@ int main()
|
||||
|
||||
{
|
||||
// Very long message (>2000 chars) — validate no truncation / crash
|
||||
// 超长消息 (>2000 字符) — 验证无截断/崩溃
|
||||
std::string long_input(5000, 'A');
|
||||
std::string json = build_request_json(nullptr, 0, long_input, "", false);
|
||||
CHECK(!json.empty(), "T6.10: 5000-char input produces valid JSON");
|
||||
@@ -362,7 +385,7 @@ int main()
|
||||
}
|
||||
|
||||
{
|
||||
// Multiple system messages concatenated
|
||||
// Multiple system messages concatenated / 多条系统消息拼接
|
||||
dstalk_message_t msgs[2] = {
|
||||
{"system", "Rule 1: be polite", nullptr, nullptr},
|
||||
{"system", "Rule 2: be concise", nullptr, nullptr}
|
||||
@@ -376,6 +399,7 @@ int main()
|
||||
|
||||
// ================================================================
|
||||
// Test Block 7: build_headers_json
|
||||
// 测试块 7:build_headers_json
|
||||
// ================================================================
|
||||
std::cout << "\n--- Block 7: build_headers_json ---\n";
|
||||
|
||||
@@ -392,7 +416,7 @@ int main()
|
||||
}
|
||||
|
||||
{
|
||||
// With empty API key
|
||||
// With empty API key / 空 API key
|
||||
std::string saved = g_cfg.api_key;
|
||||
g_cfg.api_key = "";
|
||||
std::string headers = build_headers_json();
|
||||
@@ -405,6 +429,7 @@ int main()
|
||||
|
||||
// ================================================================
|
||||
// Test Block 8: extract_host_port
|
||||
// 测试块 8:extract_host_port
|
||||
// ================================================================
|
||||
std::cout << "\n--- Block 8: extract_host_port ---\n";
|
||||
|
||||
@@ -473,6 +498,7 @@ int main()
|
||||
|
||||
// ================================================================
|
||||
// Test Block 9: secure_zero
|
||||
// 测试块 9:secure_zero
|
||||
// ================================================================
|
||||
std::cout << "\n--- Block 9: secure_zero ---\n";
|
||||
|
||||
@@ -488,7 +514,7 @@ int main()
|
||||
}
|
||||
|
||||
{
|
||||
// Zero-length should not crash
|
||||
// Zero-length should not crash / 零长度不应崩溃
|
||||
char buf[4] = {1,2,3,4};
|
||||
secure_zero(buf, 0);
|
||||
CHECK(buf[0] == 1 && buf[3] == 4,
|
||||
@@ -496,18 +522,20 @@ int main()
|
||||
}
|
||||
|
||||
{
|
||||
// Null pointer + zero length = no-op
|
||||
// Null pointer + zero length = no-op / 空指针 + 零长度 = 空操作
|
||||
secure_zero(nullptr, 0);
|
||||
CHECK(true, "T9.3: secure_zero(nullptr, 0) does not crash");
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Test Block 10: my_free_result — null safety
|
||||
// 测试块 10:my_free_result — 空指针安全
|
||||
// ================================================================
|
||||
std::cout << "\n--- Block 10: my_free_result null safety ---\n";
|
||||
|
||||
{
|
||||
// g_host is nullptr, so free_result should early-return
|
||||
// g_host 为 nullptr,free_result 应提前返回
|
||||
my_free_result(nullptr);
|
||||
CHECK(true, "T10.1: free_result(nullptr) does not crash (null host)");
|
||||
}
|
||||
@@ -524,11 +552,13 @@ int main()
|
||||
|
||||
// ================================================================
|
||||
// Test Block 11: my_configure — null host safety
|
||||
// 测试块 11:my_configure — null host 安全
|
||||
// ================================================================
|
||||
std::cout << "\n--- Block 11: my_configure null host safety ---\n";
|
||||
|
||||
{
|
||||
// g_host is nullptr, configure should still return 0 (log skipped)
|
||||
// g_host 为 nullptr,configure 仍应返回 0(跳过日志)
|
||||
int ret = my_configure(
|
||||
"anthropic", "https://api.anthropic.com",
|
||||
"sk-key", "claude-sonnet", 2048, 0.5);
|
||||
@@ -539,13 +569,13 @@ int main()
|
||||
}
|
||||
|
||||
{
|
||||
// Null string params — should not crash
|
||||
// Null string params — should not crash / null 字符串参数 — 不应崩溃
|
||||
int ret = my_configure(nullptr, nullptr, nullptr, nullptr, 4096, 0.7);
|
||||
CHECK(ret == 0, "T11.5: my_configure with all-null strings returns 0");
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Summary
|
||||
// Summary / 总结
|
||||
// ================================================================
|
||||
std::cout << "\n";
|
||||
if (g_failures == 0) {
|
||||
|
||||
Reference in New Issue
Block a user