#include "HttpControlServer.h" #include #include #include #include namespace { int gFailures = 0; void Expect(bool condition, const std::string& message) { if (condition) return; ++gFailures; std::cerr << "FAILED: " << message << "\n"; } void ExpectEquals(const std::string& actual, const std::string& expected, const std::string& message) { if (actual == expected) return; ++gFailures; std::cerr << "FAILED: " << message << "\n" << "expected: " << expected << "\n" << "actual: " << actual << "\n"; } void TestParsesHttpRequest() { using namespace RenderCadenceCompositor; HttpControlServer::HttpRequest request; const bool parsed = HttpControlServer::ParseHttpRequest( "GET /api/state?cacheBust=1 HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n", request); Expect(parsed, "request parses"); ExpectEquals(request.method, "GET", "method is parsed"); ExpectEquals(request.path, "/api/state", "query string is stripped from path"); ExpectEquals(request.headers["host"], "127.0.0.1", "headers are lower-cased and trimmed"); } void TestStateEndpointUsesCallback() { using namespace RenderCadenceCompositor; HttpControlServer server; HttpControlServerCallbacks callbacks; callbacks.getStateJson = []() { return std::string("{\"ok\":true}"); }; server.SetCallbacksForTest(callbacks); HttpControlServer::HttpRequest request; request.method = "GET"; request.path = "/api/state"; const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request); ExpectEquals(response.status, "200 OK", "state endpoint succeeds"); ExpectEquals(response.contentType, "application/json", "state endpoint is JSON"); ExpectEquals(response.body, "{\"ok\":true}", "state endpoint returns callback JSON"); } void TestWebSocketAcceptKey() { using namespace RenderCadenceCompositor; const std::string acceptKey = HttpControlServer::WebSocketAcceptKey("dGhlIHNhbXBsZSBub25jZQ=="); ExpectEquals(acceptKey, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", "WebSocket accept key matches RFC example"); } void TestRootServesUiIndex() { using namespace RenderCadenceCompositor; const std::filesystem::path root = std::filesystem::temp_directory_path() / "render-cadence-compositor-ui-test"; std::filesystem::create_directories(root); { std::ofstream output(root / "index.html", std::ios::binary); output << "
"; } HttpControlServer server; HttpControlServer::HttpRequest request; request.method = "GET"; request.path = "/"; server.SetRootsForTest(root, std::filesystem::path()); const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request); ExpectEquals(response.status, "200 OK", "root endpoint serves UI index"); ExpectEquals(response.contentType, "text/html", "UI index content type is html"); Expect(response.body.find("root") != std::string::npos, "UI index body is returned"); std::filesystem::remove_all(root); } void TestKnownPostEndpointReturnsActionError() { using namespace RenderCadenceCompositor; HttpControlServer server; HttpControlServer::HttpRequest request; request.method = "POST"; request.path = "/api/layers/add"; request.body = "{\"shaderId\":\"happy-accident\"}"; const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request); ExpectEquals(response.status, "400 Bad Request", "unimplemented post returns OpenAPI action error status"); ExpectEquals(response.contentType, "application/json", "unimplemented post returns JSON"); Expect(response.body.find("\"ok\":false") != std::string::npos, "unimplemented post reports ok false"); Expect(response.body.find("not implemented") != std::string::npos, "unimplemented post reports diagnostic"); } void TestLayerPostEndpointsUseCallbacks() { using namespace RenderCadenceCompositor; HttpControlServer server; HttpControlServerCallbacks callbacks; callbacks.addLayer = [](const std::string& body) { Expect(body.find("solid") != std::string::npos, "add callback receives request body"); return ControlActionResult{ true, std::string() }; }; callbacks.removeLayer = [](const std::string& body) { Expect(body.find("runtime-layer-1") != std::string::npos, "remove callback receives request body"); return ControlActionResult{ false, "Unknown layer id." }; }; server.SetCallbacksForTest(callbacks); HttpControlServer::HttpRequest addRequest; addRequest.method = "POST"; addRequest.path = "/api/layers/add"; addRequest.body = "{\"shaderId\":\"solid\"}"; const HttpControlServer::HttpResponse addResponse = server.RouteRequestForTest(addRequest); ExpectEquals(addResponse.status, "200 OK", "add layer callback success returns 200"); Expect(addResponse.body.find("\"ok\":true") != std::string::npos, "add layer callback returns action success"); HttpControlServer::HttpRequest removeRequest; removeRequest.method = "POST"; removeRequest.path = "/api/layers/remove"; removeRequest.body = "{\"layerId\":\"runtime-layer-1\"}"; const HttpControlServer::HttpResponse removeResponse = server.RouteRequestForTest(removeRequest); ExpectEquals(removeResponse.status, "400 Bad Request", "remove layer callback failure returns 400"); Expect(removeResponse.body.find("Unknown layer id.") != std::string::npos, "remove layer callback returns diagnostic"); } void TestGenericPostCallbackHandlesControlRoutes() { using namespace RenderCadenceCompositor; HttpControlServer server; HttpControlServerCallbacks callbacks; callbacks.executePost = [](const std::string& path, const std::string& body) { ExpectEquals(path, "/api/layers/set-bypass", "generic callback receives route path"); Expect(body.find("runtime-layer-1") != std::string::npos, "generic callback receives request body"); return ControlActionResult{ true, std::string() }; }; server.SetCallbacksForTest(callbacks); HttpControlServer::HttpRequest request; request.method = "POST"; request.path = "/api/layers/set-bypass"; request.body = "{\"layerId\":\"runtime-layer-1\",\"bypass\":true}"; const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request); ExpectEquals(response.status, "200 OK", "generic control callback success returns 200"); Expect(response.body.find("\"ok\":true") != std::string::npos, "generic control callback returns action success"); } void TestUnknownEndpointReturns404() { using namespace RenderCadenceCompositor; HttpControlServer server; HttpControlServer::HttpRequest request; request.method = "GET"; request.path = "/api/nope"; const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request); ExpectEquals(response.status, "404 Not Found", "unknown endpoint returns 404"); } } int main() { TestParsesHttpRequest(); TestStateEndpointUsesCallback(); TestWebSocketAcceptKey(); TestRootServesUiIndex(); TestKnownPostEndpointReturnsActionError(); TestLayerPostEndpointsUseCallbacks(); TestGenericPostCallbackHandlesControlRoutes(); TestUnknownEndpointReturns404(); if (gFailures != 0) { std::cerr << gFailures << " RenderCadenceCompositorHttpControlServer test failure(s).\n"; return 1; } std::cout << "RenderCadenceCompositorHttpControlServer tests passed.\n"; return 0; }