- C++ 新增四模块(modules.cpp,19 个导出符号全部 hj_*): 小周天 hj_stream:流会话(sequence/delta_index 单调)与 content.block.delta envelope 确定性编码(重放字节一致) 烛照 hj_policy:URL 协议白名单(拒 file/javascript/data)、路径授权根与 .. 段拒绝、幂等键非空≤128、四维决策表(autonomy×risk→allow/ask/deny,非法输入 deny fail-closed) 洛书 hj_plan:预算核算(达限/负数拒绝) 河图 hj_recall:确定性重排(词频×稳定排序,canonical JSON 输出;非法输入 PARSE_FAILED) - doctest 22 用例 20135 断言全绿(含每模块 golden 与确定性双跑) - Go 绑定(cgo 自包含编译引擎源码):Engine RAII/TransformMarkdown/ValidateURL/ValidatePath/Decide/CheckBudget/RecallRerank;go test 3 用例通过(transform/rerank golden 与 C++ 字节一致) - Rust 绑定(bindgen 生成 + 安全包装):Engine RAII(Drop 释放)、Error 枚举映射全部 HjStatus、Send/Sync(HjEngine 线程安全);cargo test 3 用例通过(同 golden) - 三端(C++/Go/Rust)与 Python golden 一致:同一 markdown 输入在四端输出字节相同(跨端一致性首次全语言验证)
107 lines
4.5 KiB
C++
107 lines
4.5 KiB
C++
// 小周天/烛照/洛书/河图模块测试。
|
||
#include <cstring>
|
||
#include <string>
|
||
|
||
#include <doctest/doctest.h>
|
||
|
||
#include "hunjing/lingtai.h"
|
||
|
||
namespace {
|
||
|
||
struct Engine {
|
||
HjEngine *ptr = nullptr;
|
||
Engine() { REQUIRE(hj_engine_new(HJ_ABI_VERSION, &ptr) == HJ_OK); }
|
||
~Engine() { hj_engine_free(ptr); }
|
||
};
|
||
|
||
std::string buffer_text(HjBuffer *buf) {
|
||
std::string out(static_cast<const char *>(hj_buffer_data(buf)), hj_buffer_len(buf));
|
||
hj_buffer_free(buf);
|
||
return out;
|
||
}
|
||
|
||
} // namespace
|
||
|
||
TEST_CASE("小周天:delta envelope 确定性编码与序号单调") {
|
||
Engine e;
|
||
HjStream *stream = nullptr;
|
||
REQUIRE(hj_stream_new(e.ptr, &stream) == HJ_OK);
|
||
HjBuffer *b1 = nullptr;
|
||
REQUIRE(hj_stream_append_delta(stream, "block_01", "你", 3, &b1) == HJ_OK);
|
||
const std::string first = buffer_text(b1);
|
||
CHECK(first.find("\"type\":\"content.block.delta\"") != std::string::npos);
|
||
CHECK(first.find("\"sequence\":0") != std::string::npos);
|
||
CHECK(first.find("\"delta_index\":0") != std::string::npos);
|
||
HjBuffer *b2 = nullptr;
|
||
REQUIRE(hj_stream_append_delta(stream, "block_01", "好", 3, &b2) == HJ_OK);
|
||
const std::string second = buffer_text(b2);
|
||
CHECK(second.find("\"sequence\":1") != std::string::npos);
|
||
CHECK(second.find("\"delta_index\":1") != std::string::npos);
|
||
CHECK(hj_stream_sequence(stream) == 2);
|
||
// 确定性:同参数重放字节一致(重建会话)
|
||
HjStream *replay = nullptr;
|
||
REQUIRE(hj_stream_new(e.ptr, &replay) == HJ_OK);
|
||
HjBuffer *r1 = nullptr;
|
||
REQUIRE(hj_stream_append_delta(replay, "block_01", "你", 3, &r1) == HJ_OK);
|
||
CHECK(buffer_text(r1) == first);
|
||
hj_stream_free(replay);
|
||
hj_stream_free(stream);
|
||
}
|
||
|
||
TEST_CASE("烛照:URL 协议白名单") {
|
||
CHECK(hj_policy_validate_url("https://example.com/a") == HJ_OK);
|
||
CHECK(hj_policy_validate_url("http://example.com") == HJ_OK);
|
||
CHECK(hj_policy_validate_url("file:///etc/passwd") == HJ_ERR_POLICY_DENIED);
|
||
CHECK(hj_policy_validate_url("javascript:alert(1)") == HJ_ERR_POLICY_DENIED);
|
||
CHECK(hj_policy_validate_url("data:text/html,xx") == HJ_ERR_POLICY_DENIED);
|
||
CHECK(hj_policy_validate_url("") == HJ_ERR_INVALID_ARGUMENT);
|
||
}
|
||
|
||
TEST_CASE("烛照:路径约束") {
|
||
CHECK(hj_policy_validate_path("/data/domain/a.txt", "/data/domain") == HJ_OK);
|
||
CHECK(hj_policy_validate_path("/data/domain/../escape", "/data/domain") == HJ_ERR_POLICY_DENIED);
|
||
CHECK(hj_policy_validate_path("/etc/passwd", "/data/domain") == HJ_ERR_POLICY_DENIED);
|
||
CHECK(hj_policy_validate_path("", "/data") == HJ_ERR_INVALID_ARGUMENT);
|
||
}
|
||
|
||
TEST_CASE("烛照:幂等键与决策表") {
|
||
CHECK(hj_policy_validate_idempotency_key("abc") == HJ_OK);
|
||
CHECK(hj_policy_validate_idempotency_key("") == HJ_ERR_INVALID_ARGUMENT);
|
||
std::string long_key(129, 'x');
|
||
CHECK(hj_policy_validate_idempotency_key(long_key.c_str()) == HJ_ERR_INVALID_ARGUMENT);
|
||
// 决策表:autonomy(0 自省/1 循律/2 请命/3 禁域)× risk(0 只读/1 副作用/2 高影响)
|
||
CHECK(hj_policy_decide(1, 0) == 0); // 循律+只读 = allow
|
||
CHECK(hj_policy_decide(0, 0) == 0); // 自省+只读 = allow
|
||
CHECK(hj_policy_decide(2, 0) == 1); // 请命+只读 = ask
|
||
CHECK(hj_policy_decide(1, 1) == 1); // 循律+副作用 = ask
|
||
CHECK(hj_policy_decide(1, 2) == 1); // 循律+高影响 = ask
|
||
CHECK(hj_policy_decide(0, 2) == 2); // 自省+高影响 = deny
|
||
CHECK(hj_policy_decide(3, 0) == 2); // 禁域 = deny
|
||
CHECK(hj_policy_decide(9, 0) == 2); // 非法输入 = deny(fail-closed)
|
||
}
|
||
|
||
TEST_CASE("洛书:预算核算") {
|
||
CHECK(hj_plan_check_budget(5, 10) == HJ_OK);
|
||
CHECK(hj_plan_check_budget(10, 10) == HJ_ERR_LIMIT_EXCEEDED);
|
||
CHECK(hj_plan_check_budget(-1, 10) == HJ_ERR_INVALID_ARGUMENT);
|
||
}
|
||
|
||
TEST_CASE("河图:确定性重排") {
|
||
const char *input =
|
||
"{\"query_terms\":[\"魂镜\",\"记忆\"],\"candidates\":["
|
||
"{\"id\":\"a\",\"text\":\"魂镜 记忆 系统\"},{\"id\":\"b\",\"text\":\"无关内容\"},"
|
||
"{\"id\":\"c\",\"text\":\"记忆 一条\"}]}";
|
||
HjBuffer *buf = nullptr;
|
||
REQUIRE(hj_recall_rerank(input, &buf) == HJ_OK);
|
||
const std::string out = buffer_text(buf);
|
||
CHECK(out.find("{\"id\":\"a\",\"score\":20}") != std::string::npos);
|
||
CHECK(out.find("{\"id\":\"c\",\"score\":10}") != std::string::npos);
|
||
CHECK(out.find("{\"id\":\"b\",\"score\":0}") != std::string::npos);
|
||
CHECK(out.find("\"a\"") < out.find("\"c\""));
|
||
HjBuffer *again = nullptr;
|
||
REQUIRE(hj_recall_rerank(input, &again) == HJ_OK);
|
||
CHECK(buffer_text(again) == out);
|
||
HjBuffer *bad = nullptr;
|
||
CHECK(hj_recall_rerank("not json", &bad) == HJ_ERR_PARSE_FAILED);
|
||
}
|