mirror of
https://github.com/openziti/ziti-sdk-c.git
synced 2026-09-11 01:05:38 +00:00
532 lines
13 KiB
C++
532 lines
13 KiB
C++
/*
|
|
Copyright (c) 2020 Netfoundry, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
#include <cstring>
|
|
#include "catch2/catch.hpp"
|
|
|
|
#include <ziti/model_support.h>
|
|
#include <iostream>
|
|
|
|
#define BAR_MODEL(xx, ...)\
|
|
xx(num, int, none, num, __VA_ARGS__)\
|
|
xx(nump, int, ptr, nump, __VA_ARGS__) \
|
|
xx(isOK, bool, none, ok, __VA_ARGS__)\
|
|
xx(msg, string, none, msg, __VA_ARGS__)\
|
|
xx(ts, timestamp, ptr, time, __VA_ARGS__)\
|
|
xx(errors, string, array, errors, __VA_ARGS__)\
|
|
xx(codes, int, array, codes, __VA_ARGS__)
|
|
|
|
DECLARE_MODEL(Bar, BAR_MODEL)
|
|
|
|
IMPL_MODEL(Bar, BAR_MODEL)
|
|
|
|
#define FOO_MODEL(xx, ...) \
|
|
xx(bar, Bar, none, bar, __VA_ARGS__) \
|
|
xx(barp, Bar, ptr, barp, __VA_ARGS__) \
|
|
xx(bar_arr, Bar, array, bara, __VA_ARGS__)
|
|
|
|
DECLARE_MODEL(Foo, FOO_MODEL)
|
|
|
|
IMPL_MODEL(Foo, FOO_MODEL)
|
|
|
|
using namespace Catch::Matchers;
|
|
|
|
#define BAR1 "{\
|
|
\"num\":42,\
|
|
\"ok\": false,\
|
|
\"time\": \"2020-07-20T14:14:14.666666Z\",\
|
|
\"msg\":\"this is a message\",\
|
|
\"errors\": [\"error1\", \"error2\"], \
|
|
\"codes\": [401, 403] \
|
|
}"
|
|
|
|
#define BAR_WITH_NULL_CODES_ARRAY "{\
|
|
\"num\":42,\
|
|
\"ok\": false,\
|
|
\"time\": \"2020-07-20T14:14:14.666666Z\",\
|
|
\"msg\":\"this is a message\",\
|
|
\"errors\": [\"error1\", \"error2\"], \
|
|
\"codes\": null \
|
|
}"
|
|
|
|
#define BAR_WITH_MISSING_CODES_ARRAY "{\
|
|
\"num\":42,\
|
|
\"ok\": false,\
|
|
\"time\": \"2020-07-20T14:14:14.666666Z\",\
|
|
\"msg\":\"this is a message\",\
|
|
\"errors\": [\"error1\", \"error2\"] \
|
|
}"
|
|
|
|
static void checkBar1(const Bar &bar) {
|
|
CHECK(bar.num == 42);
|
|
CHECK(!bar.isOK);
|
|
CHECK_THAT(bar.errors[0], Equals("error1"));
|
|
CHECK_THAT(bar.errors[1], Equals("error2"));
|
|
CHECK(bar.errors[2] == nullptr);
|
|
|
|
int c1 = *bar.codes[0];
|
|
CHECK(c1 == 401);
|
|
int c2 = *bar.codes[1];
|
|
CHECK(c2 == 403);
|
|
CHECK(bar.codes[2] == nullptr);
|
|
}
|
|
|
|
#define BAR2 "{\
|
|
\"num\":-42,\
|
|
\"ok\": true,\
|
|
}"
|
|
|
|
static void checkBar2(const Bar &bar) {
|
|
CHECK(bar.num == -42);
|
|
CHECK(bar.isOK);
|
|
CHECK(bar.errors == nullptr);
|
|
CHECK(bar.codes == nullptr);
|
|
}
|
|
|
|
TEST_CASE("new model tests") {
|
|
const char *bar_json = BAR1;
|
|
Bar bar;
|
|
|
|
REQUIRE(parse_Bar(&bar, bar_json, strlen(bar_json)) == 0);
|
|
|
|
checkBar1(bar);
|
|
|
|
char *json = Bar_to_json(&bar, 0, nullptr);
|
|
std::cout << json << std::endl;
|
|
free(json);
|
|
free_Bar(&bar);
|
|
}
|
|
|
|
TEST_CASE("embedded struct") {
|
|
const char *json = "{"
|
|
"\"bar\":" BAR1 ","
|
|
"\"barp\": " BAR2 ","
|
|
"\"bara\": [" BAR1 "," BAR2 "]"
|
|
"}";
|
|
Foo foo;
|
|
REQUIRE(parse_Foo(&foo, json, strlen(json)) == 0);
|
|
checkBar1(foo.bar);
|
|
|
|
REQUIRE(foo.barp != nullptr);
|
|
checkBar2(*foo.barp);
|
|
|
|
REQUIRE(foo.bar_arr != nullptr);
|
|
CHECK(foo.bar_arr[2] == nullptr);
|
|
checkBar1(*foo.bar_arr[0]);
|
|
checkBar2(*foo.bar_arr[1]);
|
|
|
|
char *json1 = Foo_to_json(&foo, 0, NULL);
|
|
std::cout << json1 << std::endl;
|
|
free(json1);
|
|
free_Foo(&foo);
|
|
}
|
|
|
|
TEST_CASE("test skipped fields") {
|
|
const char *json = R"({
|
|
"bar":{
|
|
"num":42,
|
|
"ok":true,
|
|
"msg":"hello\nworld!"
|
|
},
|
|
"skipper": [{"this":"should be skipped"},42,null],
|
|
"also-skip": {"more":"skipping"},
|
|
"barp":{
|
|
"skip-field":{},
|
|
"nump":42,
|
|
"ok":true,
|
|
"msg":"hello world!"
|
|
}
|
|
})";
|
|
Foo foo;
|
|
REQUIRE(parse_Foo(&foo, json, strlen(json)) == 0);
|
|
|
|
REQUIRE(foo.bar.num == 42);
|
|
REQUIRE(foo.bar.isOK);
|
|
REQUIRE_THAT(foo.bar.msg, Catch::Matchers::Equals("hello\nworld!"));
|
|
|
|
REQUIRE(foo.barp != nullptr);
|
|
REQUIRE(*foo.barp->nump == 42);
|
|
REQUIRE(foo.barp->isOK);
|
|
REQUIRE_THAT(foo.barp->msg, Catch::Matchers::Equals("hello world!"));
|
|
|
|
size_t jsonlen;
|
|
char *json1 = Foo_to_json(&foo, 0, &jsonlen);
|
|
std::cout << json1 << std::endl;
|
|
free(json1);
|
|
free_Foo(&foo);
|
|
}
|
|
|
|
TEST_CASE("test string escape", "[model]") {
|
|
const char *json = R"({
|
|
"msg":"\thello\n\"world\"!"
|
|
})";
|
|
|
|
Bar bar;
|
|
REQUIRE(parse_Bar(&bar, json, strlen(json)) == 0);
|
|
REQUIRE_THAT(bar.msg, Equals("\thello\n\"world\"!"));
|
|
|
|
char *jsonout = Bar_to_json(&bar, 0, NULL);
|
|
std::cout << jsonout << std::endl;
|
|
}
|
|
|
|
#define baz_model(XX, ...) \
|
|
XX(bar, json, none, bar, __VA_ARGS__) \
|
|
XX(ok, bool, none, ok, __VA_ARGS__)
|
|
|
|
#undef MODEL_API
|
|
#define MODEL_API static
|
|
DECLARE_MODEL(Baz, baz_model)
|
|
IMPL_MODEL(Baz, baz_model)
|
|
|
|
TEST_CASE("test raw json", "[model]") {
|
|
const char *json = "{"
|
|
"\"bar\":" BAR1 ","
|
|
"\"ok\": true"
|
|
"}";
|
|
Baz baz;
|
|
REQUIRE(parse_Baz(&baz, json, strlen(json)) == 0);
|
|
REQUIRE_THAT(baz.bar, Equals(BAR1));
|
|
REQUIRE(baz.ok);
|
|
|
|
Bar bar;
|
|
REQUIRE(parse_Bar(&bar, baz.bar, strlen(baz.bar)) == 0);
|
|
checkBar1(bar);
|
|
free_Bar(&bar);
|
|
free_Baz(&baz);
|
|
}
|
|
|
|
#define map_model(XX, ...) \
|
|
XX(map, model_map, none, map, __VA_ARGS__) \
|
|
XX(ok, bool, none, ok, __VA_ARGS__)
|
|
|
|
DECLARE_MODEL(ObjMap, map_model)
|
|
IMPL_MODEL(ObjMap, map_model)
|
|
|
|
TEST_CASE("model map test", "[model]") {
|
|
const char *json = "{"
|
|
"\"map\":" BAR1 ","
|
|
"\"ok\": true"
|
|
"}";
|
|
|
|
ObjMap o;
|
|
REQUIRE(parse_ObjMap(&o, json, strlen(json)) == 0);
|
|
CHECK(o.ok);
|
|
CHECK_THAT((const char *) model_map_get(&o.map, "num"), Equals("42"));
|
|
CHECK_THAT((const char *) model_map_get(&o.map, "errors"), Equals(R"(["error1", "error2"])"));
|
|
|
|
char *j = ObjMap_to_json(&o, 0, NULL);
|
|
|
|
std::cout << j << std::endl;
|
|
free(j);
|
|
|
|
model_map_clear(&o.map, nullptr);
|
|
}
|
|
|
|
TEST_CASE("model compare", "[model]") {
|
|
Bar b1;
|
|
memset(&b1, 0, sizeof(Bar));
|
|
b1.num = 45;
|
|
b1.isOK = false;
|
|
b1.msg = (char *) "this is bar1";
|
|
|
|
Bar b2;
|
|
memset(&b2, 0, sizeof(Bar));
|
|
b2.num = 42;
|
|
b2.isOK = true;
|
|
b2.msg = (char *) "this is bar2";
|
|
|
|
CHECK(model_cmp(&b1, &b2, &Bar_META) != 0);
|
|
|
|
b1.isOK = true;
|
|
CHECK(model_cmp(&b1, &b2, &Bar_META) != 0);
|
|
|
|
b2.num = 45;
|
|
b2.msg = (char *) "this is bar1";
|
|
CHECK(model_cmp(&b1, &b2, &Bar_META) == 0);
|
|
|
|
b2.msg = nullptr;
|
|
CHECK(model_cmp(&b1, &b2, &Bar_META) != 0);
|
|
|
|
b1.msg = nullptr;
|
|
CHECK(model_cmp(&b1, &b2, &Bar_META) == 0);
|
|
}
|
|
|
|
TEST_CASE("model compare with map", "[model]") {
|
|
|
|
ObjMap o1;
|
|
o1.map = {0};
|
|
o1.ok = true;
|
|
|
|
ObjMap o2;
|
|
o2.map = {0},
|
|
o2.ok = true;
|
|
|
|
CHECK(cmp_ObjMap(&o1, &o2) == 0);
|
|
|
|
model_map_set(&o1.map, "key1", (void *) "one");
|
|
CHECK(cmp_ObjMap(&o1, &o2) != 0);
|
|
|
|
model_map_set(&o2.map, "key2", (void *) "two");
|
|
CHECK(cmp_ObjMap(&o1, &o2) != 0);
|
|
|
|
model_map_set(&o2.map, "key1", (void *) "one");
|
|
model_map_set(&o1.map, "key2", (void *) "two");
|
|
CHECK(cmp_ObjMap(&o1, &o2) == 0);
|
|
}
|
|
|
|
TEST_CASE("model compare with array", "[model]") {
|
|
const char *bar_json = BAR1;
|
|
Bar bar1, bar2;
|
|
|
|
REQUIRE(parse_Bar(&bar1, bar_json, strlen(bar_json)) == 0);
|
|
REQUIRE(parse_Bar(&bar2, bar_json, strlen(bar_json)) == 0);
|
|
|
|
CHECK(cmp_Bar(&bar1, &bar2) == 0);
|
|
|
|
bar1.errors[0] = strdup("changed error");
|
|
CHECK(cmp_Bar(&bar1, &bar2) != 0);
|
|
|
|
free_Bar(&bar1);
|
|
free_Bar(&bar2);
|
|
}
|
|
|
|
TEST_CASE("model with null array is null") {
|
|
const char *bar_json = BAR_WITH_NULL_CODES_ARRAY;
|
|
Bar bar;
|
|
|
|
REQUIRE(parse_Bar(&bar, bar_json, strlen(bar_json)) == 0);
|
|
|
|
CHECK(bar.codes == nullptr);
|
|
|
|
free_Bar(&bar);
|
|
}
|
|
|
|
TEST_CASE("model with missing array is null") {
|
|
const char *bar_json = BAR_WITH_MISSING_CODES_ARRAY;
|
|
Bar bar;
|
|
|
|
REQUIRE(parse_Bar(&bar, bar_json, strlen(bar_json)) == 0);
|
|
|
|
CHECK(bar.codes == nullptr);
|
|
|
|
free_Bar(&bar);
|
|
}
|
|
|
|
#define string_map_model(XX, ...) \
|
|
XX(tags, string, map, tags, __VA_ARGS__)
|
|
|
|
DECLARE_MODEL(tagged, string_map_model)
|
|
|
|
IMPL_MODEL(tagged, string_map_model)
|
|
|
|
|
|
TEST_CASE("model with string map", "[model]") {
|
|
const char *json = R"({
|
|
"tags":{
|
|
"num":"42",
|
|
"ok":"true",
|
|
"msg":"hello\nworld!"
|
|
}
|
|
})";
|
|
|
|
tagged obj;
|
|
REQUIRE(parse_tagged(&obj, json, strlen(json)) == 0);
|
|
|
|
const char *num = (const char *) model_map_get(&obj.tags, "num");
|
|
CHECK_THAT(num, Equals("42"));
|
|
CHECK_THAT((const char *) model_map_get(&obj.tags, "ok"), Equals("true"));
|
|
CHECK_THAT((const char *) model_map_get(&obj.tags, "msg"), Equals("hello\nworld!"));
|
|
|
|
char *buf = tagged_to_json(&obj, 0, nullptr);
|
|
|
|
printf("%s", buf);
|
|
free_tagged(&obj);
|
|
}
|
|
|
|
#define objmap_model(XX, ...) \
|
|
XX(objects, Bar, map, objects, __VA_ARGS__)
|
|
|
|
DECLARE_MODEL(MapOfObjects, objmap_model)
|
|
|
|
IMPL_MODEL(MapOfObjects, objmap_model)
|
|
|
|
TEST_CASE("map of objects", "[model]") {
|
|
const char *json = "{"
|
|
"\"objects\":{"
|
|
"\"bar1\":" BAR1 ","
|
|
"\"bar2\":" BAR2
|
|
"}}";
|
|
|
|
MapOfObjects m;
|
|
REQUIRE(parse_MapOfObjects(&m, json, strlen(json)) == 0);
|
|
|
|
Bar *b1 = static_cast<Bar *>(model_map_get(&m.objects, "bar1"));
|
|
Bar *b2 = static_cast<Bar *>(model_map_get(&m.objects, "bar2"));
|
|
|
|
REQUIRE(b1 != nullptr);
|
|
REQUIRE(b2 != nullptr);
|
|
CHECK(b1->num == 42);
|
|
CHECK(!b1->isOK);
|
|
|
|
CHECK_THAT(b1->msg, Equals("this is a message"));
|
|
|
|
char *js = MapOfObjects_to_json(&m, 0, nullptr);
|
|
|
|
std::cout << js << std::endl;
|
|
|
|
free(js);
|
|
|
|
free_MapOfObjects(&m);
|
|
}
|
|
|
|
#define basket_model(XX, ...) \
|
|
XX(json_fruits,json,map, json_fruits,__VA_ARGS__) \
|
|
XX(fruits,Fruit,map,fruits,__VA_ARGS__) \
|
|
XX(strings,string,map,strings,__VA_ARGS__)
|
|
|
|
#define fruit_model(XX, ...) \
|
|
XX(color,string,none,color,__VA_ARGS__) \
|
|
XX(count,int,none,count,__VA_ARGS__)
|
|
|
|
DECLARE_MODEL(Fruit, fruit_model)
|
|
|
|
IMPL_MODEL(Fruit, fruit_model)
|
|
|
|
DECLARE_MODEL(Basket, basket_model)
|
|
|
|
IMPL_MODEL(Basket, basket_model)
|
|
|
|
|
|
TEST_CASE("map compare", "[model]") {
|
|
const char *json1 = R"({
|
|
"fruits" : {
|
|
"orange" : {
|
|
"color": "orange",
|
|
"count": 1
|
|
},
|
|
"apple": {
|
|
"color": "red",
|
|
"count": 2
|
|
}
|
|
},
|
|
"json_fruits" : {
|
|
"orange" : {
|
|
"color": "orange",
|
|
"count": 1
|
|
},
|
|
"apple": {
|
|
"color": "red",
|
|
"count": 2
|
|
}
|
|
},
|
|
"strings" : {
|
|
"one": "1",
|
|
"two": "2"
|
|
}
|
|
})";
|
|
|
|
const char *json2 = R"({
|
|
"json_fruits" : {
|
|
"orange" : {
|
|
"color": "orange",
|
|
"count": 1
|
|
}
|
|
},
|
|
"fruits" : {
|
|
"orange" : {
|
|
"color": "orange",
|
|
"count": 1
|
|
}
|
|
},
|
|
"strings" : {
|
|
"two": "2"
|
|
}
|
|
})";
|
|
|
|
Basket b1, b2;
|
|
parse_Basket(&b1, json1, strlen(json1));
|
|
parse_Basket(&b2, json2, strlen(json2));
|
|
|
|
int rc = cmp_Basket(&b1, &b2);
|
|
CHECK(rc != 0);
|
|
|
|
char *apple = (char *) model_map_remove(&b1.json_fruits, "apple");
|
|
free(apple);
|
|
|
|
rc = cmp_Basket(&b1, &b2);
|
|
CHECK(rc != 0);
|
|
|
|
Fruit *app = (Fruit *) model_map_remove(&b1.fruits, "apple");
|
|
CHECK(app->count == 2);
|
|
CHECK_THAT(app->color, Matches("red"));
|
|
free_Fruit(app);
|
|
free(app);
|
|
|
|
char *one = (char *) model_map_remove(&b1.strings, "one");
|
|
CHECK_THAT(one, Matches("1"));
|
|
free(one);
|
|
|
|
rc = cmp_Basket(&b1, &b2);
|
|
CHECK(rc == 0);
|
|
|
|
free_Basket(&b1);
|
|
free_Basket(&b2);
|
|
}
|
|
|
|
TEST_CASE("parse-json-u-escape", "[model]") {
|
|
const char *json = "{"
|
|
"\"bar\":{"
|
|
"\"msg\":\"hello\\u000C\\u0430\\u0431\\u0432\\u0433\\u0434!\""
|
|
"}"
|
|
"}";
|
|
Foo foo;
|
|
REQUIRE(parse_Foo(&foo, json, strlen(json)) == 0);
|
|
|
|
CHECK_THAT(foo.bar.msg, Catch::Matchers::Equals("hello\u000cабвгд!"));
|
|
|
|
size_t json_len;
|
|
char *json_out = Foo_to_json(&foo, 0, &json_len);
|
|
CHECK(json_len == strlen(json_out));
|
|
CHECK_THAT(json_out, Catch::Matchers::Contains("\"hello\\u000cабвгд!\""));
|
|
free(json_out);
|
|
}
|
|
|
|
TEST_CASE("parse-bad-json-escapes", "[model]") {
|
|
const char *json[] = {
|
|
// short u escape
|
|
"{"
|
|
"\"bar\":{"
|
|
"\"msg\":\"hello\\u00C\\u0430\\u0431\\u0432\\u0433\\u0434!\""
|
|
"}"
|
|
"}",
|
|
// invalid char in u string
|
|
"{"
|
|
"\"bar\":{"
|
|
"\"msg\":\"hello\\u00OC\\u0430\\u0431\\u0432\\u0433\\u0434!\""
|
|
"}"
|
|
"}",
|
|
// short escape at the end
|
|
"{\"bar\":{"
|
|
"\"msg\":\"hello\\u000C\\u0430\\u0431\\u0432\\u0433\\u\""
|
|
"}}",
|
|
nullptr
|
|
};
|
|
for (int i = 0; json[i] != nullptr; i++) {
|
|
Foo foo;
|
|
CHECK(parse_Foo(&foo, json[i], strlen(json[i])) < 0);
|
|
}
|
|
|
|
} |