大更新

This commit is contained in:
2026-08-08 19:16:58 +08:00
parent d289f8d6e2
commit ae5dc3eb46
178 changed files with 69427 additions and 525 deletions
+31 -31
View File
@@ -1,7 +1,7 @@
#include "adminive/adapters/boost_pfr.hpp"
#include "adminive/adapters/nlohmann_json.hpp"
#include "adminive/http.hpp"
#include <cassert>
#include "adminive_test.hpp"
#include <string>
#include <tuple>
#include <variant>
@@ -281,21 +281,21 @@ int main() {
};
adminive::Resource_Service<Runtime_Config, Json> service(runtime, "/config", std::move(transaction));
const auto invalid = service.update_response(R"({"nested":{"count":0}})");
assert(invalid.status == 422);
assert(invalid.body.at("field_errors").contains("nested.count"));
ADMINIVE_CHECK(invalid.status == 422);
ADMINIVE_CHECK(invalid.body.at("field_errors").contains("nested.count"));
const auto rejected_commit = service.update_response(R"({"name":"blocked"})");
assert(rejected_commit.status == 422);
assert(rejected_commit.body.at("field_errors").at("name") == "name is blocked");
assert(runtime.name() == "default");
ADMINIVE_CHECK(rejected_commit.status == 422);
ADMINIVE_CHECK(rejected_commit.body.at("field_errors").at("name") == "name is blocked");
ADMINIVE_CHECK(runtime.name() == "default");
const auto valid = service.update_response(R"({"nested":{"count":8},"name":"updated"})");
assert(valid.status == 200);
assert(runtime.count() == 8);
assert(runtime.name() == "updated");
assert(runtime.apply_count() == 1);
assert(transaction_count == 1);
ADMINIVE_CHECK(valid.status == 200);
ADMINIVE_CHECK(runtime.count() == 8);
ADMINIVE_CHECK(runtime.name() == "updated");
ADMINIVE_CHECK(runtime.apply_count() == 1);
ADMINIVE_CHECK(transaction_count == 1);
const auto inherited = adminive::to_descriptor_json<Json, Inherited_Config>();
assert(inherited.at("fields").at(0).at("name") == "alpha");
assert(inherited.at("fields").at(1).at("name") == "beta");
ADMINIVE_CHECK(inherited.at("fields").at(0).at("name") == "alpha");
ADMINIVE_CHECK(inherited.at("fields").at(1).at("name") == "beta");
adminive::Managed_Value<Inherited_Config> managed_inherited;
managed_inherited.field<0>().write([](int& value) {
value = 12;
@@ -303,47 +303,47 @@ int main() {
managed_inherited.field<1>().write([](std::string& value) {
value = "changed";
});
assert(managed_inherited.field<0>().snapshot() == 12);
assert(managed_inherited.field<1>().snapshot() == "changed");
ADMINIVE_CHECK(managed_inherited.field<0>().snapshot() == 12);
ADMINIVE_CHECK(managed_inherited.field<1>().snapshot() == "changed");
Control_Config controls;
const auto control_form = adminive::to_amis_form_schema<Json>(controls, adminive::describe_edit_view<Control_Config>());
assert(control_form.at("body").at(0).at("type") == "input-color");
ADMINIVE_CHECK(control_form.at("body").at(0).at("type") == "input-color");
Device_Config devices;
const auto device_form = adminive::to_amis_form_schema<Json>(devices, adminive::describe_edit_view<Device_Config>());
assert(device_form.at("body").at(0).at("type") == "fieldset");
assert(device_form.at("body").at(0).at("body").at(0).at("type") == "select");
assert(device_form.at("body").at(0).at("body").at(1).at("body").at(2).at("type") == "input-color");
ADMINIVE_CHECK(device_form.at("body").at(0).at("type") == "fieldset");
ADMINIVE_CHECK(device_form.at("body").at(0).at("body").at(0).at("type") == "select");
ADMINIVE_CHECK(device_form.at("body").at(0).at("body").at(1).at("body").at(2).at("type") == "input-color");
const auto polymorphic_patch = Json{{"device", Json{{"type", "network"}, {"host", "10.0.0.1"}, {"port", 7000}, {"baud_rate", 9600}}}};
const auto polymorphic_update = adminive::apply_frontend_patch<Json>(devices, polymorphic_patch);
assert(polymorphic_update.success);
assert(std::holds_alternative<Network_Device>(devices.device));
assert(std::get<Network_Device>(devices.device).host == "10.0.0.1");
ADMINIVE_CHECK(polymorphic_update.success);
ADMINIVE_CHECK(std::holds_alternative<Network_Device>(devices.device));
ADMINIVE_CHECK(std::get<Network_Device>(devices.device).host == "10.0.0.1");
const auto status_descriptor = adminive::to_status_descriptor_json<Json, Overview_Status>();
const auto status_service = adminive::make_amis_status_service<Json>(status_descriptor, "/status", 1000);
assert(status_service.at("body").at("title") == "运行状态");
assert(status_service.at("body").at("body").at("body").size() == 2);
ADMINIVE_CHECK(status_service.at("body").at("title") == "运行状态");
ADMINIVE_CHECK(status_service.at("body").at("body").at("body").size() == 2);
const auto descriptor_without_defaults = adminive::to_descriptor_json<Json, Secret_Config>();
assert(!descriptor_without_defaults.at("fields").at(0).contains("default"));
ADMINIVE_CHECK(!descriptor_without_defaults.at("fields").at(0).contains("default"));
const auto descriptor_with_defaults = adminive::to_descriptor_json_with_defaults<Json, Secret_Config>();
assert(!descriptor_with_defaults.at("fields").at(0).contains("default"));
assert(descriptor_with_defaults.at("fields").at(1).at("default") == "public");
ADMINIVE_CHECK(!descriptor_with_defaults.at("fields").at(0).contains("default"));
ADMINIVE_CHECK(descriptor_with_defaults.at("fields").at(1).at("default") == "public");
Secret_Config secret;
const auto frontend_secret = adminive::to_frontend_json<Json>(secret);
assert(!frontend_secret.contains("secret"));
assert(frontend_secret.at("visible") == "public");
ADMINIVE_CHECK(!frontend_secret.contains("secret"));
ADMINIVE_CHECK(frontend_secret.at("visible") == "public");
bool duplicate_rejected{};
try {
static_cast<void>(adminive::to_descriptor_json<Json, Duplicate_Config>());
} catch(const std::invalid_argument&) {
duplicate_rejected = true;
}
assert(duplicate_rejected);
ADMINIVE_CHECK(duplicate_rejected);
bool invalid_sort_rejected{};
try {
adminive::validate_table_view<Invalid_Sort_Config>(adminive::describe_table_view<Invalid_Sort_Config>());
} catch(const std::invalid_argument&) {
invalid_sort_rejected = true;
}
assert(invalid_sort_rejected);
ADMINIVE_CHECK(invalid_sort_rejected);
return 0;
}
+7 -7
View File
@@ -1,5 +1,5 @@
#include "config_store.hpp"
#include <cassert>
#include "adminive_test.hpp"
#include <filesystem>
#include <string>
int main() {
@@ -9,18 +9,18 @@ int main() {
std::filesystem::remove_all(test_directory);
{
Config_Store store(config_path);
assert(std::filesystem::exists(config_path));
assert(store.radio_service().snapshot().profile_name == "Primary Radio Profile");
ADMINIVE_CHECK(std::filesystem::exists(config_path));
ADMINIVE_CHECK(store.radio_service().snapshot().profile_name == "Primary Radio Profile");
auto updated = store.radio_service().snapshot();
updated.profile_name = "Persisted Profile";
store.persist_radio_service(updated);
assert(store.radio_service().snapshot().profile_name == "Persisted Profile");
assert(!std::filesystem::exists(config_path.string() + ".tmp"));
ADMINIVE_CHECK(store.radio_service().snapshot().profile_name == "Persisted Profile");
ADMINIVE_CHECK(!std::filesystem::exists(config_path.string() + ".tmp"));
}
{
Config_Store store(config_path);
assert(store.radio_service().snapshot().profile_name == "Persisted Profile");
assert(store.default_device_type() == Device_Table_Type::serial);
ADMINIVE_CHECK(store.radio_service().snapshot().profile_name == "Persisted Profile");
ADMINIVE_CHECK(store.default_device_type() == Device_Table_Type::serial);
}
std::filesystem::remove_all(test_directory);
return 0;
+33 -33
View File
@@ -1,6 +1,6 @@
#include "adminive/adapters/drogon.hpp"
#include "adminive/adapters/nlohmann_json.hpp"
#include <cassert>
#include "adminive_test.hpp"
#include <functional>
#include <string>
#include <utility>
@@ -67,39 +67,39 @@ int main() {
});
status.bind(app, options);
}
assert(app.filters("/config/data", drogon::Post).size() == 2);
assert(app.filters("/status/data", drogon::Get).size() == 2);
ADMINIVE_CHECK(app.filters("/config/data", drogon::Post).size() == 2);
ADMINIVE_CHECK(app.filters("/status/data", drogon::Get).size() == 2);
auto request = std::make_shared<drogon::HttpRequest>();
drogon::HttpResponsePtr response;
app.handle("/config/view", drogon::Get, request, [&response](const drogon::HttpResponsePtr& value) {
response = value;
});
assert(response->status == drogon::k200OK);
assert(Json::parse(response->body).at("data").at("kind") == "form");
ADMINIVE_CHECK(response->status == drogon::k200OK);
ADMINIVE_CHECK(Json::parse(response->body).at("data").at("kind") == "form");
request->body = R"({"name":"updated"})";
request->user = "wyc";
response.reset();
app.handle("/config/data", drogon::Post, request, [&response](const drogon::HttpResponsePtr& value) {
response = value;
});
assert(!response);
assert(tasks.size() == 1);
ADMINIVE_CHECK(!response);
ADMINIVE_CHECK(tasks.size() == 1);
tasks.front()();
tasks.erase(tasks.begin());
assert(response);
assert(response->status == drogon::k200OK);
assert(config.name == "updated");
assert(committed_user == "wyc");
ADMINIVE_CHECK(response);
ADMINIVE_CHECK(response->status == drogon::k200OK);
ADMINIVE_CHECK(config.name == "updated");
ADMINIVE_CHECK(committed_user == "wyc");
response.reset();
app.handle("/status/data", drogon::Get, request, [&response](const drogon::HttpResponsePtr& value) {
response = value;
});
assert(!response);
assert(tasks.size() == 1);
ADMINIVE_CHECK(!response);
ADMINIVE_CHECK(tasks.size() == 1);
tasks.front()();
tasks.erase(tasks.begin());
assert(response->status == drogon::k200OK);
assert(Json::parse(response->body).at("data").at("user").at("value") == "wyc");
ADMINIVE_CHECK(response->status == drogon::k200OK);
ADMINIVE_CHECK(Json::parse(response->body).at("data").at("user").at("value") == "wyc");
request->user = "error";
response.reset();
app.handle("/status/data", drogon::Get, request, [&response](const drogon::HttpResponsePtr& value) {
@@ -107,45 +107,45 @@ int main() {
});
tasks.front()();
tasks.erase(tasks.begin());
assert(response->status == drogon::k500InternalServerError);
ADMINIVE_CHECK(response->status == drogon::k500InternalServerError);
response.reset();
app.handle("/status/descriptor", drogon::Get, request, [&response](const drogon::HttpResponsePtr& value) {
response = value;
});
assert(response->status == drogon::k200OK);
ADMINIVE_CHECK(response->status == drogon::k200OK);
adminive::Drogon_Collection_Resource<drogon_test::Config, Json> collection("/configs", {drogon_test::Config{"first"}});
collection.bind(app, options);
response.reset();
app.handle("/configs/view", drogon::Get, request, [&response](const drogon::HttpResponsePtr& value) {
response = value;
});
assert(response->status == drogon::k200OK);
assert(Json::parse(response->body).at("data").at("kind") == "table");
assert(Json::parse(response->body).at("data").at("columns").at(0).at("searchable") == true);
ADMINIVE_CHECK(response->status == drogon::k200OK);
ADMINIVE_CHECK(Json::parse(response->body).at("data").at("kind") == "table");
ADMINIVE_CHECK(Json::parse(response->body).at("data").at("columns").at(0).at("searchable") == true);
request->parameters = {{"page", "1"}, {"perPage", "10"}, {"name", "fir"}};
response.reset();
app.handle("/configs", drogon::Get, request, [&response](const drogon::HttpResponsePtr& value) {
response = value;
});
assert(response->status == drogon::k200OK);
assert(Json::parse(response->body).at("data").at("total") == 1);
ADMINIVE_CHECK(response->status == drogon::k200OK);
ADMINIVE_CHECK(Json::parse(response->body).at("data").at("total") == 1);
response.reset();
app.handle("/configs/1", drogon::Get, request, [&response](const drogon::HttpResponsePtr& value) {
response = value;
});
assert(response->status == drogon::k200OK);
assert(Json::parse(response->body).at("data").at("name") == "first");
ADMINIVE_CHECK(response->status == drogon::k200OK);
ADMINIVE_CHECK(Json::parse(response->body).at("data").at("name") == "first");
request->body = R"({"name":"second"})";
response.reset();
app.handle("/configs", drogon::Post, request, [&response](const drogon::HttpResponsePtr& value) {
response = value;
});
assert(!response);
assert(tasks.size() == 1);
ADMINIVE_CHECK(!response);
ADMINIVE_CHECK(tasks.size() == 1);
tasks.front()();
tasks.erase(tasks.begin());
assert(response->status == drogon::k200OK);
assert(collection.size() == 2);
ADMINIVE_CHECK(response->status == drogon::k200OK);
ADMINIVE_CHECK(collection.size() == 2);
request->body = R"({"name":"changed"})";
response.reset();
app.handle("/configs/2", drogon::Patch, request, [&response](const drogon::HttpResponsePtr& value) {
@@ -153,16 +153,16 @@ int main() {
});
tasks.front()();
tasks.erase(tasks.begin());
assert(response->status == drogon::k200OK);
assert(Json::parse(response->body).at("data").at("name") == "changed");
ADMINIVE_CHECK(response->status == drogon::k200OK);
ADMINIVE_CHECK(Json::parse(response->body).at("data").at("name") == "changed");
response.reset();
app.handle("/configs/1", drogon::Delete, request, [&response](const drogon::HttpResponsePtr& value) {
response = value;
});
tasks.front()();
tasks.erase(tasks.begin());
assert(response->status == drogon::k200OK);
assert(collection.size() == 1);
assert(app.filters("/configs/{1:id}", drogon::Patch).size() == 2);
ADMINIVE_CHECK(response->status == drogon::k200OK);
ADMINIVE_CHECK(collection.size() == 1);
ADMINIVE_CHECK(app.filters("/configs/{1:id}", drogon::Patch).size() == 2);
return 0;
}
+87
View File
@@ -0,0 +1,87 @@
#include "gallery.hpp"
#include "adminive_test.hpp"
#include <string>
#include <thread>
namespace {
using adminive::example::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() {
adminive::example::Component_Gallery gallery;
httplib::Server server;
gallery.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);
const Json docs = body_json(client.Get("/admin/gallery/docs")).at("data");
ADMINIVE_CHECK(docs.at("cases").size() == 14);
ADMINIVE_CHECK(docs.at("capabilities").at("adapters").size() > 5);
ADMINIVE_CHECK(docs.at("examples").at("form_create_view").at("mode") == "create");
std::size_t creatable_fields = 0;
for(const auto& field : docs.at("examples").at("descriptor").at("fields")) {
if(field.at("creatable").get<bool>()) {
++creatable_fields;
}
}
ADMINIVE_CHECK(creatable_fields > 5);
Json field_policies = Json::object();
for(const auto& field : docs.at("examples").at("field_policies")) {
field_policies[field.at("name").get<std::string>()] = field;
}
ADMINIVE_CHECK(field_policies.at("backend_only").at("writable") == true);
ADMINIVE_CHECK(field_policies.at("backend_only").at("editable") == false);
ADMINIVE_CHECK(field_policies.at("backend_only").at("creatable") == false);
ADMINIVE_CHECK(field_policies.at("hidden_backend").at("writable") == true);
ADMINIVE_CHECK(field_policies.at("hidden_backend").at("readable") == false);
ADMINIVE_CHECK(field_policies.at("no_default_text").at("include_default") == false);
ADMINIVE_CHECK(docs.at("examples").at("data").contains("backend_only"));
ADMINIVE_CHECK(!docs.at("examples").at("data").contains("hidden_backend"));
for(const auto& field : docs.at("examples").at("descriptor").at("fields")) {
if(field.at("name") == "no_default_text") {
ADMINIVE_CHECK(!field.contains("default"));
}
}
ADMINIVE_CHECK(!docs.at("examples").at("adapter_data").contains("secret"));
ADMINIVE_CHECK(docs.at("examples").at("reflection_descriptor").at("fields").size() == 2);
ADMINIVE_CHECK(docs.at("examples").at("collection_list_view").at("mode") == "list");
const Json manifest = body_json(client.Get("/admin/gallery/frontend/manifest")).at("data");
ADMINIVE_CHECK(manifest.at("slots").size() == 3);
ADMINIVE_CHECK(manifest.at("slots").at("alpha").at("component_kind") == "amis_schema");
const Json managed = body_json(client.Get("/admin/gallery/managed")).at("data");
ADMINIVE_CHECK(managed.at("lock_count").get<std::size_t>() > 0);
const Json boundaries = body_json(client.Get("/admin/gallery/boundaries")).at("data");
ADMINIVE_CHECK(boundaries.size() == 11);
ADMINIVE_CHECK(boundaries.at(4).at("id") == "manifest-duplicate-slot");
ADMINIVE_CHECK(boundaries.at(4).at("rejected") == true);
check_status(client.Get("/admin/gallery/lab/invalid-filter"), 400);
const Json item_status = body_json(client.Get("/admin/gallery/items/1/status")).at("data");
ADMINIVE_CHECK(item_status.at("state").at("value") == "enabled");
httplib::Headers headers{{"X-Adminive-User", "gallery-test"}, {"X-Request-Id", "request-1"}};
const auto success = client.Post("/admin/gallery/transaction/success", headers, R"({"name":"success"})", "application/json");
check_status(success, 200);
ADMINIVE_CHECK(body_json(success).at("demo").at("events") == Json::array({"prepare", "commit"}));
const auto prepare = client.Post("/admin/gallery/transaction/prepare-reject", headers, R"({"name":"prepare"})", "application/json");
check_status(prepare, 500);
ADMINIVE_CHECK(body_json(prepare).at("demo").at("events") == Json::array({"prepare", "rollback"}));
const auto commit = client.Post("/admin/gallery/transaction/commit-fail", headers, R"({"name":"commit"})", "application/json");
check_status(commit, 500);
const Json commit_json = body_json(commit);
ADMINIVE_CHECK(commit_json.at("demo").at("events") == Json::array({"prepare", "commit", "rollback"}));
ADMINIVE_CHECK(commit_json.at("demo").at("context").at("user") == "gallery-test");
const auto rollback = client.Post("/admin/gallery/transaction/rollback-fail", headers, R"({"name":"rollback"})", "application/json");
check_status(rollback, 500);
ADMINIVE_CHECK(body_json(rollback).at("msg").get<std::string>().find("rollback failed") != std::string::npos);
server.stop();
thread.join();
return 0;
}
@@ -0,0 +1,132 @@
#include "adminive/adapters/nlohmann_json.hpp"
#include "adminive/adapters/httplib.hpp"
#include "adminive_test.hpp"
#include <atomic>
#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 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::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_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;
resource.bind(server, adminive::Httplib_Bind_Options{[](const httplib::Request& request) {
adminive::Request_Context context;
context.user = request.get_header_value("X-User");
context.request_id = request.get_header_value("X-Request-Id");
context.remote_address = request.remote_addr;
context.attributes.emplace("transport", "httplib");
return context;
}});
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");
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?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;
}
+40 -40
View File
@@ -1,7 +1,7 @@
#include "adminive/adapters/nlohmann_json.hpp"
#include "adminive/http.hpp"
#include <atomic>
#include <cassert>
#include "adminive_test.hpp"
#include <string>
#include <thread>
namespace managed_test {
@@ -87,72 +87,72 @@ int main() {
static_assert(Writable_Field<decltype(unsynchronized)>);
static_assert(!Reference_Escaping_Read<decltype(immutable)>);
const auto resolved = guarded.resolved_synchronization();
assert(resolved.lock_count == 2);
assert(resolved.slot(0) != structive::Resolved_Synchronization_View::unsynchronized_slot);
assert(resolved.slot(1) == structive::Resolved_Synchronization_View::unsynchronized_slot);
assert(resolved.slot(2) != structive::Resolved_Synchronization_View::unsynchronized_slot);
assert(resolved.slot(0) != resolved.slot(2));
assert(resolved.slot(3) == structive::Resolved_Synchronization_View::unsynchronized_slot);
ADMINIVE_CHECK(resolved.lock_count == 2);
ADMINIVE_CHECK(resolved.slot(0) != structive::Resolved_Synchronization_View::unsynchronized_slot);
ADMINIVE_CHECK(resolved.slot(1) == structive::Resolved_Synchronization_View::unsynchronized_slot);
ADMINIVE_CHECK(resolved.slot(2) != structive::Resolved_Synchronization_View::unsynchronized_slot);
ADMINIVE_CHECK(resolved.slot(0) != resolved.slot(2));
ADMINIVE_CHECK(resolved.slot(3) == structive::Resolved_Synchronization_View::unsynchronized_slot);
Probe_Lock::reset();
assert(immutable.snapshot() == 2);
assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0);
assert(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0);
ADMINIVE_CHECK(immutable.snapshot() == 2);
ADMINIVE_CHECK(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0);
ADMINIVE_CHECK(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0);
Probe_Lock::reset();
editable.write([](int& value) {
value = 11;
});
assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1);
assert(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0);
ADMINIVE_CHECK(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1);
ADMINIVE_CHECK(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0);
Probe_Lock::reset();
backend.write([](int& value) {
value = 12;
});
assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1);
ADMINIVE_CHECK(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1);
Probe_Lock::reset();
unsynchronized.write([](int& value) {
value = 13;
});
assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0);
assert(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0);
ADMINIVE_CHECK(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0);
ADMINIVE_CHECK(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0);
const auto descriptor = adminive::to_descriptor_json<Json, Section_Config>();
assert(descriptor.at("protocol_version") == 5);
assert(!descriptor.at("fields").at(0).contains("lock_mode"));
assert(descriptor.at("fields").at(0).at("writable") == true);
assert(descriptor.at("fields").at(0).at("synchronized") == true);
assert(descriptor.at("fields").at(1).at("writable") == false);
assert(descriptor.at("fields").at(1).at("synchronized") == false);
assert(descriptor.at("fields").at(2).at("writable") == true);
assert(descriptor.at("fields").at(2).at("editable") == false);
assert(descriptor.at("fields").at(2).at("synchronized") == true);
assert(descriptor.at("fields").at(3).at("writable") == true);
assert(descriptor.at("fields").at(3).at("synchronized") == false);
ADMINIVE_CHECK(descriptor.at("protocol_version") == 5);
ADMINIVE_CHECK(!descriptor.at("fields").at(0).contains("lock_mode"));
ADMINIVE_CHECK(descriptor.at("fields").at(0).at("writable") == true);
ADMINIVE_CHECK(descriptor.at("fields").at(0).at("synchronized") == true);
ADMINIVE_CHECK(descriptor.at("fields").at(1).at("writable") == false);
ADMINIVE_CHECK(descriptor.at("fields").at(1).at("synchronized") == false);
ADMINIVE_CHECK(descriptor.at("fields").at(2).at("writable") == true);
ADMINIVE_CHECK(descriptor.at("fields").at(2).at("editable") == false);
ADMINIVE_CHECK(descriptor.at("fields").at(2).at("synchronized") == true);
ADMINIVE_CHECK(descriptor.at("fields").at(3).at("writable") == true);
ADMINIVE_CHECK(descriptor.at("fields").at(3).at("synchronized") == false);
adminive::Managed_Value<Root_Config, adminive::Mutex_Policy<Probe_Lock>> nested;
auto section = nested.member<&Root_Config::section>();
static_assert(!Writable_Field<decltype(section)>);
Probe_Lock::reset();
assert(section.member<&Section_Config::immutable_value>().snapshot() == 2);
assert(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0);
ADMINIVE_CHECK(section.member<&Section_Config::immutable_value>().snapshot() == 2);
ADMINIVE_CHECK(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0);
Probe_Lock::reset();
section.member<&Section_Config::editable_value>().write([](int& value) {
value = 31;
});
assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1);
ADMINIVE_CHECK(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1);
Probe_Lock::reset();
section.member<&Section_Config::unsynchronized_value>().write([](int& value) {
value = 32;
});
assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0);
ADMINIVE_CHECK(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0);
adminive::Managed_Value<Section_Config, structive::No_Lock_Policy> no_lock;
adminive::Resource_Service<Section_Config, Json> service(no_lock, "/section");
const auto rejected = service.update_response(R"({"backend_value":99,"immutable_value":88})");
assert(rejected.status == 422);
ADMINIVE_CHECK(rejected.status == 422);
const auto update = service.update_response(R"({"editable_value":21,"unsynchronized_value":22})");
assert(update.status == 200);
ADMINIVE_CHECK(update.status == 200);
const auto no_lock_snapshot = no_lock.snapshot();
assert(no_lock_snapshot.editable_value == 21);
assert(no_lock_snapshot.backend_value == 3);
assert(no_lock_snapshot.immutable_value == 2);
assert(no_lock_snapshot.unsynchronized_value == 22);
ADMINIVE_CHECK(no_lock_snapshot.editable_value == 21);
ADMINIVE_CHECK(no_lock_snapshot.backend_value == 3);
ADMINIVE_CHECK(no_lock_snapshot.immutable_value == 2);
ADMINIVE_CHECK(no_lock_snapshot.unsynchronized_value == 22);
adminive::Managed_Value<Concurrent_Config> concurrent;
std::atomic<bool> start{};
auto first_writer = std::thread([&] {
@@ -175,7 +175,7 @@ int main() {
while(!start.load(std::memory_order_acquire)) {}
for(int index = 0; index < 1000; ++index) {
const Json value = adminive::to_json<Json>(concurrent);
assert(value.at("immutable_value") == 7);
ADMINIVE_CHECK(value.at("immutable_value") == 7);
}
});
start.store(true, std::memory_order_release);
@@ -183,8 +183,8 @@ int main() {
second_writer.join();
serializer.join();
const auto concurrent_snapshot = concurrent.snapshot();
assert(concurrent_snapshot.first == 4999);
assert(concurrent_snapshot.second == 4999);
assert(concurrent_snapshot.immutable_value == 7);
ADMINIVE_CHECK(concurrent_snapshot.first == 4999);
ADMINIVE_CHECK(concurrent_snapshot.second == 4999);
ADMINIVE_CHECK(concurrent_snapshot.immutable_value == 7);
return 0;
}
+35 -35
View File
@@ -1,6 +1,6 @@
#include "adminive/adapters/nlohmann_json.hpp"
#include "adminive/http.hpp"
#include <cassert>
#include "adminive_test.hpp"
#include <cstdint>
#include <limits>
#include <optional>
@@ -265,39 +265,39 @@ int main() {
using namespace safety_test;
Root_Config root;
auto update = adminive::apply_frontend_patch<Json>(root, Json{{"child", Json{{"editable_value", 8}}}});
assert(update.success);
assert(root.child.editable_value == 8);
assert(root.child.required_value == 3);
ADMINIVE_CHECK(update.success);
ADMINIVE_CHECK(root.child.editable_value == 8);
ADMINIVE_CHECK(root.child.required_value == 3);
update = adminive::apply_frontend_patch<Json>(root, Json{{"child", Json{{"locked_value", 9}}}});
assert(!update.success);
assert(update.field_errors.contains("child.locked_value"));
assert(root.child.locked_value == 2);
ADMINIVE_CHECK(!update.success);
ADMINIVE_CHECK(update.field_errors.contains("child.locked_value"));
ADMINIVE_CHECK(root.child.locked_value == 2);
Root_Config created;
update = adminive::apply_frontend_create<Json>(created, Json{{"child", Json{{"editable_value", 5}}}});
assert(!update.success);
assert(update.field_errors.contains("child.required_value"));
ADMINIVE_CHECK(!update.success);
ADMINIVE_CHECK(update.field_errors.contains("child.required_value"));
const auto root_form = adminive::to_amis_form_schema<Json>(root, adminive::describe_edit_view<Root_Config>());
assert(root_form.at("body").at(0).at("body").at(2).at("visibleOn") == "${child.editable_value > 0}");
ADMINIVE_CHECK(root_form.at("body").at(0).at("body").at(2).at("visibleOn") == "${child.editable_value > 0}");
Numeric_Config numeric;
update = adminive::apply_frontend_patch<Json>(numeric, Json{{"signed_value", 1.5}});
assert(!update.success);
ADMINIVE_CHECK(!update.success);
update = adminive::apply_frontend_patch<Json>(numeric, Json{{"unsigned_value", -1}});
assert(!update.success);
ADMINIVE_CHECK(!update.success);
update = adminive::apply_frontend_patch<Json>(numeric, Json{{"signed_value", std::numeric_limits<std::uint64_t>::max()}});
assert(!update.success);
ADMINIVE_CHECK(!update.success);
Optional_Config optional;
const auto optional_form = adminive::to_amis_form_schema<Json>(optional, adminive::describe_edit_view<Optional_Config>());
assert(optional_form.at("body").at(0).at("clearable") == true);
assert(optional_form.at("body").at(1).at("clearable") == true);
ADMINIVE_CHECK(optional_form.at("body").at(0).at("clearable") == true);
ADMINIVE_CHECK(optional_form.at("body").at(1).at("clearable") == true);
update = adminive::apply_frontend_patch<Json>(optional, Json{{"count", nullptr}, {"note", nullptr}});
assert(update.success);
assert(!optional.count);
assert(!optional.note);
ADMINIVE_CHECK(update.success);
ADMINIVE_CHECK(!optional.count);
ADMINIVE_CHECK(!optional.note);
Multi_Error_Config multi;
update = adminive::apply_frontend_patch<Json>(multi, Json{{"first", -1}, {"second", -2}});
assert(!update.success);
assert(update.field_errors.contains("first"));
assert(update.field_errors.contains("second"));
ADMINIVE_CHECK(!update.success);
ADMINIVE_CHECK(update.field_errors.contains("first"));
ADMINIVE_CHECK(update.field_errors.contains("second"));
Runtime_Config runtime;
bool rollback_called{};
adminive::Resource_Transaction<Runtime_Model> transaction;
@@ -305,39 +305,39 @@ int main() {
throw std::runtime_error("external commit failed");
};
transaction.rollback = [&rollback_called](const Runtime_Model& original, const adminive::Request_Context&) {
assert(original.value == 1);
ADMINIVE_CHECK(original.value == 1);
rollback_called = true;
};
adminive::Resource_Service<Runtime_Config, Json> service(runtime, "/runtime", std::move(transaction));
const auto response = service.update_response(R"({"value":7})");
assert(response.status == 500);
assert(runtime.value() == 1);
assert(rollback_called);
ADMINIVE_CHECK(response.status == 500);
ADMINIVE_CHECK(runtime.value() == 1);
ADMINIVE_CHECK(rollback_called);
Device_Config devices;
update = adminive::apply_frontend_patch<Json>(devices, Json{{"device", Json{{"type", "serial"}, {"baud_rate", 9600}}}});
assert(update.success);
assert(std::get<Serial_Device>(devices.device).port == "COM1");
assert(std::get<Serial_Device>(devices.device).baud_rate == 9600);
ADMINIVE_CHECK(update.success);
ADMINIVE_CHECK(std::get<Serial_Device>(devices.device).port == "COM1");
ADMINIVE_CHECK(std::get<Serial_Device>(devices.device).baud_rate == 9600);
update = adminive::apply_frontend_patch<Json>(devices, Json{{"device", Json{{"type", "network"}, {"host", "10.0.0.1"}, {"port", 7000}}}});
assert(update.success);
assert(std::holds_alternative<Network_Device>(devices.device));
ADMINIVE_CHECK(update.success);
ADMINIVE_CHECK(std::holds_alternative<Network_Device>(devices.device));
update = adminive::apply_frontend_patch<Json>(devices, Json{{"device", Json{{"type", "token"}, {"token", "abc"}}}});
assert(update.success);
assert(std::holds_alternative<Token_Device>(devices.device));
assert(std::get<Token_Device>(devices.device).token == "abc");
ADMINIVE_CHECK(update.success);
ADMINIVE_CHECK(std::holds_alternative<Token_Device>(devices.device));
ADMINIVE_CHECK(std::get<Token_Device>(devices.device).token == "abc");
bool invalid_name_rejected{};
try {
static_cast<void>(adminive::to_descriptor_json<Json, Invalid_Name_Config>());
} catch(const std::invalid_argument&) {
invalid_name_rejected = true;
}
assert(invalid_name_rejected);
ADMINIVE_CHECK(invalid_name_rejected);
bool conflict_rejected{};
try {
static_cast<void>(adminive::to_descriptor_json<Json, Conflict_Config>());
} catch(const std::invalid_argument&) {
conflict_rejected = true;
}
assert(conflict_rejected);
ADMINIVE_CHECK(conflict_rejected);
return 0;
}
+137 -137
View File
@@ -1,6 +1,6 @@
#include "example_descriptors.hpp"
#include "gallery.hpp"
#include <cassert>
#include "adminive_test.hpp"
#include <string>
#include <string_view>
#include <vector>
@@ -29,10 +29,10 @@ const Json* find_named_node(const Json& value, std::string_view name) {
void assert_flex_items_renderable(const Json& value) {
if(value.is_object()) {
if(value.contains("type") && value.at("type") == "flex") {
assert(value.contains("items"));
ADMINIVE_CHECK(value.contains("items"));
for(const auto& item : value.at("items")) {
assert(item.is_object());
assert(item.contains("type"));
ADMINIVE_CHECK(item.is_object());
ADMINIVE_CHECK(item.contains("type"));
}
}
for(const auto& [key, child] : value.items()) {
@@ -96,172 +96,172 @@ int main() {
using adminive::example::Radio_State;
Radio_State state;
const auto descriptor = adminive::to_descriptor_json<Json, Radio_State>();
assert(descriptor.at("protocol") == "adminive.resource");
assert(descriptor.at("protocol_version") == 5);
assert(descriptor.at("name") == "radio_state");
assert(descriptor.at("fields").size() == 5);
ADMINIVE_CHECK(descriptor.at("protocol") == "adminive.resource");
ADMINIVE_CHECK(descriptor.at("protocol_version") == 5);
ADMINIVE_CHECK(descriptor.at("name") == "radio_state");
ADMINIVE_CHECK(descriptor.at("fields").size() == 5);
const auto& mode_field = descriptor.at("fields").at(0);
assert(mode_field.at("name") == "mode");
assert(mode_field.at("presentation").at("label") == "运行模式");
assert(mode_field.at("value_type") == "enum");
assert(mode_field.at("presentation").at("options").size() == 4);
assert(mode_field.at("presentation").at("options").at(0).at("value") == "receive");
assert(mode_field.at("presentation").at("options").at(2).at("value") == "duplex");
ADMINIVE_CHECK(mode_field.at("name") == "mode");
ADMINIVE_CHECK(mode_field.at("presentation").at("label") == "运行模式");
ADMINIVE_CHECK(mode_field.at("value_type") == "enum");
ADMINIVE_CHECK(mode_field.at("presentation").at("options").size() == 4);
ADMINIVE_CHECK(mode_field.at("presentation").at("options").at(0).at("value") == "receive");
ADMINIVE_CHECK(mode_field.at("presentation").at("options").at(2).at("value") == "duplex");
const auto& port_field = descriptor.at("fields").at(1);
assert(port_field.at("name") == "port");
assert(port_field.at("presentation").at("label") == "监听端口");
assert(port_field.at("minimum") == 1);
assert(port_field.at("maximum") == 65535);
assert(descriptor.at("fields").at(2).at("multiple_of") == 2);
assert(!descriptor.contains("list"));
ADMINIVE_CHECK(port_field.at("name") == "port");
ADMINIVE_CHECK(port_field.at("presentation").at("label") == "监听端口");
ADMINIVE_CHECK(port_field.at("minimum") == 1);
ADMINIVE_CHECK(port_field.at("maximum") == 65535);
ADMINIVE_CHECK(descriptor.at("fields").at(2).at("multiple_of") == 2);
ADMINIVE_CHECK(!descriptor.contains("list"));
const auto table_view = adminive::describe_table_view<Radio_State>();
assert(table_view.columns.size() == 5);
assert(table_view.columns.at(0).field == "mode");
assert(table_view.columns.at(0).label == "模式");
assert(table_view.columns.at(0).sortable);
assert(table_view.columns.at(0).filterable);
assert(table_view.columns.at(2).field == "port");
assert(table_view.columns.at(2).searchable);
assert(table_view.default_order_by == "buffer_count");
ADMINIVE_CHECK(table_view.columns.size() == 5);
ADMINIVE_CHECK(table_view.columns.at(0).field == "mode");
ADMINIVE_CHECK(table_view.columns.at(0).label == "模式");
ADMINIVE_CHECK(table_view.columns.at(0).sortable);
ADMINIVE_CHECK(table_view.columns.at(0).filterable);
ADMINIVE_CHECK(table_view.columns.at(2).field == "port");
ADMINIVE_CHECK(table_view.columns.at(2).searchable);
ADMINIVE_CHECK(table_view.default_order_by == "buffer_count");
const auto table_view_json = adminive::to_view_json<Json, Radio_State>(table_view);
assert(table_view_json.at("protocol") == "adminive.view");
assert(table_view_json.at("kind") == "table");
assert(table_view_json.at("columns").at(0).at("field") == "mode");
assert(table_view_json.at("columns").at(0).at("filterable") == true);
ADMINIVE_CHECK(table_view_json.at("protocol") == "adminive.view");
ADMINIVE_CHECK(table_view_json.at("kind") == "table");
ADMINIVE_CHECK(table_view_json.at("columns").at(0).at("field") == "mode");
ADMINIVE_CHECK(table_view_json.at("columns").at(0).at("filterable") == true);
const auto initial = adminive::to_json<Json>(state);
assert(initial.at("mode") == "receive");
assert(initial.at("port") == 9999);
ADMINIVE_CHECK(initial.at("mode") == "receive");
ADMINIVE_CHECK(initial.at("port") == 9999);
Radio_State assigned;
auto result = adminive::assign_json<Json>(assigned, Json{{"mode", "transmit"}, {"port", 7000}, {"buffer_count", 6}, {"low_watermark", 10}, {"high_watermark", 20}});
assert(result.success);
assert(assigned.mode == Radio_Mode::transmit);
assert(assigned.port.value() == 7000);
ADMINIVE_CHECK(result.success);
ADMINIVE_CHECK(assigned.mode == Radio_Mode::transmit);
ADMINIVE_CHECK(assigned.port.value() == 7000);
result = adminive::apply_json_patch<Json>(assigned, Json{{"mode", "duplex"}, {"port", 7200}});
assert(result.success);
assert(assigned.mode == Radio_Mode::duplex);
assert(assigned.port.value() == 7200);
ADMINIVE_CHECK(result.success);
ADMINIVE_CHECK(assigned.mode == Radio_Mode::duplex);
ADMINIVE_CHECK(assigned.port.value() == 7200);
result = adminive::apply_patch<Json>(state, Json{{"buffer_count", 3}});
assert(!result.success);
assert(state.buffer_count.value() == 2);
ADMINIVE_CHECK(!result.success);
ADMINIVE_CHECK(state.buffer_count.value() == 2);
result = adminive::apply_patch<Json>(state, Json{{"low_watermark", 64}, {"high_watermark", 32}});
assert(!result.success);
assert(state.low_watermark == 0);
ADMINIVE_CHECK(!result.success);
ADMINIVE_CHECK(state.low_watermark == 0);
result = adminive::apply_patch<Json>(state, Json{{"missing", 1}});
assert(!result.success);
assert(result.field_errors.contains("missing"));
ADMINIVE_CHECK(!result.success);
ADMINIVE_CHECK(result.field_errors.contains("missing"));
Radio_State created;
result = adminive::apply_frontend_create<Json>(created, Json{{"mode", "maintenance"}, {"port", 7300}, {"buffer_count", 10}, {"low_watermark", 12}, {"high_watermark", 24}});
assert(result.success);
assert(created.mode == Radio_Mode::maintenance);
ADMINIVE_CHECK(result.success);
ADMINIVE_CHECK(created.mode == Radio_Mode::maintenance);
result = adminive::apply_frontend_create<Json>(created, Json{{"port", 7300}});
assert(!result.success);
assert(result.field_errors.contains("mode"));
assert(result.field_errors.contains("buffer_count"));
ADMINIVE_CHECK(!result.success);
ADMINIVE_CHECK(result.field_errors.contains("mode"));
ADMINIVE_CHECK(result.field_errors.contains("buffer_count"));
const auto form_view = adminive::edit_form<Radio_State>().submit("Confirm Changes");
const auto form = adminive::to_amis_form_schema<Json>(state, form_view, "/admin/radio_state");
assert(form.at("type") == "form");
assert(form.at("body").size() == 5);
assert(form.at("body").at(0).at("type") == "select");
assert(form.at("body").at(0).at("options").size() == 4);
assert(form.at("body").at(1).at("type") == "input-number");
assert(form.at("body").at(2).at("step") == 2);
assert(form.at("actions").at(1).at("label") == "Confirm Changes");
ADMINIVE_CHECK(form.at("type") == "form");
ADMINIVE_CHECK(form.at("body").size() == 5);
ADMINIVE_CHECK(form.at("body").at(0).at("type") == "select");
ADMINIVE_CHECK(form.at("body").at(0).at("options").size() == 4);
ADMINIVE_CHECK(form.at("body").at(1).at("type") == "input-number");
ADMINIVE_CHECK(form.at("body").at(2).at("step") == 2);
ADMINIVE_CHECK(form.at("actions").at(1).at("label") == "Confirm Changes");
const auto crud = adminive::to_amis_table_schema<Json, Radio_State>("/admin/radio_states", table_view);
assert(crud.at("type") == "page");
ADMINIVE_CHECK(crud.at("type") == "page");
const auto& crud_body = crud.at("body").at(0);
assert(crud_body.at("type") == "crud");
assert(crud_body.at("columns").size() == 7);
assert(crud_body.at("columns").at(1).at("name") == "mode");
assert(crud_body.at("columns").at(1).at("type") == "mapping");
assert(crud_body.at("columns").at(1).at("map").at("duplex") == "双工");
assert(crud_body.at("columns").at(1).at("filterable") == true);
assert(crud_body.at("columns").at(3).at("name") == "port");
assert(crud_body.at("columns").at(3).at("searchable") == true);
ADMINIVE_CHECK(crud_body.at("type") == "crud");
ADMINIVE_CHECK(crud_body.at("columns").size() == 7);
ADMINIVE_CHECK(crud_body.at("columns").at(1).at("name") == "mode");
ADMINIVE_CHECK(crud_body.at("columns").at(1).at("type") == "mapping");
ADMINIVE_CHECK(crud_body.at("columns").at(1).at("map").at("duplex") == "双工");
ADMINIVE_CHECK(crud_body.at("columns").at(1).at("filterable") == true);
ADMINIVE_CHECK(crud_body.at("columns").at(3).at("name") == "port");
ADMINIVE_CHECK(crud_body.at("columns").at(3).at("searchable") == true);
const auto& create_form = crud_body.at("headerToolbar").at(2).at("dialog").at("body");
assert(create_form.at("actions").at(1).at("label") == "新增");
ADMINIVE_CHECK(create_form.at("actions").at(1).at("label") == "新增");
const auto status_descriptor = adminive::to_status_descriptor_json<Json, Radio_Item_Status>();
assert(status_descriptor.at("protocol") == "adminive.status");
assert(status_descriptor.at("fields").size() == 3);
assert(status_descriptor.at("fields").at(0).at("name") == "operating_state");
assert(status_descriptor.at("fields").at(0).at("value_type") == "enum");
assert(status_descriptor.at("fields").at(0).at("color") == true);
ADMINIVE_CHECK(status_descriptor.at("protocol") == "adminive.status");
ADMINIVE_CHECK(status_descriptor.at("fields").size() == 3);
ADMINIVE_CHECK(status_descriptor.at("fields").at(0).at("name") == "operating_state");
ADMINIVE_CHECK(status_descriptor.at("fields").at(0).at("value_type") == "enum");
ADMINIVE_CHECK(status_descriptor.at("fields").at(0).at("color") == true);
const auto first_status = adminive::to_status_json<Json>(state.status());
const auto second_status = adminive::to_status_json<Json>(state.status());
assert(first_status.at("operating_state").contains("color"));
assert(!first_status.at("operating_state").at("color").get<std::string>().empty());
assert(first_status.at("refresh_sequence").at("value") == 1);
assert(second_status.at("refresh_sequence").at("value") == 2);
ADMINIVE_CHECK(first_status.at("operating_state").contains("color"));
ADMINIVE_CHECK(!first_status.at("operating_state").at("color").get<std::string>().empty());
ADMINIVE_CHECK(first_status.at("refresh_sequence").at("value") == 1);
ADMINIVE_CHECK(second_status.at("refresh_sequence").at("value") == 2);
const auto overview_descriptor = adminive::to_status_descriptor_json<Json, adminive::example::Server_Status>();
const auto status_crud = adminive::to_amis_table_schema<Json, Radio_State>("/admin/radio_states", table_view, &status_descriptor, 2000, &overview_descriptor, "/admin/status", 2000);
assert(status_crud.at("body").is_array());
assert(status_crud.at("body").at(0).at("type") == "service");
assert(status_crud.at("body").at(1).at("columns").size() == 8);
ADMINIVE_CHECK(status_crud.at("body").is_array());
ADMINIVE_CHECK(status_crud.at("body").at(0).at("type") == "service");
ADMINIVE_CHECK(status_crud.at("body").at(1).at("columns").size() == 8);
const auto& status_column = status_crud.at("body").at(1).at("columns").at(6);
assert(status_column.at("label") == "实时状态");
assert(status_column.at("popOver").at("trigger") == "click");
assert(status_column.at("popOver").at("body").at("api").at("url") == "/admin/radio_states/${id}/status");
ADMINIVE_CHECK(status_column.at("label") == "实时状态");
ADMINIVE_CHECK(status_column.at("popOver").at("trigger") == "click");
ADMINIVE_CHECK(status_column.at("popOver").at("body").at("api").at("url") == "/admin/radio_states/${id}/status");
adminive::example::Http_Collection_Resource<Radio_State> collection_resource("/admin/radio_states", std::vector<Radio_State>{state});
collection_resource.register_overview_status<adminive::example::Server_Status>("/admin/status", 2000);
collection_resource.register_status<&Radio_State::status>(3000);
const auto registered_schema = collection_resource.amis_schema();
const auto& registered_status_column = registered_schema.at("body").at(1).at("columns").at(6);
assert(registered_status_column.at("popOver").at("body").at("interval") == 3000);
ADMINIVE_CHECK(registered_status_column.at("popOver").at("body").at("interval") == 3000);
Radio_Service_Config service_config;
const auto service_descriptor = adminive::to_descriptor_json<Json, Radio_Service_Config>();
assert(service_descriptor.at("fields").size() == 7);
assert(service_descriptor.at("fields").at(0).at("value_type") == "string");
assert(service_descriptor.at("fields").at(1).at("value_type") == "enum");
assert(service_descriptor.at("fields").at(2).at("value_type") == "integer");
assert(service_descriptor.at("fields").at(3).at("value_type") == "number");
assert(service_descriptor.at("fields").at(4).at("value_type") == "object");
assert(service_descriptor.at("fields").at(4).at("children").size() == 3);
assert(service_descriptor.at("fields").at(5).at("presentation").at("visible_on") == "${$self.mode == 'duplex'}");
assert(service_descriptor.at("fields").at(6).at("children").at(1).at("presentation").at("control") == "date");
assert(service_descriptor.at("fields").at(6).at("children").at(2).at("presentation").at("control") == "color");
ADMINIVE_CHECK(service_descriptor.at("fields").size() == 7);
ADMINIVE_CHECK(service_descriptor.at("fields").at(0).at("value_type") == "string");
ADMINIVE_CHECK(service_descriptor.at("fields").at(1).at("value_type") == "enum");
ADMINIVE_CHECK(service_descriptor.at("fields").at(2).at("value_type") == "integer");
ADMINIVE_CHECK(service_descriptor.at("fields").at(3).at("value_type") == "number");
ADMINIVE_CHECK(service_descriptor.at("fields").at(4).at("value_type") == "object");
ADMINIVE_CHECK(service_descriptor.at("fields").at(4).at("children").size() == 3);
ADMINIVE_CHECK(service_descriptor.at("fields").at(5).at("presentation").at("visible_on") == "${$self.mode == 'duplex'}");
ADMINIVE_CHECK(service_descriptor.at("fields").at(6).at("children").at(1).at("presentation").at("control") == "date");
ADMINIVE_CHECK(service_descriptor.at("fields").at(6).at("children").at(2).at("presentation").at("control") == "color");
const auto service_view = adminive::describe_edit_view<Radio_Service_Config>();
const auto service_view_json = adminive::to_view_json<Json, Radio_Service_Config>(service_view);
assert(service_view_json.at("body").at("kind") == "tabs");
assert(service_view_json.at("body").at("children").size() == 3);
ADMINIVE_CHECK(service_view_json.at("body").at("kind") == "tabs");
ADMINIVE_CHECK(service_view_json.at("body").at("children").size() == 3);
const auto service_form = adminive::to_amis_form_schema<Json>(service_config, service_view, "/admin/config/radio/data");
assert(service_form.at("body").at(0).at("type") == "tabs");
ADMINIVE_CHECK(service_form.at("body").at(0).at("type") == "tabs");
const Json* profile_control = find_named_node(service_form, "profile_name");
const Json* mode_control = find_named_node(service_form, "mode");
const Json* worker_control = find_named_node(service_form, "worker_count");
const Json* date_control = find_named_node(service_form, "appearance.effective_date");
const Json* color_control = find_named_node(service_form, "appearance.accent_color");
assert(profile_control && profile_control->at("type") == "input-text");
assert(mode_control && mode_control->at("type") == "select");
assert(worker_control && worker_control->at("type") == "input-number");
assert(date_control && date_control->at("type") == "input-date");
assert(color_control && color_control->at("type") == "input-color");
assert(service_form.at("actions").at(1).at("label") == "确认修改");
ADMINIVE_CHECK(profile_control && profile_control->at("type") == "input-text");
ADMINIVE_CHECK(mode_control && mode_control->at("type") == "select");
ADMINIVE_CHECK(worker_control && worker_control->at("type") == "input-number");
ADMINIVE_CHECK(date_control && date_control->at("type") == "input-date");
ADMINIVE_CHECK(color_control && color_control->at("type") == "input-color");
ADMINIVE_CHECK(service_form.at("actions").at(1).at("label") == "确认修改");
result = adminive::apply_frontend_patch<Json>(service_config, Json{{"profile_name", "Duplex Profile"}, {"mode", "duplex"}, {"worker_count", 8}, {"receive_gain", 2.5}, {"primary_endpoint", Json{{"enabled", true}, {"host", "10.0.0.1"}, {"port", 9200}}}, {"backup_endpoint", Json{{"enabled", true}, {"host", "10.0.0.2"}, {"port", 9201}}}, {"appearance", Json{{"panel_title", "Duplex Radio"}, {"effective_date", "2026-08-07"}, {"accent_color", "#16a34a"}}}});
assert(result.success);
assert(service_config.mode == Radio_Mode::duplex);
assert(service_config.primary_endpoint.host == "10.0.0.1");
assert(service_config.appearance.accent_color == "#16a34a");
ADMINIVE_CHECK(result.success);
ADMINIVE_CHECK(service_config.mode == Radio_Mode::duplex);
ADMINIVE_CHECK(service_config.primary_endpoint.host == "10.0.0.1");
ADMINIVE_CHECK(service_config.appearance.accent_color == "#16a34a");
Alert_Config alert_config;
const auto alert_view = adminive::describe_edit_view<Alert_Config>();
const auto alert_view_json = adminive::to_view_json<Json, Alert_Config>(alert_view);
assert(alert_view_json.at("body").at("kind") == "vertical");
ADMINIVE_CHECK(alert_view_json.at("body").at("kind") == "vertical");
const auto alert_form = adminive::to_amis_form_schema<Json>(alert_config, alert_view, "/admin/config/alerts/data");
assert(find_named_node(alert_form, "rule_name"));
assert(find_named_node(alert_form, "effective_date")->at("type") == "input-date");
assert(find_named_node(alert_form, "highlight_color")->at("type") == "input-color");
ADMINIVE_CHECK(find_named_node(alert_form, "rule_name"));
ADMINIVE_CHECK(find_named_node(alert_form, "effective_date")->at("type") == "input-date");
ADMINIVE_CHECK(find_named_node(alert_form, "highlight_color")->at("type") == "input-color");
const Json* webhook_group = find_titled_node(alert_form, "Webhook 端点");
assert(webhook_group);
assert(webhook_group->at("visibleOn") == "${enabled && channel == 'webhook'}");
ADMINIVE_CHECK(webhook_group);
ADMINIVE_CHECK(webhook_group->at("visibleOn") == "${enabled && channel == 'webhook'}");
adminive::example::Http_Resource<Radio_Service_Config> service_resource(service_config, "/admin/config/radio");
const auto resource_view = service_resource.view_schema();
assert(resource_view.at("protocol") == "adminive.view");
assert(resource_view.at("kind") == "form");
ADMINIVE_CHECK(resource_view.at("protocol") == "adminive.view");
ADMINIVE_CHECK(resource_view.at("kind") == "form");
const auto resource_form = service_resource.amis_schema();
assert(resource_form.at("api").at("method") == "post");
assert(resource_form.at("api").at("url") == "/admin/config/radio/data");
assert(resource_form.at("actions").at(1).at("label") == "确认修改");
ADMINIVE_CHECK(resource_form.at("api").at("method") == "post");
ADMINIVE_CHECK(resource_form.at("api").at("url") == "/admin/config/radio/data");
ADMINIVE_CHECK(resource_form.at("actions").at(1).at("label") == "确认修改");
adminive::example::Component_Gallery gallery;
const auto gallery_schema = gallery.amis_schema();
assert(gallery_schema.at("type") == "panel");
ADMINIVE_CHECK(gallery_schema.at("type") == "panel");
assert_flex_items_renderable(gallery_schema);
const Json* automatic_control = find_named_node(gallery_schema, "automatic_text");
const Json* text_control = find_named_node(gallery_schema, "text_value");
@@ -274,31 +274,31 @@ int main() {
const Json* color_gallery_control = find_named_node(gallery_schema, "color_value");
const Json* optional_control = find_named_node(gallery_schema, "optional_text");
const Json* readonly_control = find_named_node(gallery_schema, "read_only_text");
assert(automatic_control && automatic_control->at("type") == "input-text");
assert(text_control && text_control->at("type") == "input-text");
assert(multiline_control && multiline_control->at("type") == "textarea");
assert(number_control && number_control->at("type") == "input-number");
assert(number_control->at("min") == 0 && number_control->at("max") == 100);
assert(even_control && even_control->at("type") == "input-number" && even_control->at("step") == 2);
assert(boolean_control && boolean_control->at("type") == "switch");
assert(select_control && select_control->at("type") == "select");
assert(date_gallery_control && date_gallery_control->at("type") == "input-date");
assert(color_gallery_control && color_gallery_control->at("type") == "input-color");
assert(optional_control && optional_control->at("clearable") == true);
assert(readonly_control && readonly_control->at("type") == "static");
ADMINIVE_CHECK(automatic_control && automatic_control->at("type") == "input-text");
ADMINIVE_CHECK(text_control && text_control->at("type") == "input-text");
ADMINIVE_CHECK(multiline_control && multiline_control->at("type") == "textarea");
ADMINIVE_CHECK(number_control && number_control->at("type") == "input-number");
ADMINIVE_CHECK(number_control->at("min") == 0 && number_control->at("max") == 100);
ADMINIVE_CHECK(even_control && even_control->at("type") == "input-number" && even_control->at("step") == 2);
ADMINIVE_CHECK(boolean_control && boolean_control->at("type") == "switch");
ADMINIVE_CHECK(select_control && select_control->at("type") == "select");
ADMINIVE_CHECK(date_gallery_control && date_gallery_control->at("type") == "input-date");
ADMINIVE_CHECK(color_gallery_control && color_gallery_control->at("type") == "input-color");
ADMINIVE_CHECK(optional_control && optional_control->at("clearable") == true);
ADMINIVE_CHECK(readonly_control && readonly_control->at("type") == "static");
const Json* save_gallery_button = find_labeled_node(gallery_schema, "保存画廊数据");
const Json* create_gallery_button = find_labeled_node(gallery_schema, "新增一行");
const Json* edit_gallery_button = find_labeled_node(gallery_schema, "编辑");
const Json* delete_gallery_button = find_labeled_node(gallery_schema, "删除");
assert(save_gallery_button && save_gallery_button->at("type") == "submit");
assert(create_gallery_button && create_gallery_button->at("type") == "button");
assert(edit_gallery_button && edit_gallery_button->at("actionType") == "dialog");
assert(delete_gallery_button && delete_gallery_button->at("actionType") == "ajax");
ADMINIVE_CHECK(save_gallery_button && save_gallery_button->at("type") == "submit");
ADMINIVE_CHECK(create_gallery_button && create_gallery_button->at("type") == "button");
ADMINIVE_CHECK(edit_gallery_button && edit_gallery_button->at("actionType") == "dialog");
ADMINIVE_CHECK(delete_gallery_button && delete_gallery_button->at("actionType") == "ajax");
adminive::example::Server_Status server_status;
server_status.server_time = "2026-08-06T00:00:00Z";
server_status.poll_sequence = 7;
const auto status_json = adminive::to_frontend_json<Json>(server_status);
assert(status_json.at("service_state") == "running");
assert(status_json.at("poll_sequence") == 7);
ADMINIVE_CHECK(status_json.at("service_state") == "running");
ADMINIVE_CHECK(status_json.at("poll_sequence") == 7);
return 0;
}
+134 -116
View File
@@ -1,6 +1,6 @@
#include "adminive/adminive.hpp"
#include "adminive/adapters/nlohmann_json.hpp"
#include <cassert>
#include "adminive_test.hpp"
#include <stdexcept>
#include <string>
namespace view_test {
@@ -43,211 +43,229 @@ int main() {
using view_test::Config;
using view_test::Json;
const auto descriptor = adminive::to_descriptor_json<Json, Config>();
assert(descriptor.at("fields").at(0).at("presentation").at("label") == "Name");
assert(descriptor.at("fields").at(0).at("presentation").at("control") == "text");
assert(!descriptor.at("fields").at(0).contains("sortable"));
ADMINIVE_CHECK(descriptor.at("fields").at(0).at("presentation").at("label") == "Name");
ADMINIVE_CHECK(descriptor.at("fields").at(0).at("presentation").at("control") == "text");
ADMINIVE_CHECK(!descriptor.at("fields").at(0).contains("sortable"));
const auto form_view = adminive::describe_edit_view<Config>();
adminive::validate_form_view<Config>(form_view);
const auto form_json = adminive::to_view_json<Json, Config>(form_view);
assert(form_json.at("kind") == "form");
assert(form_json.at("body").at("kind") == "vertical");
assert(form_json.at("body").at("children").at(0).at("kind") == "group");
assert(form_json.at("body").at("children").at(1).at("kind") == "grid");
assert(form_json.at("body").at("children").at(2).at("kind") == "flow");
assert(form_json.at("body").at("children").at(3).at("kind") == "tabs");
ADMINIVE_CHECK(form_json.at("kind") == "form");
ADMINIVE_CHECK(form_json.at("body").at("kind") == "vertical");
ADMINIVE_CHECK(form_json.at("body").at("children").at(0).at("kind") == "group");
ADMINIVE_CHECK(form_json.at("body").at("children").at(1).at("kind") == "grid");
ADMINIVE_CHECK(form_json.at("body").at("children").at(2).at("kind") == "flow");
ADMINIVE_CHECK(form_json.at("body").at("children").at(3).at("kind") == "tabs");
Config config;
const auto form_amis = adminive::to_amis_form_schema<Json>(config, form_view, "/config/data");
assert(form_amis.at("type") == "form");
assert(form_amis.at("actions").at(1).at("label") == "Save");
assert(form_amis.at("body").at(0).at("type") == "fieldset");
assert(form_amis.at("body").at(1).at("type") == "grid");
assert(form_amis.at("body").at(2).at("type") == "flex");
assert(form_amis.at("body").at(2).at("items").at(0).at("type") == "container");
assert(form_amis.at("body").at(2).at("items").at(1).at("type") == "container");
assert(form_amis.at("body").at(2).at("items").at(0).at("body").at(0).at("type") == "card");
assert(form_amis.at("body").at(3).at("type") == "tabs");
assert(form_amis.at("data").at("read_only") == "fixed");
assert(form_amis.at("api").at("data").at("name") == "${name}");
assert(form_amis.at("api").at("data").at("count") == "${count}");
assert(form_amis.at("api").at("data").at("enabled") == "${enabled}");
assert(form_amis.at("api").at("data").at("color") == "${color}");
assert(!form_amis.at("api").at("data").contains("read_only"));
ADMINIVE_CHECK(form_amis.at("type") == "form");
ADMINIVE_CHECK(form_amis.at("actions").at(1).at("label") == "Save");
ADMINIVE_CHECK(form_amis.at("body").at(0).at("type") == "fieldset");
ADMINIVE_CHECK(form_amis.at("body").at(1).at("type") == "grid");
ADMINIVE_CHECK(form_amis.at("body").at(2).at("type") == "flex");
ADMINIVE_CHECK(form_amis.at("body").at(2).at("items").at(0).at("type") == "container");
ADMINIVE_CHECK(form_amis.at("body").at(2).at("items").at(1).at("type") == "container");
ADMINIVE_CHECK(form_amis.at("body").at(2).at("items").at(0).at("body").at(0).at("type") == "card");
ADMINIVE_CHECK(form_amis.at("body").at(3).at("type") == "tabs");
ADMINIVE_CHECK(form_amis.at("data").at("read_only") == "fixed");
ADMINIVE_CHECK(form_amis.at("api").at("data").at("name") == "${name}");
ADMINIVE_CHECK(form_amis.at("api").at("data").at("count") == "${count}");
ADMINIVE_CHECK(form_amis.at("api").at("data").at("enabled") == "${enabled}");
ADMINIVE_CHECK(form_amis.at("api").at("data").at("color") == "${color}");
ADMINIVE_CHECK(!form_amis.at("api").at("data").contains("read_only"));
const auto table_view = adminive::describe_table_view<Config>();
adminive::validate_table_view<Config>(table_view);
const auto table_json = adminive::to_view_json<Json, Config>(table_view);
assert(table_json.at("kind") == "table");
assert(table_json.at("columns").at(0).at("searchable") == true);
assert(table_json.at("columns").at(0).at("fixed") == "left");
assert(table_json.at("columns").at(1).at("sortable") == true);
assert(table_json.at("columns").at(2).at("filterable") == true);
assert(table_json.at("default_order_by") == "count");
assert(table_json.at("default_order_dir") == "desc");
ADMINIVE_CHECK(table_json.at("kind") == "table");
ADMINIVE_CHECK(table_json.at("columns").at(0).at("searchable") == true);
ADMINIVE_CHECK(table_json.at("columns").at(0).at("fixed") == "left");
ADMINIVE_CHECK(table_json.at("columns").at(1).at("sortable") == true);
ADMINIVE_CHECK(table_json.at("columns").at(2).at("filterable") == true);
ADMINIVE_CHECK(table_json.at("default_order_by") == "count");
ADMINIVE_CHECK(table_json.at("default_order_dir") == "desc");
const auto composition = adminive::composition_view("config_page", adminive::compose::vertical(adminive::compose::heading("Config Page").accent("#123456"), adminive::compose::horizontal(adminive::compose::slot("form"), adminive::compose::slot("table"))));
const auto composition_json = adminive::to_view_json<Json>(composition);
assert(composition_json.at("kind") == "composition");
assert(composition_json.at("body").at("children").at(0).at("kind") == "heading");
ADMINIVE_CHECK(composition_json.at("kind") == "composition");
ADMINIVE_CHECK(composition_json.at("body").at("children").at(0).at("kind") == "heading");
const auto composition_amis = adminive::to_amis_composition_schema<Json>(composition, {{"form", form_amis}, {"table", Json{{"type", "tpl"}, {"tpl", "table"}}}});
assert(composition_amis.at("body").at(0).at("type") == "tpl");
assert(composition_amis.at("body").at(1).at("type") == "grid");
ADMINIVE_CHECK(composition_amis.at("body").at(0).at("type") == "tpl");
ADMINIVE_CHECK(composition_amis.at("body").at(1).at("type") == "grid");
const auto five_columns = adminive::composition_view("five_columns", adminive::compose::horizontal(adminive::compose::slot("a"), adminive::compose::slot("b"), adminive::compose::slot("c"), adminive::compose::slot("d"), adminive::compose::slot("e")));
const auto five_columns_amis = adminive::to_amis_composition_schema<Json>(five_columns, {{"a", Json{{"type", "tpl"}}}, {"b", Json{{"type", "tpl"}}}, {"c", Json{{"type", "tpl"}}}, {"d", Json{{"type", "tpl"}}}, {"e", Json{{"type", "tpl"}}}});
const auto& five_widths = five_columns_amis.at("body").at(0).at("columns");
assert(five_widths.at(0).at("md") == 3);
assert(five_widths.at(1).at("md") == 3);
assert(five_widths.at(2).at("md") == 2);
assert(five_widths.at(3).at("md") == 2);
assert(five_widths.at(4).at("md") == 2);
ADMINIVE_CHECK(five_widths.at(0).at("md") == 3);
ADMINIVE_CHECK(five_widths.at(1).at("md") == 3);
ADMINIVE_CHECK(five_widths.at(2).at("md") == 2);
ADMINIVE_CHECK(five_widths.at(3).at("md") == 2);
ADMINIVE_CHECK(five_widths.at(4).at("md") == 2);
const auto flow_composition = adminive::composition_view("flow_page", adminive::compose::flow(adminive::compose::slot("left"), adminive::compose::slot("right")));
const auto flow_amis = adminive::to_amis_composition_schema<Json>(flow_composition, {{"left", Json{{"type", "tpl"}, {"tpl", "left"}}}, {"right", Json{{"type", "tpl"}, {"tpl", "right"}}}});
assert(flow_amis.at("body").at(0).at("type") == "flex");
assert(flow_amis.at("body").at(0).at("items").at(0).at("type") == "container");
assert(flow_amis.at("body").at(0).at("items").at(1).at("type") == "container");
ADMINIVE_CHECK(flow_amis.at("body").at(0).at("type") == "flex");
ADMINIVE_CHECK(flow_amis.at("body").at(0).at("items").at(0).at("type") == "container");
ADMINIVE_CHECK(flow_amis.at("body").at(0).at("items").at(1).at("type") == "container");
const auto list_composition = adminive::composition_view("list_page", adminive::compose::list(adminive::compose::slot("left"), adminive::compose::slot("right")));
const auto list_composition_json = adminive::to_view_json<Json>(list_composition);
assert(list_composition_json.at("body").at("kind") == "list");
ADMINIVE_CHECK(list_composition_json.at("body").at("kind") == "list");
const auto list_composition_amis = adminive::to_amis_composition_schema<Json>(list_composition, {{"left", Json{{"type", "tpl"}, {"tpl", "left"}}}, {"right", Json{{"type", "tpl"}, {"tpl", "right"}}}});
assert(list_composition_amis.at("body").at(0).at("type") == "container");
assert(list_composition_amis.at("body").at(0).at("body").size() == 2);
ADMINIVE_CHECK(list_composition_amis.at("body").at(0).at("type") == "container");
ADMINIVE_CHECK(list_composition_amis.at("body").at(0).at("body").size() == 2);
const auto frontend_composition = adminive::composition_view("frontend_page", adminive::compose::frontend(adminive::compose::slot("left"), adminive::compose::slot("right")));
const auto frontend_composition_json = adminive::to_view_json<Json>(frontend_composition);
assert(frontend_composition_json.at("body").at("kind") == "frontend");
ADMINIVE_CHECK(frontend_composition_json.at("body").at("kind") == "frontend");
const auto frontend_composition_amis = adminive::to_amis_composition_schema<Json>(frontend_composition, {{"left", Json{{"type", "tpl"}, {"tpl", "left"}}}, {"right", Json{{"type", "tpl"}, {"tpl", "right"}}}});
assert(frontend_composition_amis.at("body").at(0).at("className") == "adminive-frontend-placement");
ADMINIVE_CHECK(frontend_composition_amis.at("body").at(0).at("className") == "adminive-frontend-placement");
const std::vector<adminive::Composition_Slot_Contract> slot_contracts{{"left", "amis_schema", "/left/descriptor", "/left/view", "/left/data", "/left/amis"}, {"right", "amis_schema", "/right/descriptor", "/right/view", "/right/data", "/right/amis"}};
const auto manifest = adminive::to_composition_manifest_json<Json>(frontend_composition, slot_contracts, {{"left", Json{{"type", "tpl"}, {"tpl", "left"}}}, {"right", Json{{"type", "tpl"}, {"tpl", "right"}}}});
assert(manifest.at("protocol") == "adminive.composition-manifest");
assert(manifest.at("view").at("body").at("kind") == "frontend");
assert(manifest.at("slots").at("left").at("view_api") == "/left/view");
assert(manifest.at("slots").at("right").at("schema").at("tpl") == "right");
ADMINIVE_CHECK(manifest.at("protocol") == "adminive.composition-manifest");
ADMINIVE_CHECK(manifest.at("view").at("body").at("kind") == "frontend");
ADMINIVE_CHECK(manifest.at("slots").at("left").at("view_api") == "/left/view");
ADMINIVE_CHECK(manifest.at("slots").at("right").at("schema").at("tpl") == "right");
const auto frontend_form = adminive::edit_form<Config>(adminive::frontend(adminive::list(adminive::use<&Config::name>(), adminive::use<&Config::count>())));
const auto frontend_form_json = adminive::to_view_json<Json, Config>(frontend_form);
assert(frontend_form_json.at("body").at("kind") == "frontend");
assert(frontend_form_json.at("body").at("children").at(0).at("kind") == "list");
ADMINIVE_CHECK(frontend_form_json.at("body").at("kind") == "frontend");
ADMINIVE_CHECK(frontend_form_json.at("body").at("children").at(0).at("kind") == "list");
const auto table_amis = adminive::to_amis_table_schema<Json, Config>("/configs", table_view);
assert(table_amis.at("type") == "page");
assert(table_amis.at("body").at(0).at("columns").at(1).at("searchable") == true);
assert(table_amis.at("body").at(0).at("columns").at(2).at("sortable") == true);
assert(table_amis.at("body").at(0).at("columns").at(3).at("filterable") == true);
ADMINIVE_CHECK(table_amis.at("type") == "page");
ADMINIVE_CHECK(table_amis.at("body").at(0).at("columns").at(1).at("searchable") == true);
ADMINIVE_CHECK(table_amis.at("body").at(0).at("columns").at(2).at("sortable") == true);
ADMINIVE_CHECK(table_amis.at("body").at(0).at("columns").at(3).at("filterable") == true);
const auto& create_submit_data = table_amis.at("body").at(0).at("headerToolbar").at(2).at("dialog").at("body").at("api").at("data");
assert(create_submit_data.at("name") == "${name}");
assert(create_submit_data.at("count") == "${count}");
assert(create_submit_data.at("enabled") == "${enabled}");
assert(create_submit_data.at("color") == "${color}");
assert(!create_submit_data.contains("read_only"));
ADMINIVE_CHECK(create_submit_data.at("name") == "${name}");
ADMINIVE_CHECK(create_submit_data.at("count") == "${count}");
ADMINIVE_CHECK(create_submit_data.at("enabled") == "${enabled}");
ADMINIVE_CHECK(create_submit_data.at("color") == "${color}");
ADMINIVE_CHECK(!create_submit_data.contains("read_only"));
const auto& edit_submit_data = table_amis.at("body").at(0).at("columns").back().at("buttons").at(0).at("dialog").at("body").at("api").at("data");
assert(edit_submit_data.at("name") == "${name}");
assert(edit_submit_data.at("count") == "${count}");
assert(edit_submit_data.at("enabled") == "${enabled}");
assert(edit_submit_data.at("color") == "${color}");
assert(!edit_submit_data.contains("read_only"));
ADMINIVE_CHECK(edit_submit_data.at("name") == "${name}");
ADMINIVE_CHECK(edit_submit_data.at("count") == "${count}");
ADMINIVE_CHECK(edit_submit_data.at("enabled") == "${enabled}");
ADMINIVE_CHECK(edit_submit_data.at("color") == "${color}");
ADMINIVE_CHECK(!edit_submit_data.contains("read_only"));
const auto list_amis = adminive::to_amis_list_schema<Json, Config>("/configs", table_view, Json{{"title", "${name}"}});
assert(list_amis.at("body").at(0).at("mode") == "list");
assert(list_amis.at("body").at(0).at("id") == "view_config_table_crud_list");
assert(!list_amis.at("body").at(0).contains("columns"));
assert(list_amis.at("body").at(0).at("listItem").at("actions").size() == 2);
assert(list_amis.at("body").at(0).at("listItem").at("actions").at(0).at("dialog").at("body").at("reload") == "view_config_table_crud_list");
assert(list_amis.at("body").at(0).at("listItem").at("actions").at(1).at("reload") == "view_config_table_crud_list");
assert(list_amis.at("body").at(0).at("headerToolbar").size() == 2);
assert(list_amis.at("body").at(0).at("headerToolbar").at(1).at("dialog").at("body").at("reload") == "view_config_table_crud_list");
ADMINIVE_CHECK(list_amis.at("body").at(0).at("mode") == "list");
ADMINIVE_CHECK(list_amis.at("body").at(0).at("id") == "view_config_table_crud_list");
ADMINIVE_CHECK(!list_amis.at("body").at(0).contains("columns"));
ADMINIVE_CHECK(list_amis.at("body").at(0).at("listItem").at("actions").size() == 2);
ADMINIVE_CHECK(list_amis.at("body").at(0).at("listItem").at("actions").at(0).at("dialog").at("body").at("reload") == "view_config_table_crud_list");
ADMINIVE_CHECK(list_amis.at("body").at(0).at("listItem").at("actions").at(1).at("reload") == "view_config_table_crud_list");
ADMINIVE_CHECK(list_amis.at("body").at(0).at("headerToolbar").size() == 2);
ADMINIVE_CHECK(list_amis.at("body").at(0).at("headerToolbar").at(1).at("dialog").at("body").at("reload") == "view_config_table_crud_list");
const auto cards_amis = adminive::to_amis_cards_schema<Json, Config>("/configs", table_view, Json{{"header", Json{{"title", "${name}"}}}}, 2);
assert(cards_amis.at("body").at(0).at("mode") == "cards");
assert(cards_amis.at("body").at(0).at("id") == "view_config_table_crud_cards");
assert(cards_amis.at("body").at(0).at("columnsCount") == 2);
assert(cards_amis.at("body").at(0).at("card").at("actions").size() == 2);
assert(cards_amis.at("body").at(0).at("card").at("actions").at(1).at("reload") == "view_config_table_crud_cards");
ADMINIVE_CHECK(cards_amis.at("body").at(0).at("mode") == "cards");
ADMINIVE_CHECK(cards_amis.at("body").at(0).at("id") == "view_config_table_crud_cards");
ADMINIVE_CHECK(cards_amis.at("body").at(0).at("columnsCount") == 2);
ADMINIVE_CHECK(cards_amis.at("body").at(0).at("card").at("actions").size() == 2);
ADMINIVE_CHECK(cards_amis.at("body").at(0).at("card").at("actions").at(1).at("reload") == "view_config_table_crud_cards");
const auto item_view = adminive::collection_item<&Config::name>().subtitle<&Config::color>().accent<&Config::color>().body<&Config::count, &Config::enabled>();
const auto collection_view = adminive::describe_collection_view<Config>();
const auto labeled_collection = collection_view.id_title("Record").create_title("Add").edit_title("Change").delete_title("Remove").actions_title("Operations").confirm_title("Apply").delete_confirmation("Remove record?").status_title("Status");
assert(labeled_collection.table.id_label == "Record");
assert(labeled_collection.table.create_label == "Add");
assert(labeled_collection.table.edit_label == "Change");
assert(labeled_collection.table.delete_label == "Remove");
assert(labeled_collection.table.actions_label == "Operations");
assert(labeled_collection.table.confirm_label == "Apply");
assert(labeled_collection.table.delete_confirm == "Remove record?");
assert(labeled_collection.table.view_status_label == "Status");
ADMINIVE_CHECK(labeled_collection.table.id_label == "Record");
ADMINIVE_CHECK(labeled_collection.table.create_label == "Add");
ADMINIVE_CHECK(labeled_collection.table.edit_label == "Change");
ADMINIVE_CHECK(labeled_collection.table.delete_label == "Remove");
ADMINIVE_CHECK(labeled_collection.table.actions_label == "Operations");
ADMINIVE_CHECK(labeled_collection.table.confirm_label == "Apply");
ADMINIVE_CHECK(labeled_collection.table.delete_confirm == "Remove record?");
ADMINIVE_CHECK(labeled_collection.table.view_status_label == "Status");
const auto semantic_list_view = collection_view.as_list(item_view);
const auto semantic_list_json = adminive::to_view_json<Json, Config>(semantic_list_view);
assert(semantic_list_json.at("kind") == "collection");
assert(semantic_list_json.at("mode") == "list");
assert(semantic_list_json.at("item").at("title_field") == "name");
assert(semantic_list_json.at("item").at("fields").at(0) == "count");
ADMINIVE_CHECK(semantic_list_json.at("kind") == "collection");
ADMINIVE_CHECK(semantic_list_json.at("mode") == "list");
ADMINIVE_CHECK(semantic_list_json.at("item").at("title_field") == "name");
ADMINIVE_CHECK(semantic_list_json.at("item").at("fields").at(0) == "count");
const auto semantic_list_amis = adminive::to_amis_collection_schema<Json, Config>("/configs", semantic_list_view);
assert(semantic_list_amis.at("body").at(0).at("id") == "view_config_collection_crud_list");
assert(semantic_list_amis.at("body").at(0).at("listItem").at("title") == "${name}");
assert(semantic_list_amis.at("body").at(0).at("listItem").at("actions").at(1).at("reload") == "view_config_collection_crud_list");
ADMINIVE_CHECK(semantic_list_amis.at("body").at(0).at("id") == "view_config_collection_crud_list");
ADMINIVE_CHECK(semantic_list_amis.at("body").at(0).at("listItem").at("title") == "${name}");
ADMINIVE_CHECK(semantic_list_amis.at("body").at(0).at("listItem").at("actions").at(1).at("reload") == "view_config_collection_crud_list");
const auto semantic_cards_view = collection_view.as_cards(item_view, 4);
const auto semantic_cards_json = adminive::to_view_json<Json, Config>(semantic_cards_view);
assert(semantic_cards_json.at("mode") == "cards");
assert(semantic_cards_json.at("cards_columns") == 4);
ADMINIVE_CHECK(semantic_cards_json.at("mode") == "cards");
ADMINIVE_CHECK(semantic_cards_json.at("cards_columns") == 4);
const auto semantic_cards_amis = adminive::to_amis_collection_schema<Json, Config>("/configs", semantic_cards_view);
assert(semantic_cards_amis.at("body").at(0).at("id") == "view_config_collection_crud_cards");
assert(semantic_cards_amis.at("body").at(0).at("columnsCount") == 4);
ADMINIVE_CHECK(semantic_cards_amis.at("body").at(0).at("id") == "view_config_collection_crud_cards");
ADMINIVE_CHECK(semantic_cards_amis.at("body").at(0).at("columnsCount") == 4);
const auto semantic_table_json = adminive::to_view_json<Json, Config>(collection_view.as_table());
assert(semantic_table_json.at("mode") == "table");
ADMINIVE_CHECK(semantic_table_json.at("mode") == "table");
adminive::Collection_Service<Config, Json, adminive::No_Lock> collection("/configs", {Config{"Alpha", 3, true, "#111111"}, Config{"Beta", 7, false, "#222222"}, Config{"Alphabet", 5, true, "#333333"}});
adminive::Collection_Query query;
query.fields["name"] = "alpha";
auto response = collection.list_response(query);
assert(response.status == 200);
assert(response.body.at("data").at("total") == 2);
assert(response.body.at("data").at("items").at(0).at("count") == 5);
ADMINIVE_CHECK(response.status == 200);
ADMINIVE_CHECK(response.body.at("data").at("total") == 2);
ADMINIVE_CHECK(response.body.at("data").at("items").at(0).at("count") == 5);
query = {};
query.fields["enabled"] = "true";
response = collection.list_response(query);
assert(response.status == 200);
assert(response.body.at("data").at("total") == 2);
ADMINIVE_CHECK(response.status == 200);
ADMINIVE_CHECK(response.body.at("data").at("total") == 2);
query = {};
query.order_by = "count";
query.order_dir = "asc";
response = collection.list_response(query);
assert(response.status == 200);
assert(response.body.at("data").at("items").at(0).at("count") == 3);
ADMINIVE_CHECK(response.status == 200);
ADMINIVE_CHECK(response.body.at("data").at("items").at(0).at("count") == 3);
query = {};
query.fields["color"] = "#111111";
response = collection.list_response(query);
assert(response.status == 400);
ADMINIVE_CHECK(response.status == 400);
query = {};
query.order_by = "enabled";
query.order_dir = "asc";
response = collection.list_response(query);
ADMINIVE_CHECK(response.status == 400);
query = {};
query.order_by = "count";
query.order_dir = "sideways";
response = collection.list_response(query);
ADMINIVE_CHECK(response.status == 400);
bool invalid_manifest{};
try {
adminive::to_composition_manifest_json<Json>(frontend_composition, {adminive::Composition_Slot_Contract{"left", "amis_schema", {}, {}, {}, {}}}, {{"left", Json{{"type", "tpl"}}}});
} catch(const std::invalid_argument&) {
invalid_manifest = true;
}
assert(invalid_manifest);
ADMINIVE_CHECK(invalid_manifest);
bool duplicate_manifest_slot{};
try {
const auto duplicate_frontend_composition = adminive::composition_view("duplicate", adminive::compose::frontend(adminive::compose::slot("left"), adminive::compose::slot("left")));
adminive::to_composition_manifest_json<Json>(duplicate_frontend_composition, {slot_contracts.front()}, {{"left", Json{{"type", "tpl"}}}});
} catch(const std::invalid_argument&) {
duplicate_manifest_slot = true;
}
ADMINIVE_CHECK(duplicate_manifest_slot);
bool invalid_grid{};
try {
adminive::validate_form_view<Config>(adminive::edit_form<Config>(adminive::grid(0, adminive::use<&Config::name>())));
} catch(const std::invalid_argument&) {
invalid_grid = true;
}
assert(invalid_grid);
ADMINIVE_CHECK(invalid_grid);
bool invalid_tabs{};
try {
adminive::validate_form_view<Config>(adminive::edit_form<Config>(adminive::tabs(adminive::use<&Config::name>())));
} catch(const std::invalid_argument&) {
invalid_tabs = true;
}
assert(invalid_tabs);
ADMINIVE_CHECK(invalid_tabs);
bool invalid_tab_position{};
try {
adminive::validate_form_view<Config>(adminive::edit_form<Config>(adminive::tab("Invalid", adminive::use<&Config::name>())));
} catch(const std::invalid_argument&) {
invalid_tab_position = true;
}
assert(invalid_tab_position);
ADMINIVE_CHECK(invalid_tab_position);
bool invalid_composition_tab_position{};
try {
adminive::validate_composition_view(adminive::composition_view("invalid", adminive::compose::tab("Invalid", adminive::compose::slot("x"))));
} catch(const std::invalid_argument&) {
invalid_composition_tab_position = true;
}
assert(invalid_composition_tab_position);
ADMINIVE_CHECK(invalid_composition_tab_position);
bool invalid_sort{};
try {
adminive::validate_table_view<Config>(adminive::table_view<Config>(adminive::column<&Config::name>()).default_sort("name"));
} catch(const std::invalid_argument&) {
invalid_sort = true;
}
assert(invalid_sort);
ADMINIVE_CHECK(invalid_sort);
return 0;
}