Adapters in src/glue for all ports of agent/ports.h (store, runtime, shell, ui, mcp, skills, plexer, tui, ops, scheduler db), llm log sink and box, repl::ReplHost over agent::Agent, cap-speak, serve-mcp, telegram and tui-local seams. main.cpp dispatches the flags in tai.py main() order. Tests: tests/glue_persist.py (real model, restart persistence, one-shot shell call), tests/glue_flags.py, tests/glue_ports.cpp. Spec: docs/glue.md, gaps: docs/glue-issues.md. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
148 lines
6.3 KiB
C++
148 lines
6.3 KiB
C++
// tests/glue_ports.cpp: unit checks of src/glue on a throwaway TAI_HOME (no model, no network).
|
|
//
|
|
// every port of agent::ports() is wired after AppBundle::open
|
|
// JsonBridge round trips between the module json types
|
|
// StoreAdapter: records, chunks, search, tags, edges, secrets, schedules, sessions through the StorePort
|
|
// SchedulerDbAdapter: claim_once is exclusive
|
|
// StdoutCapture and SysInfoProbe
|
|
//
|
|
// Build (from taicpp/, after `make` so build/src/**/*.o exist) and run, in sh:
|
|
// INC=""; for d in $(find src -type d); do INC="$INC -I$d"; done
|
|
// g++ -std=c++23 -Wall -Wextra -Ibuild/gen -Isrc $INC -I../../moloapp/src tests/glue_ports.cpp
|
|
// $(find build/src -name '*.o') -lsqlite3 -lcurl -lz -pthread -lutil -o build/glue_ports (one line)
|
|
// build/glue_ports
|
|
#include <unistd.h>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
#include "glue/app_bundle.h"
|
|
#include "glue/json_bridge.h"
|
|
#include "glue/stdout_capture.h"
|
|
#include "glue/sysinfo_probe.h"
|
|
|
|
using namespace detz;
|
|
|
|
static int g_checks = 0, g_fails = 0;
|
|
#define CHECK(cond) \
|
|
do { \
|
|
++g_checks; \
|
|
if (!(cond)) { \
|
|
++g_fails; \
|
|
std::fprintf(stderr, "FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); \
|
|
} \
|
|
} while (0)
|
|
|
|
static void json_bridge() {
|
|
store::Json s = store::Json::loads(R"({"a": [1, 2.5, "x"], "b": {"c": null, "d": true}})");
|
|
json::Value v = glue::JsonBridge::value(s);
|
|
CHECK(v.get("a").size() == 3);
|
|
CHECK(v.get("b").get("d").as_bool());
|
|
CHECK(glue::JsonBridge::store(v).dumps() == s.dumps());
|
|
wire::Json w = glue::JsonBridge::wire(v);
|
|
CHECK(w.dumps() == s.dumps());
|
|
misc::Json m = glue::JsonBridge::misc(v);
|
|
CHECK(m.dumps() == s.dumps());
|
|
}
|
|
|
|
static void ports_wired(glue::AppBundle& b) {
|
|
agent::Ports& p = agent::ports();
|
|
CHECK(p.runtime && p.shell && p.ui && p.mcp && p.skills && p.plexer && p.tui && p.ops && p.scheduler_db);
|
|
CHECK(llm::log_sink() != nullptr);
|
|
CHECK(llm::box_port() != nullptr);
|
|
CHECK(b.agent().store == &b.store_port());
|
|
CHECK(!b.agent().messages.is_null() && b.agent().messages.size() >= 1);
|
|
CHECK(b.agent().messages.at(0).get("role").str() == "system");
|
|
// runtime port: a worker store is a second connection on the same vault
|
|
auto worker = p.runtime->open_store();
|
|
CHECK(worker != nullptr);
|
|
CHECK(worker->profile() == b.agent().profile || !worker->profile().empty());
|
|
}
|
|
|
|
static void store_port(glue::AppBundle& b) {
|
|
agent::StorePort& s = b.store_port();
|
|
std::string id = s.add_record("note", "glue title", "alpha beta gamma unique-token-9137", {"glue"}, "");
|
|
CHECK(id.rfind("mem:", 0) == 0);
|
|
s.add_chunks("record", id, {"alpha beta gamma unique-token-9137"}, "");
|
|
agent::Value read = s.read_record(id, 0, 100, "");
|
|
CHECK(read.is_object() && read.get("slice").str().find("unique-token-9137") != std::string::npos);
|
|
agent::Value hits = s.fts_search("unique-token-9137", {}, "", 5);
|
|
CHECK(hits.is_array() && hits.size() >= 1);
|
|
agent::Value found = s.search_records("glue", std::nullopt, {}, 5, "");
|
|
CHECK(found.is_array() && found.size() >= 1);
|
|
CHECK(s.item_tags(id, "").size() >= 1);
|
|
std::string other = s.add_record("note", "second", "second record", {}, "");
|
|
std::string edge = s.add_edge(id, other, agent::Value("linked"), "");
|
|
CHECK(!edge.empty());
|
|
CHECK(s.edges_for(id, "").size() >= 1);
|
|
CHECK(s.delete_record(id, ""));
|
|
CHECK(s.read_record(id, 0, 10, "").is_null());
|
|
|
|
s.save_secret("glue-secret", "value-1", agent::Value(), {}, "");
|
|
CHECK(s.load_secret("glue-secret", "").value_or("") == "value-1");
|
|
CHECK(s.list_secret_infos("").size() >= 1);
|
|
s.delete_secret("glue-secret", "");
|
|
CHECK(!s.load_secret("glue-secret", "").has_value());
|
|
|
|
int64_t sched = s.add_schedule("n", "do it", s.profile(), 0, "2999-01-01T00:00:00+00:00", 600, {});
|
|
CHECK(sched > 0);
|
|
CHECK(s.list_schedules("").size() >= 1);
|
|
s.remove_schedule(sched, "");
|
|
|
|
agent::Value msgs = agent::Value::array();
|
|
agent::Value m1 = agent::Value::object();
|
|
m1.set("role", "user");
|
|
m1.set("content", "hello glue");
|
|
msgs.push(m1);
|
|
s.save_session("glue-prof", msgs, "main", "ctx-test");
|
|
agent::Value back = s.load_session("glue-prof", "main", "ctx-test");
|
|
CHECK(back.size() == 1 && back.at(0).get("content").str() == "hello glue");
|
|
|
|
agent::Value rows = s.sql_rows("SELECT 1, 'x', NULL", agent::Value::array());
|
|
CHECK(rows.size() == 1 && rows.at(0).at(0).as_int() == 1 && rows.at(0).at(1).str() == "x" && rows.at(0).at(2).is_null());
|
|
}
|
|
|
|
static void scheduler_port(glue::AppBundle& b) {
|
|
agent::StorePort& s = b.store_port();
|
|
int64_t id = s.add_schedule("", "run once", s.profile(), 0, "2000-01-01T00:00:00+00:00", 600, {});
|
|
auto due = b.scheduler_db().select_due("2001-01-01T00:00:00+00:00");
|
|
bool seen = false;
|
|
for (const auto& d : due) if (d.id == id) seen = true;
|
|
CHECK(seen);
|
|
CHECK(b.scheduler_db().claim_once(id, "2001-01-01T00:00:00+00:00"));
|
|
CHECK(!b.scheduler_db().claim_once(id, "2001-01-01T00:00:01+00:00"));
|
|
b.scheduler_db().note_result(id, "done", "ok");
|
|
s.remove_schedule(id, "");
|
|
}
|
|
|
|
static void helpers() {
|
|
{
|
|
glue::StdoutCapture cap;
|
|
std::printf("captured-line\n");
|
|
std::string out = cap.finish();
|
|
CHECK(out == "captured-line\n");
|
|
}
|
|
std::string info = glue::SysInfoProbe::collect(std::vector<std::string>{"cpu", "root"});
|
|
CHECK(info.find("cpu: ") != std::string::npos && info.find("root: ") != std::string::npos && info.find("os: ") == std::string::npos);
|
|
}
|
|
|
|
int main() {
|
|
char dir[] = "/tmp/detz-glue-ports-XXXXXX";
|
|
if (!mkdtemp(dir)) return 2;
|
|
setenv("TAI_HOME", dir, 1);
|
|
setenv("HOME", dir, 1);
|
|
setenv("TAI_PASSPHRASE", "", 1);
|
|
json_bridge();
|
|
helpers();
|
|
glue::BundleOptions o;
|
|
o.version = "test";
|
|
o.mode = "test";
|
|
auto bundle = glue::AppBundle::open(o);
|
|
ports_wired(*bundle);
|
|
store_port(*bundle);
|
|
scheduler_port(*bundle);
|
|
bundle.reset();
|
|
CHECK(agent::ports().shell == nullptr); // the bundle clears the ports
|
|
std::printf("%d checks, %d failures\n", g_checks, g_fails);
|
|
return g_fails ? 1 : 0;
|
|
}
|