/* * @file deepseek_plugin_test.cpp * @brief DeepSeek 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] 标记匹配、 * tool_calls delta 提取、请求构建、append_history、extract_host_port、secure_zero、空指针安全。 * Copyright (c) 2026 dstalk contributors. GPLv3. */ #define BOOST_JSON_HEADER_ONLY #define BOOST_ALL_NO_LIB #include "../plugins/deepseek/src/deepseek_plugin.cpp" #include #include #include 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"; \ } else { \ std::cerr << "[FAIL] " << (msg) << "\n"; \ g_failures++; \ } \ } while (0) // Test helper: populate g_cfg for build functions // 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.api_key = "sk-ds-test-key-67890"; g_cfg.model = "deepseek-v4-pro"; g_cfg.max_tokens = 4096; g_cfg.temperature = 0.7; } // DeepSeek 插件测试 (W21.6):parse_sse_line 无效/畸形输入、[DONE] 标记及空白变体、 // content delta 提取、tool_calls delta 累积、build_request_json(基础、tools、边界)、 // build_headers_json、extract_host_port、secure_zero、append_history(所有消息类型)、 // my_free_result、my_configure。 // DeepSeek plugin tests (W21.6): parse_sse_line invalid/malformed inputs, [DONE] sentinel // with whitespace variants, content delta extraction, tool_calls delta accumulation, // build_request_json (basic, tools, edge cases), build_headers_json, extract_host_port, // secure_zero, append_history (all message types), my_free_result, and my_configure. int main() { // ================================================================ // Test Block 1: parse_sse_line — invalid/malformed inputs // 测试块 1:parse_sse_line — 无效/畸形输入 // ================================================================ std::cout << "\n--- Block 1: parse_sse_line invalid/malformed ---\n"; { std::string token; bool ret = parse_sse_line("", token, nullptr); CHECK(!ret, "T1.1: empty line returns false"); } { std::string token; bool ret = parse_sse_line("not a data line", token, nullptr); CHECK(!ret, "T1.2: non-'data:' prefix returns false"); } { std::string token; bool ret = parse_sse_line("event: message", token, nullptr); CHECK(!ret, "T1.3: 'event:' line returns false"); } { // "data:" without space — rfind("data: ", 0) should fail // "data:" 无空格 — rfind("data: ", 0) 应失败 std::string token; bool ret = parse_sse_line("data:{\"x\":1}", token, nullptr); CHECK(!ret, "T1.4: 'data:' without trailing space returns false (rfind mismatch)"); } { // "data: " followed by invalid JSON / "data: " 后跟无效 JSON std::string token; bool ret = parse_sse_line("data: not valid json!!!", token, nullptr); CHECK(!ret, "T1.5: 'data: ' + invalid JSON returns false (no crash)"); } { // "data: " followed by binary garbage / "data: " 后跟二进制垃圾 std::string token; bool ret = parse_sse_line("data: \x00\x01\xFF\xFE", token, nullptr); CHECK(!ret, "T1.6: 'data: ' + binary garbage returns false (no crash)"); } { // Empty data after "data: " / "data: " 后数据为空 std::string token; bool ret = parse_sse_line("data: ", token, nullptr); CHECK(!ret, "T1.7: 'data: ' with empty payload returns false"); } // ================================================================ // Test Block 2: parse_sse_line — [DONE] sentinel // 测试块 2:parse_sse_line — [DONE] 标记 // ================================================================ std::cout << "\n--- Block 2: parse_sse_line [DONE] sentinel ---\n"; { std::string token = "SHOULD_BE_CLEARED"; bool ret = parse_sse_line("data: [DONE]", token, nullptr); CHECK(ret, "T2.1: 'data: [DONE]' returns true (stream end)"); CHECK(token.empty(), "T2.2: [DONE] clears token"); } { // [DONE] with leading whitespace / [DONE] 前导空白 std::string token; bool ret = parse_sse_line("data: [DONE]", token, nullptr); CHECK(ret, "T2.3: 'data: [DONE]' (leading spaces) returns true"); CHECK(token.empty(), "T2.4: whitespace-trimmed [DONE] clears token"); } { // [DONE] with trailing whitespace / [DONE] 尾部空白 std::string token; bool ret = parse_sse_line("data: [DONE] ", token, nullptr); CHECK(ret, "T2.5: 'data: [DONE] ' (trailing spaces) returns true"); CHECK(token.empty(), "T2.6: trailing-whitespace [DONE] clears token"); } { // [DONE] with tabs and newlines around it / [DONE] 周围有制表符和换行符 std::string token; bool ret = parse_sse_line("data: \t [DONE] \t\r\n", token, nullptr); CHECK(ret, "T2.7: '[DONE]' with mixed whitespace returns true"); CHECK(token.empty(), "T2.8: mixed-whitespace [DONE] clears token"); } { // [DONE] without spaces — exact match / [DONE] 精确匹配(无空格) std::string token; bool ret = parse_sse_line("data: [DONE]", token, nullptr); CHECK(ret, "T2.9: '[DONE]' exact match returns true"); } { // "[done]" lowercase — should NOT match (case-sensitive) // "[done]" 小写 — 不应匹配(大小写敏感) std::string token; bool ret = parse_sse_line("data: [done]", token, nullptr); CHECK(!ret, "T2.10: '[done]' lowercase NOT treated as DONE (case-sensitive)"); } { // "[DONE" without closing bracket / "[DONE" 缺少闭括号 std::string token; bool ret = parse_sse_line("data: [DONE", token, nullptr); CHECK(!ret, "T2.11: '[DONE' (no closing bracket) not treated as DONE"); } // ================================================================ // Test Block 3: parse_sse_line — content delta // 测试块 3:parse_sse_line — content delta // ================================================================ std::cout << "\n--- Block 3: parse_sse_line content delta ---\n"; { std::string token; const char* json = "data: {\"choices\":[{\"delta\":{\"content\":\"Hello\"}," "\"index\":0}]}"; bool ret = parse_sse_line(json, token, nullptr); CHECK(ret, "T3.1: delta with content 'Hello' returns true"); CHECK(token == "Hello", "T3.2: token equals 'Hello'"); } { std::string token; const char* json = "data: {\"choices\":[{\"delta\":{\"content\":\"\"},\"index\":0}]}"; bool ret = parse_sse_line(json, token, nullptr); CHECK(ret, "T3.3: delta with empty content returns true"); CHECK(token.empty(), "T3.4: empty content token is empty"); } { // Delta with no content field / delta 不含 content 字段 std::string token; const char* json = "data: {\"choices\":[{\"delta\":{},\"index\":0}]}"; bool ret = parse_sse_line(json, token, nullptr); CHECK(!ret, "T3.5: delta without 'content' field returns false"); } { // Empty choices array / 空 choices 数组 std::string token; const char* json = "data: {\"choices\":[]}"; bool ret = parse_sse_line(json, token, nullptr); CHECK(!ret, "T3.6: empty choices array returns false"); } { // Single character token (typical streaming) / 单字符 token(典型流式) std::string token; const char* json = "data: {\"choices\":[{\"delta\":{\"content\":\"H\"},\"index\":0}]}"; bool ret = parse_sse_line(json, token, nullptr); CHECK(ret, "T3.7: single-char delta returns true"); CHECK(token == "H", "T3.8: single-char token correct"); } { // Multi-byte UTF-8 content (emoji) in delta / delta 中的多字节 UTF-8 内容(emoji) std::string token; const char* json = "data: {\"choices\":[{\"delta\":{\"content\":\"\\uD83D\\uDE00\"}," "\"index\":0}]}"; bool ret = parse_sse_line(json, token, nullptr); CHECK(ret, "T3.9: emoji delta returns true"); // U+1F600 in UTF-8: F0 9F 98 80 std::string emoji = "\xF0\x9F\x98\x80"; CHECK(token == emoji, "T3.10: emoji token decoded correctly (U+1F600)"); } { // Malformed JSON structure — no "delta" key / 畸形 JSON 结构 — 无 "delta" key std::string token; const char* json = "data: {\"choices\":[{\"no_delta\":{},\"index\":0}]}"; bool ret = parse_sse_line(json, token, nullptr); CHECK(!ret, "T3.11: choice without 'delta' key returns false"); } { // Realistic DeepSeek streaming chunk (with finish_reason) // 真实的 DeepSeek 流式数据块(含 finish_reason) std::string token; const char* json = "data: {\"id\":\"chatcmpl-xxx\"," "\"object\":\"chat.completion.chunk\"," "\"created\":1712345678," "\"model\":\"deepseek-v4-pro\"," "\"choices\":[{\"index\":0," "\"delta\":{\"content\":\" World\"}," "\"finish_reason\":null}]}"; bool ret = parse_sse_line(json, token, nullptr); CHECK(ret, "T3.12: realistic DeepSeek chunk returns true"); CHECK(token == " World", "T3.13: realistic chunk token correct"); } // ================================================================ // Test Block 4: parse_sse_line — tool_calls delta // 测试块 4:parse_sse_line — tool_calls delta // ================================================================ std::cout << "\n--- Block 4: parse_sse_line tool_calls delta ---\n"; { // tool_calls chunk with id + function name (first chunk) // tool_calls 数据块含 id + function name(首个数据块) StreamContext ctx = {}; std::string token; const char* json = "data: {\"choices\":[{\"index\":0," "\"delta\":{\"tool_calls\":[{\"index\":0," "\"id\":\"call_abc123\"," "\"type\":\"function\"," "\"function\":{\"name\":\"get_weather\",\"arguments\":\"\"}}]}}]}"; bool ret = parse_sse_line(json, token, &ctx); CHECK(!ret, "T4.1: tool_calls first chunk returns false (no content token)"); CHECK(ctx.tool_calls.size() >= 1, "T4.2: tool_calls accumulated in ctx"); if (ctx.tool_calls.size() >= 1) { CHECK(ctx.tool_calls[0].index == 0, "T4.3: tool_call index=0"); CHECK(ctx.tool_calls[0].id == "call_abc123", "T4.4: tool_call id stored"); CHECK(ctx.tool_calls[0].name == "get_weather", "T4.5: tool_call name stored"); } } { // tool_calls arguments chunk (second chunk, same index) // tool_calls arguments 数据块(第二个数据块,相同 index) StreamContext ctx; // First, set up the initial state / 先设置初始状态 ctx.tool_calls.push_back({0, "call_abc123", "get_weather", ""}); std::string token; const char* json = "data: {\"choices\":[{\"index\":0," "\"delta\":{\"tool_calls\":[{\"index\":0," "\"function\":{\"arguments\":\"{\\\"city\\\":\\\"\"}}]}}]}"; bool ret = parse_sse_line(json, token, &ctx); CHECK(!ret, "T4.6: tool_calls arguments chunk returns false"); if (ctx.tool_calls.size() >= 1) { CHECK(ctx.tool_calls[0].arguments == "{\"city\":\"", "T4.7: arguments accumulated (first fragment)"); } } { // tool_calls final arguments chunk / tool_calls 最终 arguments 数据块 StreamContext ctx; ctx.tool_calls.push_back({0, "call_abc123", "get_weather", "{\"city\":\""}); std::string token; const char* json = "data: {\"choices\":[{\"index\":0," "\"delta\":{\"tool_calls\":[{\"index\":0," "\"function\":{\"arguments\":\"Beijing\\\"}\"}}]}}]}"; bool ret = parse_sse_line(json, token, &ctx); CHECK(!ret, "T4.8: tool_calls final chunk returns false"); if (ctx.tool_calls.size() >= 1) { CHECK(ctx.tool_calls[0].arguments == "{\"city\":\"Beijing\"}", "T4.9: full arguments accumulated correctly"); } } { // tool_calls with null ctx — should skip tool_calls processing // tool_calls 配合 null ctx — 应跳过 tool_calls 处理 std::string token; const char* json = "data: {\"choices\":[{\"index\":0," "\"delta\":{\"tool_calls\":[{\"index\":0," "\"function\":{\"arguments\":\"{}\"}}]}}]}"; bool ret = parse_sse_line(json, token, nullptr); CHECK(!ret, "T4.10: tool_calls with null ctx returns false (no crash)"); } { // Multiple tool_calls in single chunk (unusual but valid) // 单个数据块中有多个 tool_calls(不常见但合法) StreamContext ctx; std::string token; const char* json = "data: {\"choices\":[{\"index\":0," "\"delta\":{\"tool_calls\":[" "{\"index\":0,\"function\":{\"arguments\":\"a\"}}," "{\"index\":1,\"function\":{\"arguments\":\"b\"}}" "]}}]}"; bool ret = parse_sse_line(json, token, &ctx); CHECK(!ret, "T4.11: multi-tool_call chunk returns false"); CHECK(ctx.tool_calls.size() >= 2, "T4.12: both tool_calls accumulated"); if (ctx.tool_calls.size() >= 2) { CHECK(ctx.tool_calls[0].index == 0, "T4.13: first tool_call index=0"); CHECK(ctx.tool_calls[1].index == 1, "T4.14: second tool_call index=1"); } } // ================================================================ // Test Block 5: build_request_json — basic cases // 测试块 5:build_request_json — 基础用例 // ================================================================ setup_config(); std::cout << "\n--- Block 5: build_request_json basic ---\n"; { 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, "T5.2: contains 'messages' array"); CHECK(json.find("\"user\"") != std::string::npos, "T5.3: contains 'user' role"); CHECK(json.find("\"Hello\"") != std::string::npos, "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\"") != std::string::npos, "T5.6: model field present"); CHECK(json.find("\"max_tokens\":4096") != std::string::npos, "T5.7: max_tokens present"); CHECK(json.find("\"temperature\"") != std::string::npos, "T5.8: temperature field present (always included in DeepSeek)"); } { // With user+assistant history / 包含 user+assistant 历史 dstalk_message_t msgs[2] = { {"user", "What is 2+2?", nullptr, nullptr}, {"assistant", "It is 4.", nullptr, nullptr} }; std::string json = build_request_json( msgs, 2, "Thanks!", "", false); CHECK(json.find("\"role\":\"user\"") != std::string::npos, "T5.9: user role present"); CHECK(json.find("\"role\":\"assistant\"") != std::string::npos, "T5.10: assistant role present"); CHECK(json.find("Thanks!") != std::string::npos, "T5.11: current user input present"); } { // Stream=true std::string json = build_request_json( nullptr, 0, "Hi", "", true); CHECK(json.find("\"stream\":true") != std::string::npos, "T5.12: stream=true present"); } { // Empty user input — no user message appended // 空用户输入 — 不追加 user 消息 std::string json = build_request_json( nullptr, 0, "", "", false); CHECK(!json.empty(), "T5.13: empty user input produces valid JSON"); // DeepSeek's build_request_json checks `if (!user_input.empty())` before adding // So there should be no user message for empty input // DeepSeek 的 build_request_json 在添加前检查 `if (!user_input.empty())` // 因此空输入时不应有 user 消息 CHECK(json.find("\"role\":\"user\"") == std::string::npos, "T5.14: empty user input NOT added to messages (DeepSeek guard)"); } // ================================================================ // Test Block 6: build_request_json — tools / edge cases // 测试块 6:build_request_json — tools / 边界情况 // ================================================================ std::cout << "\n--- Block 6: build_request_json tools / edges ---\n"; { // With tools_json / 含 tools_json std::string tools = "[{\"type\":\"function\"," "\"function\":{\"name\":\"get_weather\"," "\"description\":\"Get current weather\"," "\"parameters\":{\"type\":\"object\"," "\"properties\":{\"city\":{\"type\":\"string\"}}," "\"required\":[\"city\"]}}}]"; std::string json = build_request_json( nullptr, 0, "Weather in Beijing?", tools, false); CHECK(json.find("\"tools\"") != std::string::npos, "T6.1: 'tools' field present when tools_json provided"); CHECK(json.find("get_weather") != std::string::npos, "T6.2: tool name present in serialized JSON"); } { // Empty tools_json — no tools field / 空 tools_json — 无 tools 字段 std::string json = build_request_json( nullptr, 0, "Hello", "", false); CHECK(json.find("\"tools\"") == std::string::npos, "T6.3: no 'tools' field when tools_json is empty"); } { // Malformed tools_json — build_request_json calls json::parse() // without try/catch, so it will throw std::exception. // This test verifies that the exception is thrown (rather than crashing). // 畸形 tools_json — build_request_json 调用 json::parse() 不含 try/catch, // 因此会抛出 std::exception。本测试验证异常被抛出(而非崩溃)。 bool threw = false; try { build_request_json(nullptr, 0, "Hello", "NOT JSON", false); } catch (const std::exception&) { threw = true; } catch (...) { threw = true; } CHECK(threw, "T6.4: malformed tools_json throws (expected, not a crash)"); } { // History with null role / null 角色的历史 dstalk_message_t msgs[1] = { {nullptr, "some content", nullptr, nullptr} }; std::string json = build_request_json(msgs, 1, "Hi", "", false); CHECK(!json.empty(), "T6.5: null role produces valid JSON (no crash)"); } { // History with null content / null 内容的历史 dstalk_message_t msgs[1] = { {"user", nullptr, nullptr, nullptr} }; std::string json = build_request_json(msgs, 1, "Hi", "", false); CHECK(!json.empty(), "T6.6: null content produces valid JSON (no crash)"); } { // Very long message / 超长消息 std::string long_input(5000, 'A'); std::string json = build_request_json( nullptr, 0, long_input, "", false); CHECK(!json.empty(), "T6.7: 5000-char input produces valid JSON"); CHECK(json.length() > 5000, "T6.8: JSON longer than input (wraps content)"); } // ================================================================ // Test Block 7: build_headers_json // 测试块 7:build_headers_json // ================================================================ std::cout << "\n--- Block 7: build_headers_json ---\n"; { std::string headers = build_headers_json("sk-test-123"); CHECK(headers.find("Authorization") != std::string::npos, "T7.1: contains Authorization header"); CHECK(headers.find("Bearer sk-test-123") != std::string::npos, "T7.2: contains 'Bearer sk-test-123'"); } { // Empty API key / 空 API key std::string headers = build_headers_json(""); CHECK(headers.find("Authorization") != std::string::npos, "T7.3: Authorization header present with empty key"); CHECK(headers.find("Bearer ") != std::string::npos, "T7.4: 'Bearer ' prefix present even with empty key"); } // ================================================================ // Test Block 8: extract_host_port (same logic as anthropic) // 测试块 8:extract_host_port(逻辑同 anthropic) // ================================================================ std::cout << "\n--- Block 8: extract_host_port ---\n"; { std::string scheme, host, port, target; bool ret = extract_host_port( "https://api.deepseek.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(port == "443", "T8.4: default HTTPS port 443"); CHECK(target == "/v1/chat/completions", "T8.5: target path extracted"); } { std::string scheme, host, port, target; bool ret = extract_host_port( "http://localhost:11434/api/generate", scheme, host, port, target); CHECK(ret, "T8.6: HTTP URL with port returns true"); CHECK(scheme == "http", "T8.7: scheme is 'http'"); CHECK(host == "localhost", "T8.8: host is 'localhost'"); CHECK(port == "11434", "T8.9: explicit port 11434"); } { std::string scheme, host, port, target; bool ret = extract_host_port( "no-scheme-url.com", scheme, host, port, target); CHECK(!ret, "T8.10: URL without scheme returns false"); } { std::string scheme, host, port, target; bool ret = extract_host_port( "https://api.deepseek.com", scheme, host, port, target); CHECK(ret, "T8.11: URL without path returns true"); CHECK(target == "/", "T8.12: target defaults to '/'"); } // ================================================================ // Test Block 9: secure_zero // 测试块 9:secure_zero // ================================================================ std::cout << "\n--- Block 9: secure_zero ---\n"; { char buf[16]; memset(buf, 0xFF, sizeof(buf)); secure_zero(buf, sizeof(buf)); bool all_zero = true; for (int i = 0; i < 16; ++i) { if (buf[i] != 0) { all_zero = false; break; } } CHECK(all_zero, "T9.1: secure_zero zeros entire buffer"); } { secure_zero(nullptr, 0); CHECK(true, "T9.2: secure_zero(nullptr, 0) does not crash"); } // ================================================================ // Test Block 10: append_history // 测试块 10:append_history // ================================================================ std::cout << "\n--- Block 10: append_history ---\n"; { json::array msgs; dstalk_message_t m = {"user", "Hello", nullptr, nullptr}; append_history(msgs, &m, 1); CHECK(msgs.size() == 1, "T10.1: one message appended"); CHECK(msgs[0].as_object()["role"].as_string() == "user", "T10.2: role preserved"); CHECK(msgs[0].as_object()["content"].as_string() == "Hello", "T10.3: content preserved"); } { // Tool message (should include tool_call_id) / Tool 消息(应包含 tool_call_id) json::array msgs; dstalk_message_t m = {"tool", "result data", "call_xyz", nullptr}; append_history(msgs, &m, 1); CHECK(msgs.size() == 1, "T10.4: tool message appended"); auto obj = msgs[0].as_object(); CHECK(obj["role"].as_string() == "tool", "T10.5: tool role preserved"); CHECK(obj["tool_call_id"].as_string() == "call_xyz", "T10.6: tool_call_id preserved"); CHECK(obj["content"].as_string() == "result data", "T10.7: tool message content preserved"); } { // Assistant with tool_calls_json / Assistant 含 tool_calls_json json::array msgs; const char* tc_json = "[{\"id\":\"call_1\",\"type\":\"function\"," "\"function\":{\"name\":\"get_weather\",\"arguments\":\"{}\"}}]"; dstalk_message_t m = {"assistant", "Let me check", nullptr, tc_json}; append_history(msgs, &m, 1); CHECK(msgs.size() == 1, "T10.8: assistant with tool_calls appended"); auto obj = msgs[0].as_object(); CHECK(obj["role"].as_string() == "assistant", "T10.9: assistant role"); CHECK(obj["content"].as_string() == "Let me check", "T10.10: content"); CHECK(obj.if_contains("tool_calls") != nullptr, "T10.11: tool_calls present"); } { // Empty history (0 messages) / 空历史(0 条消息) json::array msgs; append_history(msgs, nullptr, 0); CHECK(msgs.size() == 0, "T10.12: empty history produces empty array"); } { // Multiple messages / 多条消息 json::array msgs; dstalk_message_t ms[2] = { {"user", "Q1", nullptr, nullptr}, {"assistant", "A1", nullptr, nullptr} }; append_history(msgs, ms, 2); CHECK(msgs.size() == 2, "T10.13: two messages appended"); } { // Null role and null content — default to empty strings // null 角色与 null 内容 — 默认为空字符串 json::array msgs; dstalk_message_t m = {nullptr, nullptr, nullptr, nullptr}; append_history(msgs, &m, 1); CHECK(msgs.size() == 1, "T10.14: null fields produce valid JSON (no crash)"); auto obj = msgs[0].as_object(); CHECK(obj["role"].as_string() == "", "T10.15: null role → empty string"); CHECK(obj["content"].as_string() == "", "T10.16: null content → empty string"); } // ================================================================ // Test Block 11: my_free_result — null safety // 测试块 11:my_free_result — 空指针安全 // ================================================================ std::cout << "\n--- Block 11: 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, "T11.1: free_result(nullptr) does not crash (null host)"); } { dstalk_chat_result_t r = {}; r.ok = 1; my_free_result(&r); CHECK(true, "T11.2: free_result with zeroed fields does not crash"); } // ================================================================ // Test Block 12: my_configure — null host safety // 测试块 12:my_configure — null host 安全 // ================================================================ std::cout << "\n--- Block 12: my_configure null host safety ---\n"; { int ret = my_configure( "deepseek", "https://api.deepseek.com/v1", "sk-key", "deepseek-v4", 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.max_tokens == 2048, "T12.3: max_tokens stored"); CHECK(g_cfg.temperature == 0.5, "T12.4: temperature stored"); } { int ret = my_configure(nullptr, nullptr, nullptr, nullptr, 4096, 1.0); CHECK(ret == 0, "T12.5: my_configure with all-null strings returns 0"); } // ================================================================ // Summary / 总结 // ================================================================ std::cout << "\n"; if (g_failures == 0) { std::cout << "=== All deepseek plugin tests passed ===\n"; return 0; } else { std::cerr << "=== " << g_failures << " test(s) FAILED ===\n"; return 1; } }