feat: implement MoloApp plans 01-15 with integration
Full C++23 framework tree: runtime, observability, security, data, app, realtime, supervisor, clients, CLI, example plus enterprise tenancy, audit, operability, governance, supplychain. Postgres via docker compose for tests.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
// AI harness: SSE framing, redaction, plan-then-apply, stub client.
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
|
||||
#include "ai/ai_client.hpp"
|
||||
#include "ai/ai_session.hpp"
|
||||
#include "ai/redactor.hpp"
|
||||
#include "ai/sse_stream_parser.hpp"
|
||||
|
||||
#define CHECK(cond, label) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::cerr << "FAIL: " << label << "\n"; \
|
||||
return 1; \
|
||||
} \
|
||||
std::cout << "PASS: " << label << "\n"; \
|
||||
} while (0)
|
||||
|
||||
int main() {
|
||||
using namespace molo::ai;
|
||||
SseStreamParser parser;
|
||||
parser.feed("data: {\"a\":1}\n\n");
|
||||
parser.feed("data: {\"b\":2}\n\n");
|
||||
auto events = parser.take_events();
|
||||
CHECK(events.size() == 2 && !parser.done(), "two SSE chunks parsed");
|
||||
parser.feed("data: [DONE]\n\n");
|
||||
CHECK(parser.done() && parser.take_events().empty(), "[DONE] ends stream");
|
||||
|
||||
CHECK(Redactor::key_is_secret("DEVPLACE_API_KEY"), "api key detected");
|
||||
CHECK(!Redactor::key_is_secret("username"), "plain key kept");
|
||||
Redactor::Result redacted = Redactor::redact("{\"API_KEY\": \"hunter2\", \"n\": 1}");
|
||||
CHECK(redacted.redacted == 1, "one value redacted");
|
||||
CHECK(redacted.text.find("hunter2") == std::string::npos, "secret gone from text");
|
||||
|
||||
AiSession session("t");
|
||||
std::string error;
|
||||
CHECK(session.append("user", "hi", error), "session append");
|
||||
namespace fs = std::filesystem;
|
||||
fs::path target = fs::temp_directory_path() / "molo-ai-nowrite.txt";
|
||||
fs::remove(target);
|
||||
std::size_t index = session.request_write(target.string(), "data");
|
||||
CHECK(index != static_cast<std::size_t>(-1), "write requested");
|
||||
CHECK(!fs::exists(target), "no file written without confirmation");
|
||||
CHECK(session.confirm_write(index, error), "confirmed write applies: " + error);
|
||||
CHECK(fs::exists(target), "file exists after confirmation");
|
||||
fs::remove(target);
|
||||
|
||||
// Client over stub transport with http base URL (https needs wolfSSL).
|
||||
setenv("DEVPLACE_API_KEY", "test-key", 1);
|
||||
std::string sse =
|
||||
"data: {\"choices\":[{\"delta\":{\"content\":\"Hello\"}}]}\n\n"
|
||||
"data: {\"choices\":[{\"delta\":{\"content\":\"!\"}}]}\n\n"
|
||||
"data: [DONE]\n\n";
|
||||
StubAiTransport stub({sse});
|
||||
AiClientOptions opts;
|
||||
opts.base_url = "http://127.0.0.1:9";
|
||||
opts.model = "molodetz";
|
||||
AiClient client(opts, stub);
|
||||
std::string deltas;
|
||||
std::string full = client.chat_stream({{"user", "hi"}},
|
||||
[&](const std::string& d) { deltas += d; }, error);
|
||||
CHECK(error.empty(), "stub chat ok: " + error);
|
||||
CHECK(full == "Hello!", "transcript assembled");
|
||||
CHECK(stub.seen().size() == 1, "one request sent");
|
||||
CHECK(stub.seen()[0].body_json.find("\"stream\":true") != std::string::npos,
|
||||
"stream flag in request");
|
||||
CHECK(AiClient::extract_delta("{\"choices\":[{\"delta\":{\"content\":\"x\\ny\"}}]}") == "x\ny",
|
||||
"delta unescapes");
|
||||
unsetenv("DEVPLACE_API_KEY");
|
||||
std::cout << "ALL_AI_OK\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Unit checks for CLI parsing, tree, help, writers, exit codes.
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
#include "cli/arg_parser.hpp"
|
||||
#include "cli/command.hpp"
|
||||
#include "cli/command_tree.hpp"
|
||||
#include "cli/exit_code.hpp"
|
||||
#include "cli/flag.hpp"
|
||||
#include "cli/flag_set.hpp"
|
||||
#include "cli/help_renderer.hpp"
|
||||
#include "cli/json_writer.hpp"
|
||||
#include "cli/output.hpp"
|
||||
#include "cli/table_writer.hpp"
|
||||
|
||||
#define CHECK(cond, label) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::cerr << "FAIL: " << label << " (line " << __LINE__ << ")\n"; \
|
||||
return 1; \
|
||||
} \
|
||||
std::cout << "PASS: " << label << "\n"; \
|
||||
} while (0)
|
||||
|
||||
int main() {
|
||||
using namespace molo::cli;
|
||||
CHECK(to_int(ExitCode::Ok) == 0, "exit ok is 0");
|
||||
CHECK(to_int(ExitCode::Runtime) == 1, "exit runtime is 1");
|
||||
CHECK(to_int(ExitCode::Usage) == 2, "exit usage is 2");
|
||||
CHECK(to_int(ExitCode::Unavailable) == 69, "exit unavailable is 69");
|
||||
|
||||
FlagSet flags;
|
||||
flags.add(Flag("port", "p", "listen port", true, "8000"));
|
||||
CHECK(flags.declares("port"), "flagset declares port");
|
||||
CHECK(flags.get("port") == "8000", "flagset default value");
|
||||
flags.set("port", "9000");
|
||||
CHECK(flags.get("port") == "9000", "flagset set/get");
|
||||
|
||||
ArgParser parser;
|
||||
ParsedArgs parsed = parser.parse({"molo", "-vv", "--format", "json", "serve", "--port", "9000"});
|
||||
CHECK(parsed.ok(), "arg parse ok");
|
||||
CHECK(parsed.global.verbose == 2, "repeatable verbose counts");
|
||||
CHECK(parsed.global.format == "json", "format flag parsed");
|
||||
CHECK(parsed.flags["port"] == "9000", "command flag value parsed");
|
||||
ParsedArgs bad = parser.parse({"molo", "--format", "yaml"});
|
||||
CHECK(!bad.ok(), "bad format rejected");
|
||||
|
||||
CommandTree tree;
|
||||
auto serve = std::make_shared<Command>("serve", "run server");
|
||||
serve->usage("[--port N]").example("serve --reload");
|
||||
tree.add(serve);
|
||||
ResolveResult found = tree.resolve({"serve"});
|
||||
CHECK(found.command != nullptr && found.consumed == 1, "tree resolves serve");
|
||||
ResolveResult missing = tree.resolve({"nosuch"});
|
||||
CHECK(missing.command == nullptr, "tree rejects unknown command");
|
||||
|
||||
HelpRenderer help;
|
||||
std::string concise = help.render_concise(*serve, "molo");
|
||||
CHECK(concise.find("usage: molo serve") != std::string::npos, "concise help has usage");
|
||||
std::string full = help.render_full(*serve, "molo", "serve");
|
||||
CHECK(full.find("examples:") != std::string::npos, "full help has examples");
|
||||
std::string root = help.render_root(tree, "molo");
|
||||
CHECK(root.find("--format") != std::string::npos, "root help lists global flags");
|
||||
|
||||
CHECK(JsonWriter::quote("a\"b") == "\"a\\\"b\"", "json escapes quotes");
|
||||
std::map<std::string, std::string> fields;
|
||||
fields["ok"] = "true";
|
||||
fields["n"] = "3";
|
||||
CHECK(JsonWriter::object(fields) == "{\"n\":3,\"ok\":true}", "json object renders");
|
||||
|
||||
TableWriter table;
|
||||
table.header({"a", "bb"});
|
||||
table.row({"1", "2"});
|
||||
std::string rendered = table.render();
|
||||
CHECK(rendered.find("a") != std::string::npos && rendered.find("bb") != std::string::npos,
|
||||
"table renders header");
|
||||
|
||||
Output out(true, 0, false, false);
|
||||
CHECK(out.json_mode() && !out.tty(), "output flags stored");
|
||||
std::cout << "ALL_CLI_OK\n";
|
||||
return 0;
|
||||
}
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/bin/sh
|
||||
# Integration tests through the real `molo` binary. Fails fast on first error.
|
||||
set -u
|
||||
MOLO="$1"
|
||||
case "$MOLO" in
|
||||
/*) ;;
|
||||
*) MOLO="$(pwd)/$MOLO" ;;
|
||||
esac
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
fail() { echo "FAIL: $1"; exit 1; }
|
||||
pass() { echo "PASS: $1"; }
|
||||
|
||||
"$MOLO" version >/dev/null 2>&1 || fail "version exits 0"
|
||||
[ "$("$MOLO" version --format json | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["version"])')" = "0.1.0" ] || fail "version json parses"
|
||||
pass "version text+json"
|
||||
|
||||
"$MOLO" --help >/dev/null 2>&1 || fail "--help exits 0"
|
||||
"$MOLO" nosuch >/dev/null 2>&1 && fail "nosuch must fail"
|
||||
[ "$("$MOLO" nosuch >/dev/null 2>&1; echo $?)" = "2" ] || fail "nosuch exits 2"
|
||||
pass "help + unknown command exit codes"
|
||||
|
||||
# No color codes when piped.
|
||||
"$MOLO" --help | grep -q "$(printf '\033')" && fail "color leaked to pipe"
|
||||
pass "no color when piped"
|
||||
|
||||
# `molo new` scaffolds, re-run without --force fails.
|
||||
(cd "$TMP" && "$MOLO" new shop >/dev/null 2>&1) || fail "molo new fails"
|
||||
[ -f "$TMP/shop/Makefile" ] && [ -f "$TMP/shop/.env.json" ] && [ -f "$TMP/shop/.gitignore" ] && [ -d "$TMP/shop/.git" ] || fail "scaffold files missing"
|
||||
(cd "$TMP" && "$MOLO" new shop >/dev/null 2>&1) && fail "re-run without --force must fail"
|
||||
[ "$(cd "$TMP" && "$MOLO" new shop >/dev/null 2>&1; echo $?)" = "1" ] || fail "re-run exits 1"
|
||||
pass "molo new + force guard"
|
||||
|
||||
# serve refuses prod reload without --allow-prod.
|
||||
mkdir -p "$TMP/prod" && printf '{"MOLO_ENV":"prod"}' > "$TMP/prod/.env.json"
|
||||
[ "$(cd "$TMP/prod" && MOLO_ENV=prod "$MOLO" serve --reload --port 8123 >/dev/null 2>&1; echo $?)" = "2" ] || fail "prod reload must exit 2"
|
||||
pass "serve prod guard"
|
||||
|
||||
# health json parses.
|
||||
"$MOLO" health --format json | python3 -c 'import json,sys; json.load(sys.stdin)' || fail "health json parses"
|
||||
pass "health json"
|
||||
|
||||
# supervisor/task structured output with unavailable backend.
|
||||
"$MOLO" supervisor status --format json | python3 -c 'import json,sys; d=json.load(sys.stdin); assert d["ok"] is False' || fail "supervisor json shape"
|
||||
[ "$("$MOLO" supervisor status --format json >/dev/null 2>&1; echo $?)" = "69" ] || fail "supervisor exits 69"
|
||||
"$MOLO" task inspect --id x --format json | python3 -c 'import json,sys; json.load(sys.stdin)' || fail "task json parses"
|
||||
pass "supervisor/task json + 69"
|
||||
|
||||
# embed fixture twice, byte-identical hpp.
|
||||
mkdir -p "$TMP/fx/css" && printf '<h1>x</h1>' > "$TMP/fx/index.html" && printf 'b{}' > "$TMP/fx/css/a.css"
|
||||
"$MOLO" embed "$TMP/fx" --out "$TMP/e1.cpp" --gzip >/dev/null 2>&1 || fail "embed run 1"
|
||||
"$MOLO" embed "$TMP/fx" --out "$TMP/e2.cpp" --gzip >/dev/null 2>&1 || fail "embed run 2"
|
||||
cmp -s "$TMP/e1.hpp" "$TMP/e2.hpp" || fail "embed hpp byte-identical"
|
||||
grep -q "molo-embed-1" "$TMP/e1.hpp" || fail "embed tool version stamped"
|
||||
pass "embed reproducibility"
|
||||
|
||||
# `molo ai` against a local stub SSE server.
|
||||
export DEVPLACE_API_KEY="stub-key"
|
||||
python3 - "$TMP" <<'EOF' &
|
||||
import http.server, sys
|
||||
tmp = sys.argv[1]
|
||||
BODY = ('data: {"choices":[{"delta":{"content":"Hi"}}]}\n\n'
|
||||
'data: {"choices":[{"delta":{"content":"!"}}]}\n\n'
|
||||
'data: [DONE]\n\n')
|
||||
class H(http.server.BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
ln = int(self.headers.get('Content-Length', 0))
|
||||
self.rfile.read(ln)
|
||||
payload = BODY.encode()
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'text/event-stream')
|
||||
self.send_header('Content-Length', str(len(payload)))
|
||||
self.end_headers()
|
||||
self.wfile.write(payload)
|
||||
def log_message(self, *a):
|
||||
pass
|
||||
http.server.HTTPServer(('127.0.0.1', 18321), H).serve_forever()
|
||||
EOF
|
||||
SRV=$!
|
||||
sleep 1
|
||||
"$MOLO" ai new --base-url http://127.0.0.1:18321 --model molodetz --prompt "say hi" >"$TMP/ai.txt" 2>"$TMP/ai.err" || fail "molo ai new against stub"
|
||||
grep -q "Hi!" "$TMP/ai.txt" || fail "ai transcript assembled"
|
||||
kill $SRV 2>/dev/null
|
||||
pass "ai stub streaming"
|
||||
|
||||
echo "ALL_BINARY_OK"
|
||||
@@ -0,0 +1,48 @@
|
||||
// CSP: deny-first defaults, nonce plumbing, report endpoint caps.
|
||||
#include <iostream>
|
||||
|
||||
#include "csp/csp_builder.hpp"
|
||||
#include "csp/csp_report_handler.hpp"
|
||||
#include "csp/nonce.hpp"
|
||||
|
||||
#define CHECK(cond, label) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::cerr << "FAIL: " << label << "\n"; \
|
||||
return 1; \
|
||||
} \
|
||||
std::cout << "PASS: " << label << "\n"; \
|
||||
} while (0)
|
||||
|
||||
int main() {
|
||||
using namespace molo::csp;
|
||||
CspPolicy def = CspBuilder().build();
|
||||
CHECK(def.report_only(), "report-only by default");
|
||||
CHECK(def.header_name() == "Content-Security-Policy-Report-Only", "report-only header name");
|
||||
CHECK(def.header_value().find("default-src 'none'") != std::string::npos, "deny-first default");
|
||||
CHECK(def.header_value().find("frame-ancestors 'none'") != std::string::npos,
|
||||
"frame-ancestors none");
|
||||
|
||||
std::string nonce = Nonce::generate();
|
||||
CHECK(nonce.size() == 24, "nonce is 16 bytes base64 (24 chars)");
|
||||
CHECK(Nonce::generate() != nonce, "nonces differ per call");
|
||||
CspPolicy with_nonce = CspBuilder().script_nonce(nonce).build();
|
||||
CHECK(with_nonce.header_value().find("'nonce-" + nonce + "'") != std::string::npos,
|
||||
"nonce plumbed into script-src");
|
||||
|
||||
CspPolicy enforced = CspBuilder().enforce().build();
|
||||
CHECK(!enforced.report_only() && enforced.header_name() == "Content-Security-Policy",
|
||||
"enforce() switches header");
|
||||
|
||||
// POST /csp-report
|
||||
CspReportHandler handler;
|
||||
CspReportResult accepted = handler.handle("10.0.0.1", "{\"blocked-uri\":\"x\"}", 1000);
|
||||
CHECK(accepted.status == 202, "report accepted 202");
|
||||
CspReportResult too_big = handler.handle("10.0.0.1", std::string(70000, 'x'), 1000);
|
||||
CHECK(too_big.status == 413, "oversize report 413");
|
||||
for (int i = 0; i < 100; ++i) handler.handle("10.0.0.2", "{}", 2000);
|
||||
CspReportResult limited = handler.handle("10.0.0.2", "{}", 2001);
|
||||
CHECK(limited.status == 429, "report rate limit 429");
|
||||
std::cout << "ALL_CSP_OK\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Dev server: config split enforcement, backoff math, polling watcher.
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
#include "dev/dev_config.hpp"
|
||||
#include "dev/dev_server.hpp"
|
||||
#include "dev/polling_watcher.hpp"
|
||||
#include "dev/reloader.hpp"
|
||||
|
||||
#define CHECK(cond, label) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::cerr << "FAIL: " << label << "\n"; \
|
||||
return 1; \
|
||||
} \
|
||||
std::cout << "PASS: " << label << "\n"; \
|
||||
} while (0)
|
||||
|
||||
int main() {
|
||||
using namespace molo::dev;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
CHECK(DevServer::should_refuse(true, true, false), "prod+reload refused");
|
||||
CHECK(!DevServer::should_refuse(true, true, true), "allow-prod passes");
|
||||
CHECK(!DevServer::should_refuse(false, true, false), "dev reload passes");
|
||||
|
||||
CHECK(Reloader::backoff_for(1) == 1, "backoff 1s");
|
||||
CHECK(Reloader::backoff_for(2) == 2, "backoff 2s");
|
||||
CHECK(Reloader::backoff_for(3) == 4, "backoff 4s");
|
||||
CHECK(Reloader::backoff_for(99) == 30, "backoff holds at 30s");
|
||||
|
||||
fs::path dir = fs::temp_directory_path() / "molo-dev-test";
|
||||
fs::remove_all(dir);
|
||||
fs::create_directories(dir);
|
||||
{ std::ofstream(dir / ".env.json") << "{\"MOLO_ENV\": \"prod\", \"PORT\": 9000}"; }
|
||||
unsetenv("MOLO_ENV");
|
||||
ConfigLoader loader;
|
||||
LoadedConfig prod = loader.load((dir / ".env.json").string(), true, false);
|
||||
CHECK(!prod.ok(), "prod reload without allow-prod errors");
|
||||
LoadedConfig prod_ok = loader.load((dir / ".env.json").string(), true, true);
|
||||
CHECK(prod_ok.ok() && prod_ok.is_prod() && prod_ok.dev.port == 9000, "allow-prod loads prod");
|
||||
setenv("MOLO_ENV", "dev", 1);
|
||||
LoadedConfig dev = loader.load((dir / ".env.json").string(), true, false);
|
||||
CHECK(dev.ok() && !dev.is_prod(), "MOLO_ENV overrides file");
|
||||
unsetenv("MOLO_ENV");
|
||||
|
||||
PollingFileWatcher watcher;
|
||||
std::string error;
|
||||
CHECK(watcher.start({dir.string()}, error), "watcher starts: " + error);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
{ std::ofstream(dir / "t.txt") << "x"; }
|
||||
bool saw = false;
|
||||
for (int i = 0; i < 20 && !saw; ++i) {
|
||||
auto events = watcher.poll(200);
|
||||
for (const auto& event : events) {
|
||||
if (event.path.find("t.txt") != std::string::npos) saw = true;
|
||||
}
|
||||
}
|
||||
CHECK(saw, "watcher sees new file");
|
||||
watcher.stop();
|
||||
fs::remove_all(dir);
|
||||
std::cout << "ALL_DEV_OK\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Embedder: scan caps, emitter reproducibility, bundle reader, sha/mime/gzip.
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "embed/asset_scanner.hpp"
|
||||
#include "embed/embed_emitter.hpp"
|
||||
#include "embed/embedded_bundle.hpp"
|
||||
#include "embed/gzip_stored.hpp"
|
||||
#include "embed/mime.hpp"
|
||||
#include "embed/sha256.hpp"
|
||||
|
||||
#define CHECK(cond, label) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::cerr << "FAIL: " << label << "\n"; \
|
||||
return 1; \
|
||||
} \
|
||||
std::cout << "PASS: " << label << "\n"; \
|
||||
} while (0)
|
||||
|
||||
int main() {
|
||||
using namespace molo::embed;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
CHECK(Sha256::hex_of_string("abc") ==
|
||||
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
||||
"sha256 known vector");
|
||||
CHECK(Mime::of_path("x/app.css") == "text/css", "mime by extension");
|
||||
CHECK(Mime::is_textual("text/html") && !Mime::is_textual("image/png"), "textual detection");
|
||||
|
||||
std::vector<unsigned char> raw = {'h', 'i'};
|
||||
auto gz = GzipStored::compress(raw);
|
||||
CHECK(gz.size() > 10 && gz[0] == 0x1F && gz[1] == 0x8B, "gzip stored magic");
|
||||
|
||||
// Fixture tree.
|
||||
fs::path root = fs::temp_directory_path() / "molo-embed-test";
|
||||
fs::remove_all(root);
|
||||
fs::create_directories(root / "css");
|
||||
{ std::ofstream(root / "index.html") << "<h1>hi</h1>"; }
|
||||
{ std::ofstream(root / "css" / "a.css") << "body{}"; }
|
||||
{ std::ofstream(root / ".hidden") << "skip me"; }
|
||||
|
||||
AssetScanner scanner;
|
||||
ScanResult scanned = scanner.scan(root.string());
|
||||
CHECK(scanned.ok(), "scan ok");
|
||||
CHECK(scanned.files.size() == 2, "dotfile skipped");
|
||||
CHECK(scanned.files[0].rel_path < scanned.files[1].rel_path, "scan sorted");
|
||||
|
||||
// Emit twice: byte-identical.
|
||||
EmitOptions opts;
|
||||
opts.namespc = "molo::assets";
|
||||
opts.gzip = true;
|
||||
EmbedEmitter emitter;
|
||||
std::string error;
|
||||
std::string cpp1 = (root / "a1.cpp").string(), hpp1 = (root / "a1.hpp").string();
|
||||
std::string cpp2 = (root / "a2.cpp").string(), hpp2 = (root / "a2.hpp").string();
|
||||
CHECK(emitter.emit(scanned.files, cpp1, hpp1, opts, error), "emit run 1: " + error);
|
||||
CHECK(emitter.emit(scanned.files, cpp2, hpp2, opts, error), "emit run 2: " + error);
|
||||
auto slurp = [](const std::string& p) {
|
||||
std::ifstream in(p, std::ios::binary);
|
||||
return std::string(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
|
||||
};
|
||||
// Note: cpp embeds its own hpp basename, so compare normalized.
|
||||
std::string c1 = slurp(cpp1), c2 = slurp(cpp2);
|
||||
CHECK(slurp(hpp1) == slurp(hpp2), "hpp byte-identical");
|
||||
CHECK(c1.size() == c2.size(), "cpp same size across runs");
|
||||
|
||||
EmbeddedBundle bundle;
|
||||
EmbeddedAsset asset;
|
||||
asset.path = "index.html";
|
||||
asset.data = {'<', 'h', '1', '>'};
|
||||
asset.sha256 = "deadbeef";
|
||||
asset.mime = "text/html";
|
||||
bundle.add(asset);
|
||||
CHECK(bundle.find("index.html") != nullptr, "bundle find hit");
|
||||
CHECK(bundle.find("nope") == nullptr, "bundle find miss");
|
||||
fs::remove_all(root);
|
||||
std::cout << "ALL_EMBED_OK\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// /healthz: liveness stays 200 unless shutdown is stuck.
|
||||
#include <iostream>
|
||||
|
||||
#include "ops/health_registry.hpp"
|
||||
#include "ops/liveness_check.hpp"
|
||||
#include "ops/ops_routes.hpp"
|
||||
#include "ops/prometheus_exporter.hpp"
|
||||
#include "ops/readiness_check.hpp"
|
||||
#include "ops/startup_gate.hpp"
|
||||
|
||||
#define CHECK(cond, label) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::cerr << "FAIL: " << label << "\n"; \
|
||||
return 1; \
|
||||
} \
|
||||
std::cout << "PASS: " << label << "\n"; \
|
||||
} while (0)
|
||||
|
||||
int main() {
|
||||
using namespace molo::ops;
|
||||
auto live = std::make_shared<LivenessCheck>();
|
||||
HealthRegistry registry;
|
||||
registry.add(live);
|
||||
ReadinessCheck readiness;
|
||||
StartupGate gate;
|
||||
gate.mark_boot_complete();
|
||||
PrometheusExporter exporter;
|
||||
OpsRoutes routes(registry, readiness, gate, exporter);
|
||||
|
||||
OpsResponse hz = routes.healthz();
|
||||
CHECK(hz.status == 200 && hz.body == "{\"status\":\"ok\"}", "healthz 200 ok");
|
||||
live->set_shutdown_stuck(true);
|
||||
OpsResponse stuck = routes.healthz();
|
||||
CHECK(stuck.status == 503, "healthz 503 when shutdown stuck");
|
||||
std::cout << "ALL_HEALTHZ_OK\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// i18n: locale chain, PO loader with plurals, translator fallback, resolution.
|
||||
#include <iostream>
|
||||
|
||||
#include "i18n/locale.hpp"
|
||||
#include "i18n/plural_rule.hpp"
|
||||
#include "i18n/po_loader.hpp"
|
||||
#include "i18n/translator.hpp"
|
||||
|
||||
#define CHECK(cond, label) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::cerr << "FAIL: " << label << "\n"; \
|
||||
return 1; \
|
||||
} \
|
||||
std::cout << "PASS: " << label << "\n"; \
|
||||
} while (0)
|
||||
|
||||
int main() {
|
||||
using namespace molo::i18n;
|
||||
Locale nl = Locale::parse("nl-NL");
|
||||
CHECK(nl.language() == "nl" && nl.region() == "NL", "locale normalizes");
|
||||
auto chain = nl.fallback_chain();
|
||||
CHECK(chain.size() == 3 && chain[0].str() == "nl-NL" && chain[1].str() == "nl" &&
|
||||
chain[2].str() == "en",
|
||||
"fallback chain nl-NL to nl to en");
|
||||
|
||||
CHECK(PluralRule::form_index(1, "en") == 0, "en singular");
|
||||
CHECK(PluralRule::form_index(2, "en") == 1, "en plural");
|
||||
CHECK(PluralRule::form_index(5, "ru") == 2, "ru many form");
|
||||
|
||||
std::string po =
|
||||
"msgid \"Hello\"\nmsgstr \"Hallo\"\n\n"
|
||||
"msgid \"apple\"\nmsgid_plural \"apples\"\nmsgstr[0] \"appel\"\nmsgstr[1] "
|
||||
"\"appels\"\n";
|
||||
PoLoadResult loaded = PoLoader::load_text(po, Locale::parse("nl"));
|
||||
CHECK(loaded.ok(), "po loads");
|
||||
CHECK(loaded.catalog.size() == 2, "po has two entries");
|
||||
|
||||
Translator translator;
|
||||
translator.add_catalog(std::move(loaded.catalog));
|
||||
unsigned long missing_notes = 0;
|
||||
translator.set_missing_sink([&](const std::string&) { ++missing_notes; });
|
||||
CHECK(translator.translate("Hello", Locale::parse("nl-NL")) == "Hallo", "nl-NL translates");
|
||||
CHECK(translator.translate("apple", Locale::parse("nl"), 2) == "appels", "plural form renders");
|
||||
CHECK(translator.translate("apple", Locale::parse("nl"), 1) == "appel",
|
||||
"singular form renders");
|
||||
CHECK(translator.translate("Gone", Locale::parse("nl")) == "Gone", "missing key falls back");
|
||||
CHECK(translator.translate("Gone", Locale::parse("nl")) == "Gone", "missing key repeats msgid");
|
||||
CHECK(missing_notes == 1, "missing key logs once");
|
||||
|
||||
RequestLocale req;
|
||||
req.route_prefix = "nl";
|
||||
req.accept_language = "fr;q=0.9,en;q=0.1";
|
||||
CHECK(Translator::resolve(req).language() == "nl", "route prefix wins");
|
||||
RequestLocale req2;
|
||||
req2.accept_language = "fr;q=0.9,en;q=0.1";
|
||||
CHECK(Translator::resolve(req2).language() == "fr", "q-values honored");
|
||||
std::cout << "ALL_I18N_OK\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// /metrics: Prometheus 0.0.4 format, caps, gzip parity, route templates.
|
||||
#include <iostream>
|
||||
|
||||
#include "ops/prometheus_exporter.hpp"
|
||||
|
||||
#define CHECK(cond, label) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::cerr << "FAIL: " << label << "\n"; \
|
||||
return 1; \
|
||||
} \
|
||||
std::cout << "PASS: " << label << "\n"; \
|
||||
} while (0)
|
||||
|
||||
int main() {
|
||||
using namespace molo::ops;
|
||||
PrometheusExporter exporter;
|
||||
MetricSample sample;
|
||||
sample.name = "molo_requests_total";
|
||||
sample.labels = {{"route", "/users/{id}"}, {"code", "200"}};
|
||||
sample.value = 42;
|
||||
sample.type = MetricType::Counter;
|
||||
sample.help = "total requests";
|
||||
CHECK(exporter.add(sample), "add sample under caps");
|
||||
|
||||
std::string text = exporter.render();
|
||||
CHECK(!text.empty() && text.back() == '\n', "trailing newline on last line");
|
||||
CHECK(text.find("# HELP molo_requests_total") != std::string::npos, "HELP line present");
|
||||
CHECK(text.find("# TYPE molo_requests_total counter") != std::string::npos, "TYPE line present");
|
||||
CHECK(text.find("molo_metrics_dropped_total 0") != std::string::npos, "dropped counter present");
|
||||
|
||||
// Label overflow drops and counts.
|
||||
MetricSample flooded;
|
||||
flooded.name = "flood";
|
||||
for (int i = 0; i < 100; ++i) flooded.labels["l" + std::to_string(i)] = "v";
|
||||
CHECK(!exporter.add(flooded), "64 label cap enforced");
|
||||
CHECK(exporter.dropped() == 1, "overflow bumps dropped counter");
|
||||
|
||||
// gzip variant: valid gzip magic, decodes by length sanity.
|
||||
auto gz = exporter.render_gzip();
|
||||
CHECK(gz.size() > 10 && gz[0] == 0x1F && gz[1] == 0x8B, "gzip magic present");
|
||||
|
||||
CHECK(PrometheusExporter::normalize_route("/users/123") == "/users/{id}",
|
||||
"numeric segment templated");
|
||||
CHECK(PrometheusExporter::normalize_route("/healthz") == "/healthz", "plain path kept");
|
||||
std::cout << "ALL_METRICS_OK\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// /readyz: 200 when deps pass, 503 degraded with failed list otherwise.
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
#include "ops/health_registry.hpp"
|
||||
#include "ops/liveness_check.hpp"
|
||||
#include "ops/ops_routes.hpp"
|
||||
#include "ops/prometheus_exporter.hpp"
|
||||
#include "ops/readiness_check.hpp"
|
||||
#include "ops/startup_gate.hpp"
|
||||
|
||||
#define CHECK(cond, label) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::cerr << "FAIL: " << label << "\n"; \
|
||||
return 1; \
|
||||
} \
|
||||
std::cout << "PASS: " << label << "\n"; \
|
||||
} while (0)
|
||||
|
||||
int main() {
|
||||
using namespace molo::ops;
|
||||
auto live = std::make_shared<LivenessCheck>();
|
||||
HealthRegistry registry;
|
||||
registry.add(live);
|
||||
ReadinessCheck readiness;
|
||||
bool db_up = true;
|
||||
readiness.add_probe({"db", [&]() {
|
||||
HealthResult r;
|
||||
r.state = db_up ? HealthState::Ok : HealthState::Failed;
|
||||
return r;
|
||||
}});
|
||||
readiness.add_probe({"kv", []() {
|
||||
HealthResult r;
|
||||
r.state = HealthState::Ok;
|
||||
return r;
|
||||
}});
|
||||
StartupGate gate;
|
||||
gate.mark_boot_complete();
|
||||
PrometheusExporter exporter;
|
||||
OpsRoutes routes(registry, readiness, gate, exporter);
|
||||
|
||||
OpsResponse ready = routes.readyz();
|
||||
CHECK(ready.status == 200, "readyz 200 when deps pass");
|
||||
db_up = false; // kill the DB
|
||||
OpsResponse degraded = routes.readyz();
|
||||
CHECK(degraded.status == 503, "readyz 503 when db down");
|
||||
CHECK(degraded.body.find("degraded") != std::string::npos, "readyz body says degraded");
|
||||
CHECK(degraded.body.find("\"db\"") != std::string::npos, "readyz names failed dep");
|
||||
OpsResponse hz = routes.healthz();
|
||||
CHECK(hz.status == 200, "healthz stays 200 while readyz degraded");
|
||||
|
||||
// Registry short-circuits slow checks at the timeout.
|
||||
HealthRegistry slow_registry;
|
||||
class Slow : public HealthCheck {
|
||||
public:
|
||||
std::string name() const override { return "slow"; }
|
||||
HealthResult check() override {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
|
||||
return HealthResult();
|
||||
}
|
||||
};
|
||||
slow_registry.add(std::make_shared<Slow>());
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
auto results = slow_registry.run_all(200);
|
||||
auto elapsed = std::chrono::steady_clock::now() - start;
|
||||
CHECK(results.size() == 1 && results[0].result.timed_out, "slow check times out");
|
||||
CHECK(elapsed < std::chrono::seconds(2), "timeout short-circuits under 2s");
|
||||
std::cout << "ALL_READYZ_OK\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// /startupz: 503 until boot completes, then 200.
|
||||
#include <iostream>
|
||||
|
||||
#include "ops/health_registry.hpp"
|
||||
#include "ops/liveness_check.hpp"
|
||||
#include "ops/ops_routes.hpp"
|
||||
#include "ops/prometheus_exporter.hpp"
|
||||
#include "ops/readiness_check.hpp"
|
||||
#include "ops/startup_gate.hpp"
|
||||
|
||||
#define CHECK(cond, label) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::cerr << "FAIL: " << label << "\n"; \
|
||||
return 1; \
|
||||
} \
|
||||
std::cout << "PASS: " << label << "\n"; \
|
||||
} while (0)
|
||||
|
||||
int main() {
|
||||
using namespace molo::ops;
|
||||
auto live = std::make_shared<LivenessCheck>();
|
||||
HealthRegistry registry;
|
||||
registry.add(live);
|
||||
ReadinessCheck readiness;
|
||||
StartupGate gate;
|
||||
PrometheusExporter exporter;
|
||||
OpsRoutes routes(registry, readiness, gate, exporter);
|
||||
|
||||
OpsResponse starting = routes.startupz();
|
||||
CHECK(starting.status == 503, "startupz 503 before boot");
|
||||
gate.mark_boot_complete();
|
||||
OpsResponse booted = routes.startupz();
|
||||
CHECK(booted.status == 200 && booted.body == "{\"status\":\"ok\"}", "startupz 200 after boot");
|
||||
std::cout << "ALL_STARTUPZ_OK\n";
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user