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:
2026-10-23 18:20:04 +02:00
parent 54068f1953
commit 3c4334cec9
1068 changed files with 100899 additions and 2 deletions
+22
View File
@@ -0,0 +1,22 @@
// Route: POST /auth/login
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto ok = f.client->post("/auth/login",
"{\"username\":\"ada\",\"password\":"
"\"correct-horse\"}",
"application/json");
check(ok.status == 200, "good credentials are 200");
check(contains(ok.body, "\"user\":\"ada\""), "username echoed");
auto bad = f.client->post("/auth/login",
"{\"username\":\"ada\",\"password\":\"wrong\"}",
"application/json");
check(bad.status == 403, "bad password is 403");
auto missing = f.client->post("/auth/login", "{\"username\":\"ada\"}",
"application/json");
check(missing.status == 400, "missing field is 400");
return finish("auth/login");
}
+21
View File
@@ -0,0 +1,21 @@
// Route: GET /auth/me (Basic auth)
#include "support/Assert.hpp"
using namespace molo::example::test;
using molo::example::fw::Method;
int main() {
Fixture f;
auto anon = f.client->get("/auth/me");
check(anon.status == 401, "no header is 401");
auto bad = f.client->request(
Method::Get, "/auth/me", "",
{{"Authorization", "Basic YWRhOndyb25n"}});
check(bad.status == 403, "bad password is 403");
auto ok = f.client->request(
Method::Get, "/auth/me", "",
{{"Authorization", "Basic YWRhOmNvcnJlY3QtaG9yc2U="}});
check(ok.status == 200, "good credentials are 200");
check(contains(ok.body, "\"user\":\"ada\""), "identity returned");
return finish("auth/me");
}
+14
View File
@@ -0,0 +1,14 @@
// Route: /cache/lookup (miss then hit)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto miss = f.client->get("/cache/lookup?key=k1");
check(contains(miss.body, "\"hit\":false"), "first lookup misses");
auto hit = f.client->get("/cache/lookup?key=k1");
check(contains(hit.body, "\"hit\":true"), "second lookup hits");
check(contains(hit.body, "computed-for-k1"), "same value served");
return finish("cache/lookup");
}
+14
View File
@@ -0,0 +1,14 @@
// Route: /app1/app1/app1/whoami (application chaining, depth 3)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto res = f.client->get("/app1/app1/app1/whoami");
check(res.status == 200, "status 200");
check(contains(res.body, "\"depth\":3"), "three mounts merged");
auto shallow = f.client->get("/app1/whoami");
check(shallow.status == 404, "partial chain is 404");
return finish("chain/deep");
}
+16
View File
@@ -0,0 +1,16 @@
// Route: GET /cli/ai (AI harness sample, secrets redacted)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto res = f.client->get("/cli/ai?prompt=add+a+route");
check(res.status == 200, "status 200");
check(contains(res.body, "devplace.net/openai/v1"), "devplace base url");
check(contains(res.body, "\"model\":\"molodetz\""), "molodetz model");
auto secret = f.client->get("/cli/ai?prompt=DEVPLACE_API_KEY%3Dabc123");
check(!contains(secret.body, "abc123"), "secret redacted");
check(contains(secret.body, "REDACTED"), "redaction marker present");
return finish("cli/ai");
}
+16
View File
@@ -0,0 +1,16 @@
// Route: GET /embedded/{name} (embedded C++ assets)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto res = f.client->get("/embedded/hello.txt");
check(res.status == 200, "status 200");
check(res.headers.get("X-Asset-Source") == "embedded",
"served from embedded bundle");
check(contains(res.body, "embedded assets"), "bundle content served");
auto missing = f.client->get("/embedded/nope.txt");
check(missing.status == 404, "missing asset is 404");
return finish("embedded/assets");
}
+19
View File
@@ -0,0 +1,19 @@
// Route: /events/fire (typed event bus)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
int seen = 0;
f.services.events->on("demo.ping",
[&](const molo::example::fw::Event&) { ++seen; });
auto fire = f.client->post(
"/events/fire", "{\"type\":\"demo.ping\",\"payload\":\"p1\"}",
"application/json");
check(fire.status == 200, "fire status 200");
check(seen == 1, "side effect: listener ran");
auto list = f.client->get("/events/fire");
check(contains(list.body, "demo.ping"), "delivery recorded");
return finish("events/fire");
}
+26
View File
@@ -0,0 +1,26 @@
// Route: GET /formats/data (content negotiation)
#include "support/Assert.hpp"
using namespace molo::example::test;
using molo::example::fw::Method;
int main() {
Fixture f;
auto def = f.client->get("/formats/data");
check(contains(def.headers.get("Content-Type"), "application/json"),
"default is json");
auto xml = f.client->request(Method::Get, "/formats/data", "",
{{"Accept", "application/xml"}});
check(contains(xml.headers.get("Content-Type"), "application/xml"),
"xml negotiated");
check(contains(xml.body, "<value>42</value>"), "xml body");
auto weighted = f.client->request(
Method::Get, "/formats/data", "",
{{"Accept", "text/html;q=0.2, text/plain;q=0.9"}});
check(contains(weighted.headers.get("Content-Type"), "text/plain"),
"q weights honored");
auto nope = f.client->request(Method::Get, "/formats/data", "",
{{"Accept", "image/png"}});
check(nope.status == 406, "no match is 406");
return finish("formats/data");
}
+21
View File
@@ -0,0 +1,21 @@
// Route: /forms/contact (form render plus validation)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto form = f.client->get("/forms/contact");
check(form.status == 200, "form renders 200");
check(contains(form.body, "<form"), "form markup present");
auto ok = f.client->post("/forms/contact",
"name=ada&email=ada@x.io&message=hi",
"application/x-www-form-urlencoded");
check(ok.status == 200, "valid post is 200");
check(contains(ok.body, "Thanks ada"), "success page names user");
auto bad = f.client->post("/forms/contact", "name=ada",
"application/x-www-form-urlencoded");
check(bad.status == 422, "invalid post is 422");
check(contains(bad.body, "validation_error"), "failures listed");
return finish("forms/contact");
}
+26
View File
@@ -0,0 +1,26 @@
// Route: GET /health (probe plus real-HTTP loopback round-trip)
#include "support/Assert.hpp"
#include "fw/Server.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto res = f.client->get("/health");
check(res.status == 200, "status 200");
check(contains(res.body, "\"status\":\"ok\""), "ok body");
molo::example::fw::Server server(f.app);
check(server.start("127.0.0.1", 0), "loopback server starts");
std::string wire = molo::example::fw::http_roundtrip(
"127.0.0.1", server.port(),
"GET /health HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n");
check(contains(wire, "200 OK"), "real HTTP status line");
check(contains(wire, "\"status\":\"ok\""), "real HTTP body");
server.stop();
auto missing = f.client->get("/no-such-route");
check(missing.status == 404, "unknown route is 404");
return finish("health");
}
+18
View File
@@ -0,0 +1,18 @@
// Route: GET / (index, template inheritance, coverage matrix)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto res = f.client->get("/");
check(res.status == 200, "status 200");
check(contains(res.headers.get("Content-Type"), "text/html"),
"html content type");
check(contains(res.body, "Living example"), "matrix heading");
check(contains(res.body, "/routing/hello/world"), "matrix links a route");
check(contains(res.body, "viewport"), "responsive viewport meta");
check(contains(res.body, "nav-narrow"), "narrow nav dropdown present");
check(res.headers.has("X-Request-ID"), "request id middleware ran");
return finish("index");
}
+19
View File
@@ -0,0 +1,19 @@
// Route: /kv/items
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto missing = f.client->get("/kv/items?key=nope");
check(missing.status == 404, "missing key is 404");
auto bad = f.client->post("/kv/items", "{\"key\":\"a\"}",
"application/json");
check(bad.status == 400, "key without value is 400");
auto put = f.client->post("/kv/items", "{\"key\":\"a\",\"value\":\"b\"}",
"application/json");
check(put.status == 201, "store status 201");
auto get = f.client->get("/kv/items?key=a");
check(contains(get.body, "\"value\":\"b\""), "round trip works");
return finish("kv/items");
}
+15
View File
@@ -0,0 +1,15 @@
// Route: GET /metrics/counters (prometheus render)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto first = f.client->get("/metrics/counters");
check(first.status == 200, "status 200");
check(contains(first.body, "example.page.views 1"), "counter at 1");
f.client->get("/metrics/counters");
check(f.services.metrics->get("example.page.views") == 2.0,
"side effect: counter at 2");
return finish("metrics/counters");
}
+16
View File
@@ -0,0 +1,16 @@
// Routes: GET /openapi.json and GET /docs
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto spec = f.client->get("/openapi.json");
check(spec.status == 200, "spec status 200");
check(contains(spec.body, "\"openapi\":\"3.0.0\""), "openapi version");
check(contains(spec.body, "/health"), "health path listed");
auto docs = f.client->get("/docs");
check(docs.status == 200, "docs status 200");
check(contains(docs.body, "/openapi.json"), "docs link spec");
return finish("openapi/docs");
}
+24
View File
@@ -0,0 +1,24 @@
// Route: /orm/users (dual backends, auto-sync self-healing)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto first = f.client->get("/orm/users?backend=sqlite");
check(first.status == 200, "sqlite status 200");
check(contains(first.body, "\"healed\":true"), "table auto-created");
auto insert = f.client->post("/orm/users?backend=sqlite",
"{\"name\":\"ada\"}", "application/json");
check(insert.status == 201, "insert status 201");
auto widen = f.client->post("/orm/users?backend=sqlite",
"{\"name\":\"g\",\"email\":\"g@x.io\"}",
"application/json");
check(contains(widen.body, "auto-added column email"),
"new column self-healed");
auto pg = f.client->get("/orm/users?backend=postgres");
check(contains(pg.body, "\"backend\":\"postgres\""),
"postgres backend selected");
check(!f.services.sqlite->tables().empty(), "side effect: table exists");
return finish("orm/users");
}
+20
View File
@@ -0,0 +1,20 @@
// Route: /pubsub/publish
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
int seen = 0;
f.services.pubsub->subscribe("news",
[&](const std::string&) { ++seen; });
auto pub = f.client->post("/pubsub/publish",
"{\"topic\":\"news\",\"message\":\"hi\"}",
"application/json");
check(pub.status == 200, "publish status 200");
check(contains(pub.body, "\"delivered\":1"), "one subscriber reached");
check(seen == 1, "side effect: listener ran");
auto hist = f.client->get("/pubsub/publish?topic=news");
check(contains(hist.body, "hi"), "history kept");
return finish("pubsub/publish");
}
+14
View File
@@ -0,0 +1,14 @@
// Route: GET /redirect/here (temporary vs permanent)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto temp = f.client->get("/redirect/here");
check(temp.status == 302, "default is temporary");
check(temp.headers.get("Location") == "/health", "location set");
auto perm = f.client->get("/redirect/here?mode=perm");
check(perm.status == 301, "perm mode is permanent");
return finish("redirect/here");
}
+18
View File
@@ -0,0 +1,18 @@
// Route: /replication/state (append plus since)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto add = f.client->post("/replication/state",
"{\"object\":\"doc-1\",\"data\":\"v1\"}",
"application/json");
check(add.status == 201, "append is 201");
check(contains(add.body, "last-writer-wins"), "conflict rule named");
auto full = f.client->get("/replication/state?since=0");
check(contains(full.body, "doc-1"), "entry replicated");
auto empty = f.client->get("/replication/state?since=99");
check(contains(empty.body, "\"entries\":[]"), "cursor filters");
return finish("replication/state");
}
+12
View File
@@ -0,0 +1,12 @@
// Route: GET /restclient/self (REST client self-call)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto res = f.client->get("/restclient/self");
check(res.status == 200, "status 200");
check(contains(res.body, "\"self_call\":200"), "self call reached 200");
return finish("restclient/self");
}
+16
View File
@@ -0,0 +1,16 @@
// Route: GET /routing/hello/{name}
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto res = f.client->get("/routing/hello/ada");
check(res.status == 200, "status 200");
check(contains(res.body, "\"hello\":\"ada\""), "path param bound");
auto encoded = f.client->get("/routing/hello/a%20b");
check(contains(encoded.body, "\"hello\":\"a b\""), "param url-decoded");
auto missing = f.client->get("/routing/hello");
check(missing.status == 404, "missing segment is 404");
return finish("routing/hello");
}
+14
View File
@@ -0,0 +1,14 @@
// Route: /routing/routers/nested/deep (nested router include)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto res = f.client->get("/routing/routers/nested/deep");
check(res.status == 200, "status 200");
check(contains(res.body, "\"nested\":true"), "nested router reached");
auto shallow = f.client->get("/routing/routers");
check(shallow.status == 404, "prefix alone is 404");
return finish("routing/routers");
}
+21
View File
@@ -0,0 +1,21 @@
// Route: /routing/views/greet (class-based view, GET plus POST)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto get = f.client->get("/routing/views/greet?name=grace");
check(get.status == 200, "GET status 200");
check(contains(get.body, "GreetView"), "view dispatched GET");
check(contains(get.body, "grace"), "query value used");
auto post = f.client->post("/routing/views/greet", "name=ada",
"application/x-www-form-urlencoded");
check(post.status == 200, "POST status 200");
check(contains(post.body, "\"method\":\"POST\""), "view dispatched POST");
auto put = f.client->request(molo::example::fw::Method::Put,
"/routing/views/greet");
check(put.status == 404 || put.status == 405,
"unregistered method rejected");
return finish("routing/views");
}
+17
View File
@@ -0,0 +1,17 @@
// Route: GET /sessions/counter (signed cookie session)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto first = f.client->get("/sessions/counter");
check(first.status == 200, "status 200");
check(contains(first.body, "\"count\":1"), "counter starts at 1");
check(contains(first.body, "\"fresh\":true"), "first visit is fresh");
check(first.headers.has("Set-Cookie"), "session cookie set");
auto second = f.client->get("/sessions/counter");
check(contains(second.body, "\"count\":2"), "jar keeps session");
check(contains(second.body, "\"fresh\":false"), "second visit resumes");
return finish("sessions/counter");
}
+17
View File
@@ -0,0 +1,17 @@
// Route: /socketio/rooms (socket.io skeleton endpoint)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto info = f.client->get("/socketio/rooms");
check(info.status == 200, "status 200");
check(contains(info.body, "socket.io"), "protocol named");
check(contains(info.body, "long-polling"), "fallback named");
auto emit = f.client->post(
"/socketio/rooms", "{\"room\":\"lobby\",\"event\":\"join\"}",
"application/json");
check(contains(emit.body, "\"ack\":true"), "emit acknowledged");
return finish("socketio/rooms");
}
+14
View File
@@ -0,0 +1,14 @@
// Route: GET /sse/events
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto res = f.client->get("/sse/events");
check(res.status == 200, "status 200");
check(contains(res.headers.get("Content-Type"), "text/event-stream"),
"sse content type");
check(contains(res.body, "data: hello"), "event framed");
return finish("sse/events");
}
+17
View File
@@ -0,0 +1,17 @@
// Route: GET /static/{name} (disk static, traversal guard)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto css = f.client->get("/static/app.css");
check(css.status == 200, "css status 200");
check(contains(css.headers.get("Content-Type"), "text/css"),
"css content type");
check(css.headers.has("ETag"), "etag present");
check(contains(css.body, "nav-narrow"), "stylesheet served");
auto missing = f.client->get("/static/nope.css");
check(missing.status == 404, "missing file is 404");
return finish("static/files");
}
+15
View File
@@ -0,0 +1,15 @@
// Route: GET /streaming/feed
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto res = f.client->get("/streaming/feed");
check(res.status == 200, "status 200");
check(res.headers.get("Transfer-Encoding") == "chunked",
"chunked encoding marked");
check(contains(res.body, "chunk-1"), "first chunk present");
check(contains(res.body, "chunk-3"), "last chunk present");
return finish("streaming/feed");
}
@@ -0,0 +1,14 @@
// Route: GET /streaming/passthrough
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto res = f.client->get("/streaming/passthrough?target=/health");
check(res.status == 200, "status 200");
check(contains(res.body, "passthrough from /health"),
"upstream named");
check(contains(res.body, "\"status\":\"ok\""), "upstream body relayed");
return finish("streaming/passthrough");
}
+18
View File
@@ -0,0 +1,18 @@
// Route: /supervisor/tasks (enqueue plus list)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto add = f.client->post("/supervisor/tasks",
"{\"kind\":\"email\",\"payload\":\"{}\" }",
"application/json");
check(add.status == 201, "enqueue is 201");
check(contains(add.body, "\"id\":\"task-"), "task id issued");
auto list = f.client->get("/supervisor/tasks");
check(contains(list.body, "task-1"), "task listed");
check(f.services.tasks->list().size() == 1,
"side effect: queue holds one");
return finish("supervisor/tasks");
}
+14
View File
@@ -0,0 +1,14 @@
// Route: GET /supervisor/workers
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto res = f.client->get("/supervisor/workers");
check(res.status == 200, "status 200");
check(contains(res.body, "\"name\":\"mailer\""), "mailer listed");
check(contains(res.body, "\"name\":\"indexer\""), "indexer listed");
check(contains(res.body, "\"running\":true"), "seed worker runs");
return finish("supervisor/workers");
}
+49
View File
@@ -0,0 +1,49 @@
#pragma once
// Shared test support for the living example suite. Not a test itself: every
// test file owns one main() and uses these helpers for loud assertions.
#include <iostream>
#include <string>
#include "app/App.hpp"
#include "fw/TestClient.hpp"
namespace molo::example::test {
inline int g_failures = 0;
inline void check(bool cond, const std::string& what) {
if (cond) {
std::cout << " ok: " << what << "\n";
} else {
++g_failures;
std::cout << " FAIL: " << what << "\n";
}
}
inline bool contains(const std::string& hay, const std::string& needle) {
return hay.find(needle) != std::string::npos;
}
inline int finish(const std::string& name) {
if (g_failures == 0) {
std::cout << "PASS: " << name << "\n";
return 0;
}
std::cout << "FAIL: " << name << " (" << g_failures << " failures)\n";
return 1;
}
struct Fixture {
molo::example::app::Services services;
std::shared_ptr<molo::example::fw::Application> app;
std::shared_ptr<molo::example::fw::TestClient> client;
Fixture() {
app = molo::example::app::build_application(services);
client = std::make_shared<molo::example::fw::TestClient>(app);
}
};
} // namespace molo::example::test
+16
View File
@@ -0,0 +1,16 @@
// Route: GET /telemetry/trace (span plus correlation id)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto res = f.client->get("/telemetry/trace");
check(res.status == 200, "status 200");
check(contains(res.body, "\"trace\":\"trace-"), "trace id issued");
check(contains(res.body, "\"correlation\":\"req-"),
"correlation id flows");
check(f.services.telemetry->spans().size() == 1,
"side effect: span recorded");
return finish("telemetry/trace");
}
+18
View File
@@ -0,0 +1,18 @@
// Route: GET /templates/page (extends, blocks, for, filters, escape)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto res = f.client->get("/templates/page");
check(res.status == 200, "status 200");
check(contains(res.body, "<title>Templates</title>"),
"base layout applied");
check(contains(res.body, "Author: ADA"), "upper filter ran");
check(contains(res.body, "&lt;b&gt;bold&lt;/b&gt;"),
"default output escaped");
check(contains(res.body, "<b>bold</b>"), "safe opt-out kept markup");
check(contains(res.body, "extends (inheritance)"), "for loop rendered");
return finish("templates/page");
}
+23
View File
@@ -0,0 +1,23 @@
// Route: POST /uploads/file
#include "support/Assert.hpp"
using namespace molo::example::test;
using molo::example::fw::Method;
int main() {
Fixture f;
auto ok = f.client->post("/uploads/file?filename=note.txt", "hello",
"text/plain");
check(ok.status == 201, "allowed upload is 201");
check(contains(ok.body, "\"size\":5"), "size reported");
auto evil = f.client->request(Method::Post,
"/uploads/file?filename=x.bin", "data",
{{"Content-Type", "application/x-evil"}});
check(evil.status == 415, "blocked content type is 415");
auto traversal = f.client->post("/uploads/file?filename=../x.txt", "d",
"text/plain");
check(traversal.status == 400, "unsafe filename is 400");
check(f.services.uploads->list().size() == 1,
"side effect: one upload stored");
return finish("uploads/file");
}
+21
View File
@@ -0,0 +1,21 @@
// Route: POST /validate/user (all-failures validation error)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto ok = f.client->post(
"/validate/user",
"{\"username\":\"ada\",\"email\":\"ada@x.io\",\"age\":\"36\"}",
"application/json");
check(ok.status == 200, "valid model is 200");
auto bad = f.client->post("/validate/user",
"{\"username\":\"ad\",\"age\":\"old\"}",
"application/json");
check(bad.status == 422, "invalid model is 422");
check(contains(bad.body, "username"), "username failure listed");
check(contains(bad.body, "email"), "missing email listed");
check(contains(bad.body, "age"), "bad age listed");
return finish("validate/user");
}
+23
View File
@@ -0,0 +1,23 @@
// Route: /webdav/files/ (PROPFIND, GET, PUT, DELETE)
#include "support/Assert.hpp"
using namespace molo::example::test;
using molo::example::fw::Method;
int main() {
Fixture f;
auto list = f.client->request(Method::Propfind, "/webdav/files/");
check(list.status == 207, "propfind is 207");
check(contains(list.body, "welcome.txt"), "seed file listed");
auto put = f.client->request(Method::Put, "/webdav/files/new.txt",
"new-data", {{"Content-Type", "text/plain"}});
check(put.status == 201, "put is 201");
auto get = f.client->request(Method::Get, "/webdav/files/new.txt");
check(contains(get.body, "new-data"), "round trip works");
auto del =
f.client->request(Method::Delete, "/webdav/files/new.txt");
check(del.status == 200, "delete is 200");
auto gone = f.client->request(Method::Get, "/webdav/files/new.txt");
check(gone.status == 404, "deleted file is 404");
return finish("webdav/files");
}
+24
View File
@@ -0,0 +1,24 @@
// Route: POST /workflow/release (bump, changelog, verify demo)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto feat = f.client->post("/workflow/release",
"commits=feat(routing): add hello",
"application/x-www-form-urlencoded");
check(feat.status == 200, "status 200");
check(contains(feat.body, "\"bump\":\"minor\""), "feat maps to minor");
check(contains(feat.body, "\"version\":\"0.2.0\""), "version bumped");
check(contains(feat.body, "\"green\":true"), "checks green");
auto breaking = f.client->post(
"/workflow/release", "commits=feat!: drop old api",
"application/x-www-form-urlencoded");
check(contains(breaking.body, "\"bump\":\"major\""),
"bang maps to major");
auto bad = f.client->post("/workflow/release", "commits=nope",
"application/x-www-form-urlencoded");
check(bad.status == 400, "malformed commit rejected");
return finish("workflow/release");
}
+15
View File
@@ -0,0 +1,15 @@
// Route: /ws/chat (websocket skeleton endpoint)
#include "support/Assert.hpp"
using namespace molo::example::test;
int main() {
Fixture f;
auto info = f.client->get("/ws/chat");
check(info.status == 200, "status 200");
check(contains(info.body, "\"protocol\":\"websocket\""),
"upgrade contract described");
auto echo = f.client->post("/ws/chat", "ping", "text/plain");
check(contains(echo.body, "\"echo\":\"ping\""), "frame echoed");
return finish("ws/chat");
}