Files
Adminive/backend/service/tests/httplib_adapter_test.cpp
T
2026-08-08 21:45:29 +08:00

168 lines
7.6 KiB
C++

#include "adminive/adapters/nlohmann_json.hpp"
#include "adminive/adapters/httplib.hpp"
#include "adminive_test.hpp"
#include <atomic>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
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() {
return {};
}
};
}
namespace adminive {
template <>
struct Type_Descriptor<httplib_adapter_test::Config> {
static auto get() {
using T = httplib_adapter_test::Config;
return object<T>("httplib_config", ADMINIVE_FIELD(T, name).editable(), ADMINIVE_FIELD(T, count).editable());
}
};
template <>
struct Type_Descriptor<httplib_adapter_test::Item_Status> {
static auto get() {
using T = httplib_adapter_test::Item_Status;
return object<T>("httplib_item_status", ADMINIVE_FIELD(T, state));
}
};
template <>
struct Type_Descriptor<httplib_adapter_test::Overview_Status> {
static auto get() {
using T = httplib_adapter_test::Overview_Status;
return object<T>("httplib_overview_status", ADMINIVE_FIELD(T, user));
}
};
template <>
struct Type_Descriptor<httplib_adapter_test::Row> {
static auto get() {
using T = httplib_adapter_test::Row;
return object<T>("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<httplib_adapter_test::Row> {
static Table_View table() {
using T = httplib_adapter_test::Row;
return table_view<T>(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<Config> 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<Config, Json> resource(config, "/config", transaction);
adminive::Http_Status_Resource<Overview_Status, Json> 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<Row, Json> 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");
}
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"}};
check_status(client.Post("/config/data", context_error_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);
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<std::uint64_t>();
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.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;
}