fix: stats flush ensures schema, query flushes pending, add sqlite round-trip test

This commit is contained in:
2026-10-24 00:35:10 +02:00
parent 3c4334cec9
commit 0bd9dcd584
20 changed files with 1468 additions and 15 deletions
+98
View File
@@ -0,0 +1,98 @@
#include <cassert>
#include <cstdio>
#include <thread>
#include <vector>
#include "realtime/expose/claim.h"
#include "realtime/expose/hello.h"
#include "realtime/expose/tunnel.h"
#include "realtime/expose/proxy.h"
#include "realtime/expose/stats.h"
#include <atomic>
#include "realtime/expose/upstream.h"
using namespace molo::realtime::expose;
int main() {
std::string r;
// claim grammar
assert(ParseClaim("/hook1", r) && ParseClaim("example.com/hook1", r));
assert(ParseClaim("example.com", r)->whole_host);
assert(!ParseClaim("/../x", r));
// upstream table: longest prefix wins, concurrent entries
UpstreamTable t;
UpstreamEntry a, b;
assert(ParseUpstream("http://127.0.0.1:3001=/api", r));
auto e1 = ParseUpstream("http://127.0.0.1:3001=/api", r);
auto e2 = ParseUpstream("http://127.0.0.1:3002=/", r);
assert(e1 && e2 && t.Add(*e1, r) && t.Add(*e2, r));
assert(t.Route("/api/u")->port == 3001 && t.Route("/other")->port == 3002);
// hello v1+v2
std::string e;
auto h2 = ParseHello(R"({"v":2,"claim":"/a","upstreams":[{"upstream":"http://127.0.0.1:1","prefix":"/"}],"agent":"t"})", "", e);
auto h1 = ParseHello(R"({"v":1,"claim":"/a","upstream":"8080","agent":"t"})", "", e);
assert(h2 && h1 && h1->upstreams.size() == 1);
// registry: auth, conflict, takeover, concurrent register
ClaimRegistry reg("s1");
auto c = ParseClaim("/app", r);
UpstreamTable u0;
std::shared_ptr<Tunnel> out; std::string d;
assert(reg.Register(*c, Sha256Hex("k1"), std::move(u0), false, out, d) == RegisterResult::kOk);
UpstreamTable u1;
std::shared_ptr<Tunnel> o2;
assert(reg.Register(*c, Sha256Hex("k1"), std::move(u1), false, o2, d) == RegisterResult::kConflict);
UpstreamTable u2;
std::shared_ptr<Tunnel> o3;
assert(reg.Register(*c, Sha256Hex("k1"), std::move(u2), true, o3, d) == RegisterResult::kOk);
assert(reg.Find("", "/app/x") && reg.Find("", "/app/x")->id == o3->id);
// concurrent Find/List under threads
std::vector<std::thread> th;
for (int i = 0; i < 8; ++i) th.emplace_back([&]{ for (int j = 0; j < 200; ++j) { reg.Find("", "/app"); reg.List(); } });
for (auto& t : th) t.join();
// proxy glue: strip prefix, hop-by-hop, stream ids, per-IP cap
assert(StripClaimPrefix("/app", false, "/app/x") == "/x");
assert(StripClaimPrefix("/app", false, "/app") == "/");
assert(StripClaimPrefix("/", false, "/a") == "/a");
assert(StripClaimPrefix("/app", true, "/app/x") == "/app/x");
assert(IsHopByHop("Connection") && IsHopByHop("Transfer-Encoding") && !IsHopByHop("X-Custom"));
assert(ForwardedFor("", "1.2.3.4") == "1.2.3.4");
assert(ForwardedFor("a", "b") == "a, b");
assert(kH2RefusedStream == 0x7 && kH2Cancel == 0x8);
std::atomic<uint32_t> nx{3};
assert(NextProxyStreamId(nx) == 3 && NextProxyStreamId(nx) == 5);
PerIpCounter ipc; for (int i = 0; i < 128; ++i) assert(ipc.Admit("9.9.9.9"));
assert(!ipc.Admit("9.9.9.9")); ipc.Release("9.9.9.9"); assert(ipc.Admit("9.9.9.9"));
// concurrent streams across N upstreams: route from threads
{
UpstreamTable ct;
std::string rr;
ct.Add(*ParseUpstream("http://127.0.0.1:3001=/api", rr), rr);
ct.Add(*ParseUpstream("http://127.0.0.1:3002=/static", rr), rr);
ct.Add(*ParseUpstream("http://127.0.0.1:3003=/", rr), rr);
std::atomic<int> ok{0};
std::vector<std::thread> w;
for (int i = 0; i < 16; ++i) w.emplace_back([&, i]{
const char* p[3] = {"/api/x", "/static/y", "/other"};
auto* e = ct.Route(p[i % 3]);
if (e) ok.fetch_add(1);
std::atomic<uint32_t> sn{3u + (uint32_t)(i * 2)};
(void)NextProxyStreamId(sn);
});
for (auto& th2 : w) th2.join();
assert(ok == 16);
}
// stats: Note/Flush/Query sqlite round-trip (reverse.md 9)
{
std::string db = "/tmp/molo_stats_test.db";
std::remove(db.c_str());
StatsCollector sc;
sc.Configure(MakeStatsStore("sqlite://" + db));
sc.Note("GET", 2048, 4096, 2000);
std::string err, out;
assert(sc.Flush(err));
assert(sc.Query(out, false, err));
assert(out.find("GET") != std::string::npos && out.find("count=1") != std::string::npos);
assert(sc.Query(out, true, err) && out.front() == '[');
std::remove(db.c_str());
}
printf("expose_test PASS\n");
return 0;
}