glove/tests/kernel/registry_test.cpp
2026-07-19 21:06:11 -04:00

100 lines
3.4 KiB
C++

// Routes tool calls to extensions by namespace prefix; rejects unknowns.
#include "glove/kernel/extension.hpp"
#include "glove/kernel/registry.hpp"
#include "glove/mcp/messages.hpp"
#include <cstdio>
#include <expected>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
namespace {
#define REQUIRE(cond) \
do { \
if (!(cond)) { \
std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", #cond, __FILE__, __LINE__); \
return 1; \
} \
} while (0)
class fake_extension final : public glove::kernel::extension {
public:
fake_extension(std::string name, std::string tool_name)
: name_{std::move(name)}, tool_name_{std::move(tool_name)} {}
auto name() const -> std::string_view override { return name_; }
auto tools() -> std::expected<std::vector<glove::mcp::tool_descriptor>, std::string> override {
return std::vector<glove::mcp::tool_descriptor>{
{.name = tool_name_,
.description = "fake",
.input_schema_json = "{}",
.annotations = {}}
};
}
auto invoke(std::string_view tool_name, std::string_view arguments_json)
-> std::expected<glove::mcp::tool_call_result, std::string> override {
last_tool_ = std::string{tool_name};
last_args_ = std::string{arguments_json};
return glove::mcp::tool_call_result{
.status = glove::mcp::tool_call_status::ok,
.content = std::string{"ran "} + last_tool_,
.structured_json = "",
.error_message = "",
};
}
std::string last_tool_;
std::string last_args_;
private:
std::string name_;
std::string tool_name_;
};
auto run() -> int {
glove::kernel::registry reg;
auto ext_a = std::make_unique<fake_extension>("alpha", "ping");
auto ext_b = std::make_unique<fake_extension>("beta", "pong");
auto* a_raw = ext_a.get();
auto* b_raw = ext_b.get();
REQUIRE(reg.add(std::move(ext_a)).has_value());
REQUIRE(reg.add(std::move(ext_b)).has_value());
// Duplicate name is rejected.
auto dup = std::make_unique<fake_extension>("alpha", "x");
REQUIRE(!reg.add(std::move(dup)).has_value());
// Aggregated list applies the prefix.
auto tools = reg.list_tools();
REQUIRE(tools.has_value());
REQUIRE(tools->size() == 2);
REQUIRE((*tools)[0].name == "alpha.ping");
REQUIRE((*tools)[1].name == "beta.pong");
// Routing splits on the first dot.
auto out = reg.invoke("alpha.ping", R"({"x":1})");
REQUIRE(out.has_value());
REQUIRE(out->status == glove::mcp::tool_call_status::ok);
REQUIRE(out->content == "ran ping");
REQUIRE(a_raw->last_tool_ == "ping");
REQUIRE(a_raw->last_args_.find("\"x\":1") != std::string::npos);
REQUIRE(b_raw->last_tool_.empty());
// Missing prefix or missing extension surface as errors.
REQUIRE(!reg.invoke("noprefix", "{}").has_value());
REQUIRE(!reg.invoke("gamma.foo", "{}").has_value());
return 0;
}
} // namespace
auto main() -> int {
return run();
}