// Тесты JSON: writer (фиксированный буфер, вложенность, переполнение) // и парсер (jsmn) — форматы протокола. #include "doctest/doctest.h" #include #include #include "ayla/json.hpp" using fgl::ayla::json::Doc; using fgl::ayla::json::Writer; TEST_CASE("json writer: формы протокола") { // datapoint-команда (properties) char buf[512]; Writer w(buf, sizeof(buf)); w.begin_object(); // { w.key("properties"); w.begin_array(); w.begin_object(); w.key("property"); w.begin_object(); w.key("base_type"); w.string("integer"); w.key("name"); w.string("fan_speed"); w.key("value"); w.integer(3); w.key("id"); w.string("ab12cd34"); w.end_object(); w.end_object(); w.end_array(); w.end_object(); REQUIRE(w.ok()); CHECK(std::string(buf) == "{\"properties\":[{\"property\":{\"base_type\":\"integer\"," "\"name\":\"fan_speed\",\"value\":3,\"id\":\"ab12cd34\"}}]}"); // GET-команда (cmds) Writer w2(buf, sizeof(buf)); w2.begin_object(); w2.key("cmds"); w2.begin_array(); w2.begin_object(); w2.key("cmd"); w2.begin_object(); w2.key("cmd_id"); w2.integer(5); w2.key("method"); w2.string("GET"); w2.key("resource"); w2.string("property.json?name=op"); w2.key("data"); w2.string(""); w2.key("uri"); w2.string("/local_lan/property/datapoint.json"); w2.end_object(); w2.end_object(); w2.end_array(); w2.end_object(); REQUIRE(w2.ok()); CHECK(std::string(buf) == "{\"cmds\":[{\"cmd\":{\"cmd_id\":5,\"method\":\"GET\"," "\"resource\":\"property.json?name=op\",\"data\":\"\"," "\"uri\":\"/local_lan/property/datapoint.json\"}}]}"); // key_exchange ответ + массив целых + bool/null Writer w3(buf, sizeof(buf)); w3.begin_object(); w3.key("random_2"); w3.string("abcd1234"); w3.key("time_2"); w3.integer(-123); w3.key("flags"); w3.begin_array(); w3.integer(1); w3.integer(2); w3.integer(3); w3.end_array(); w3.key("ok"); w3.boolean(true); w3.key("nothing"); w3.literal("null"); w3.end_object(); REQUIRE(w3.ok()); CHECK(std::string(buf) == "{\"random_2\":\"abcd1234\",\"time_2\":-123,\"flags\":[1,2,3]," "\"ok\":true,\"nothing\":null}"); } TEST_CASE("json writer: экранирование и переполнение") { char buf[64]; Writer w(buf, sizeof(buf)); w.begin_object(); w.key("a\"b\\c"); // ключ с кавычкой и обратным слешем w.string("x\ny\"z"); w.end_object(); REQUIRE(w.ok()); CHECK(std::string(buf) == "{\"a\\\"b\\\\c\":\"x\\ny\\\"z\"}"); // Переполнение буфера — ok() = false, не выходим за границы. Writer w2(buf, sizeof(buf)); w2.begin_object(); w2.key("data"); w2.string("0123456789012345678901234567890123456789012345678901234567890123456789"); w.end_object(); CHECK_FALSE(w2.ok()); CHECK(strlen(buf) < sizeof(buf)); // NUL-терминировано всегда } TEST_CASE("json parser: форматы протокола") { // datapoint push char buf[128]; strcpy(buf, "{\"name\":\"fan_speed\",\"value\":3,\"metadata\":{}}"); Doc doc; REQUIRE(doc.parse(buf)); char name[32]; REQUIRE(doc.get_string("name", name, sizeof(name))); CHECK(std::string(name) == "fan_speed"); int64_t value = -1; REQUIRE(doc.get_int("value", &value)); CHECK(value == 3); bool b = true; CHECK_FALSE(doc.get_bool("value", &b)); // не bool CHECK_FALSE(doc.get_string("absent", name, sizeof(name))); // key_exchange strcpy(buf, "{\"key_exchange\":{\"ver\":1,\"proto\":1,\"key_id\":62999," "\"random_1\":\"ab+/12\",\"time_1\":-987654321,\"sec\":\"\"}}"); REQUIRE(doc.parse(buf)); int64_t ver = -1, key_id = -1, time_1 = -1; REQUIRE(doc.get_int("ver", &ver)); REQUIRE(doc.get_int("key_id", &key_id)); REQUIRE(doc.get_int("time_1", &time_1)); char rnd[32]; REQUIRE(doc.get_string("random_1", rnd, sizeof(rnd))); CHECK(ver == 1); CHECK(key_id == 62999); CHECK(time_1 == -987654321); CHECK(std::string(rnd) == "ab+/12"); // envelope payload strcpy(buf, "{\"seq_no\":42,\"data\":{\"name\":\"x\",\"value\":true}}"); REQUIRE(doc.parse(buf)); int64_t seq = -1; REQUIRE(doc.get_int("seq_no", &seq)); CHECK(seq == 42); bool v = false; REQUIRE(doc.get_bool("value", &v)); CHECK(v); // Мусор strcpy(buf, "not json"); CHECK_FALSE(doc.parse(buf)); strcpy(buf, "[1,2]"); CHECK_FALSE(doc.parse(buf)); // верхний уровень — объект } TEST_CASE("json parser: экранированные строки") { char buf[96]; strcpy(buf, "{\"name\":\"a\\\"b\\\\c\\nd\"}"); Doc doc; REQUIRE(doc.parse(buf)); char out[32]; REQUIRE(doc.get_string("name", out, sizeof(out))); CHECK(std::string(out) == "a\"b\\c\nd"); // Не помещается в out char small[4]; CHECK_FALSE(doc.get_string("name", small, sizeof(small))); }