#include "adminive/adapters/nlohmann_json.hpp" #include "adminive/adapters/httplib.hpp" #include "adminive_test.hpp" #include #include #include #include #include namespace httplib_adapter_test { using Json = nlohmann::json; struct Config { std::string name{"initial"}; int count{1}; }; struct Item_Status { std::string state{"ready"}; }; struct Overview_Status { std::string user; }; struct Row { std::string name; bool enabled{true}; int score{}; Item_Status status() { if(name == "status-error") { throw std::runtime_error("item status failed"); } return {}; } }; } namespace adminive { template <> struct Type_Descriptor { static auto get() { using T = httplib_adapter_test::Config; return object("httplib_config", ADMINIVE_FIELD(T, name).editable(), ADMINIVE_FIELD(T, count).editable()); } }; template <> struct Type_Descriptor { static auto get() { using T = httplib_adapter_test::Item_Status; return object("httplib_item_status", ADMINIVE_FIELD(T, state)); } }; template <> struct Type_Descriptor { static auto get() { using T = httplib_adapter_test::Overview_Status; return object("httplib_overview_status", ADMINIVE_FIELD(T, user)); } }; template <> struct Type_Descriptor { static auto get() { using T = httplib_adapter_test::Row; return object("httplib_row", ADMINIVE_FIELD(T, name).creatable().editable(), ADMINIVE_FIELD(T, enabled).creatable().editable(), ADMINIVE_FIELD(T, score).creatable().editable()); } }; template <> struct Type_View_Descriptor { static Table_View table() { using T = httplib_adapter_test::Row; return table_view(column<&T::name>("Name").sort().search(), column<&T::enabled>("Enabled").filter(), column<&T::score>("Score").sort()).reorderable(); } }; } namespace { using httplib_adapter_test::Json; Json body_json(const httplib::Result& result) { ADMINIVE_CHECK(result); return Json::parse(result->body); } void check_status(const httplib::Result& result, int status) { ADMINIVE_CHECK(result); ADMINIVE_CHECK(result->status == status); } } int main() { using namespace httplib_adapter_test; Config config; std::string committed_user; std::string committed_request_id; std::string committed_remote; adminive::Resource_Transaction transaction; transaction.commit = [&](const Config&, const adminive::Request_Context& context) { committed_user = context.user; committed_request_id = context.request_id; committed_remote = context.remote_address; }; adminive::Http_Resource resource(config, "/config", transaction); adminive::Http_Status_Resource status("/status", [](const adminive::Request_Context& context) { if(context.user == "status-error") { throw std::runtime_error("status failed"); } return Overview_Status{context.user}; }, 25); adminive::Http_Collection_Resource collection("/rows", {Row{"alpha", true, 70}, Row{"beta", false, 90}, Row{"gamma", true, 50}}); collection.register_status<&Row::status>(25); httplib::Server server; adminive::Httplib_Bind_Options options; options.context_factory = [](const httplib::Request& request) { const std::string user = request.get_header_value("X-User"); if(user == "context-error") { throw std::runtime_error("context failed"); } if(user == "context-unknown") { throw 7; } adminive::Request_Context context; context.user = user; context.request_id = request.get_header_value("X-Request-Id"); context.remote_address = request.remote_addr; context.attributes.emplace("transport", "httplib"); return context; }; resource.bind(server, options); status.bind(server, options); collection.bind(server); const int port = server.bind_to_any_port("127.0.0.1"); ADMINIVE_CHECK(port > 0); std::thread thread([&] { ADMINIVE_CHECK(server.listen_after_bind()); }); httplib::Client client("127.0.0.1", port); check_status(client.Get("/config/descriptor"), 200); ADMINIVE_CHECK(body_json(client.Get("/config/view")).at("data").at("kind") == "form"); ADMINIVE_CHECK(body_json(client.Get("/config/data")).at("data").at("name") == "initial"); check_status(client.Get("/config/amis"), 200); httplib::Headers headers{{"X-User", "alice"}, {"X-Request-Id", "request-42"}}; const auto update = client.Post("/config/data", headers, R"({"name":"updated","count":9})", "application/json"); check_status(update, 200); ADMINIVE_CHECK(config.name == "updated"); ADMINIVE_CHECK(config.count == 9); ADMINIVE_CHECK(committed_user == "alice"); ADMINIVE_CHECK(committed_request_id == "request-42"); ADMINIVE_CHECK(committed_remote == "127.0.0.1"); httplib::Headers context_error_headers{{"X-User", "context-error"}}; httplib::Headers context_unknown_headers{{"X-User", "context-unknown"}}; check_status(client.Post("/config/data", context_error_headers, R"({"name":"not-applied"})", "application/json"), 500); check_status(client.Post("/config/data", context_unknown_headers, R"({"name":"not-applied"})", "application/json"), 500); ADMINIVE_CHECK(config.name == "updated"); check_status(client.Get("/status/descriptor"), 200); check_status(client.Get("/status/amis"), 200); ADMINIVE_CHECK(body_json(client.Get("/status/data", headers)).at("data").at("user").at("value") == "alice"); httplib::Headers status_error_headers{{"X-User", "status-error"}}; check_status(client.Get("/status/data", status_error_headers), 500); check_status(client.Get("/status/data", context_error_headers), 500); check_status(client.Get("/status/data", context_unknown_headers), 500); ADMINIVE_CHECK(body_json(client.Get("/rows/descriptor")).at("data").at("name") == "httplib_row"); ADMINIVE_CHECK(body_json(client.Get("/rows/view")).at("data").at("kind") == "table"); ADMINIVE_CHECK(body_json(client.Get("/rows?perPage=2&page=1&orderBy=score&orderDir=desc")).at("data").at("items").at(0).at("name") == "beta"); ADMINIVE_CHECK(body_json(client.Get("/rows?name=alp")).at("data").at("total") == 1); ADMINIVE_CHECK(body_json(client.Get("/rows?enabled=true")).at("data").at("total") == 2); check_status(client.Get("/rows?score=90"), 400); check_status(client.Get("/rows?unknown=value"), 400); check_status(client.Get("/rows?orderBy=enabled&orderDir=asc"), 400); check_status(client.Get("/rows?orderBy=score&orderDir=sideways"), 400); const auto created = body_json(client.Post("/rows", R"({"name":"delta","enabled":true,"score":80})", "application/json")); const auto created_id = created.at("data").at("id").get(); ADMINIVE_CHECK(created.at("data").at("name") == "delta"); ADMINIVE_CHECK(body_json(client.Get(("/rows/" + std::to_string(created_id)).c_str())).at("data").at("score") == 80); check_status(client.Put(("/rows/" + std::to_string(created_id)).c_str(), R"({"score":81})", "application/json"), 200); ADMINIVE_CHECK(body_json(client.Get(("/rows/" + std::to_string(created_id)).c_str())).at("data").at("score") == 81); check_status(client.Patch(("/rows/" + std::to_string(created_id)).c_str(), R"({"name":"delta-patched"})", "application/json"), 200); ADMINIVE_CHECK(body_json(client.Get(("/rows/" + std::to_string(created_id) + "/status").c_str())).at("data").at("state").at("value") == "ready"); check_status(client.Patch(("/rows/" + std::to_string(created_id)).c_str(), R"({"name":"status-error"})", "application/json"), 200); check_status(client.Get(("/rows/" + std::to_string(created_id) + "/status").c_str()), 500); check_status(client.Post("/rows/order", R"({"ids":[3,2,1]})", "application/json"), 200); ADMINIVE_CHECK(body_json(client.Get("/rows?orderBy=id&orderDir=asc")).at("data").at("total") == 4); check_status(client.Delete(("/rows/" + std::to_string(created_id)).c_str()), 200); ADMINIVE_CHECK(body_json(client.Get("/rows")).at("data").at("total") == 3); server.stop(); thread.join(); return 0; }