mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-01 00:55:51 +00:00
refactor: Clio Config (#1593)
Add constraint + parse json into Config Second part of refactoring Clio Config; First PR found [here](https://github.com/XRPLF/clio/pull/1544) Steps that are left to implement: - Replacing all the places where we fetch config values (by using config.valueOr/MaybeValue) to instead get it from Config Definition - Generate markdown file using Clio Config Description
This commit is contained in:
@@ -20,13 +20,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "util/newconfig/Array.hpp"
|
||||
#include "util/newconfig/ConfigConstraints.hpp"
|
||||
#include "util/newconfig/ConfigDefinition.hpp"
|
||||
#include "util/newconfig/ConfigValue.hpp"
|
||||
#include "util/newconfig/Types.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace util::config;
|
||||
|
||||
/**
|
||||
* @brief A mock ClioConfigDefinition for testing purposes.
|
||||
*
|
||||
* In the actual Clio configuration, arrays typically hold optional values, meaning users are not required to
|
||||
* provide values for them.
|
||||
*
|
||||
* For primitive types (i.e., single specific values), some are mandatory and must be explicitly defined in the
|
||||
* user's configuration file, including both the key and the corresponding value, while some are optional
|
||||
*/
|
||||
|
||||
inline ClioConfigDefinition
|
||||
generateConfig()
|
||||
{
|
||||
@@ -36,60 +48,167 @@ generateConfig()
|
||||
{"header.admin", ConfigValue{ConfigType::Boolean}.defaultValue(true)},
|
||||
{"header.sub.sub2Value", ConfigValue{ConfigType::String}.defaultValue("TSM")},
|
||||
{"ip", ConfigValue{ConfigType::Double}.defaultValue(444.22)},
|
||||
{"array.[].sub",
|
||||
Array{
|
||||
ConfigValue{ConfigType::Double}.defaultValue(111.11), ConfigValue{ConfigType::Double}.defaultValue(4321.55)
|
||||
}},
|
||||
{"array.[].sub2",
|
||||
Array{
|
||||
ConfigValue{ConfigType::String}.defaultValue("subCategory"),
|
||||
ConfigValue{ConfigType::String}.defaultValue("temporary")
|
||||
}},
|
||||
{"higher.[].low.section", Array{ConfigValue{ConfigType::String}.defaultValue("true")}},
|
||||
{"higher.[].low.admin", Array{ConfigValue{ConfigType::Boolean}.defaultValue(false)}},
|
||||
{"dosguard.whitelist.[]",
|
||||
Array{
|
||||
ConfigValue{ConfigType::String}.defaultValue("125.5.5.2"),
|
||||
ConfigValue{ConfigType::String}.defaultValue("204.2.2.2")
|
||||
}},
|
||||
{"dosguard.port", ConfigValue{ConfigType::Integer}.defaultValue(55555)}
|
||||
{"array.[].sub", Array{ConfigValue{ConfigType::Double}}},
|
||||
{"array.[].sub2", Array{ConfigValue{ConfigType::String}.optional()}},
|
||||
{"higher.[].low.section", Array{ConfigValue{ConfigType::String}.withConstraint(validateChannelName)}},
|
||||
{"higher.[].low.admin", Array{ConfigValue{ConfigType::Boolean}}},
|
||||
{"dosguard.whitelist.[]", Array{ConfigValue{ConfigType::String}.optional()}},
|
||||
{"dosguard.port", ConfigValue{ConfigType::Integer}.defaultValue(55555).withConstraint(validatePort)},
|
||||
{"optional.withDefault", ConfigValue{ConfigType::Double}.defaultValue(0.0).optional()},
|
||||
{"optional.withNoDefault", ConfigValue{ConfigType::Double}.optional()},
|
||||
{"requireValue", ConfigValue{ConfigType::String}}
|
||||
};
|
||||
}
|
||||
|
||||
/* The config definition above would look like this structure in config.json:
|
||||
"header": {
|
||||
"text1": "value",
|
||||
"port": 123,
|
||||
"admin": true,
|
||||
"sub": {
|
||||
"sub2Value": "TSM"
|
||||
}
|
||||
},
|
||||
"ip": 444.22,
|
||||
"array": [
|
||||
{
|
||||
"sub": 111.11,
|
||||
"sub2": "subCategory"
|
||||
},
|
||||
{
|
||||
"sub": 4321.55,
|
||||
"sub2": "temporary"
|
||||
}
|
||||
],
|
||||
"higher": [
|
||||
{
|
||||
"low": {
|
||||
"section": "true",
|
||||
"admin": false
|
||||
/* The config definition above would look like this structure in config.json
|
||||
{
|
||||
"header": {
|
||||
"text1": "value",
|
||||
"port": 321,
|
||||
"admin": true,
|
||||
"sub": {
|
||||
"sub2Value": "TSM"
|
||||
}
|
||||
}
|
||||
],
|
||||
"dosguard": {
|
||||
"whitelist": [
|
||||
"125.5.5.2", "204.2.2.2"
|
||||
],
|
||||
"port" : 55555
|
||||
},
|
||||
|
||||
|
||||
},
|
||||
"ip": 444.22,
|
||||
"array": [
|
||||
{
|
||||
"sub": //optional for user to include
|
||||
"sub2": //optional for user to include
|
||||
},
|
||||
],
|
||||
"higher": [
|
||||
{
|
||||
"low": {
|
||||
"section": //optional for user to include
|
||||
"admin": //optional for user to include
|
||||
}
|
||||
}
|
||||
],
|
||||
"dosguard": {
|
||||
"whitelist": [
|
||||
// mandatory for user to include
|
||||
],
|
||||
"port" : 55555
|
||||
},
|
||||
},
|
||||
"optional" : {
|
||||
"withDefault" : 0.0,
|
||||
"withNoDefault" : //optional for user to include
|
||||
},
|
||||
"requireValue" : // value must be provided by user
|
||||
}
|
||||
*/
|
||||
|
||||
/* Used to test overwriting default values in ClioConfigDefinition Above */
|
||||
constexpr static auto JSONData = R"JSON(
|
||||
{
|
||||
"header": {
|
||||
"text1": "value",
|
||||
"port": 321,
|
||||
"admin": false,
|
||||
"sub": {
|
||||
"sub2Value": "TSM"
|
||||
}
|
||||
},
|
||||
"array": [
|
||||
{
|
||||
"sub": 111.11,
|
||||
"sub2": "subCategory"
|
||||
},
|
||||
{
|
||||
"sub": 4321.55,
|
||||
"sub2": "temporary"
|
||||
},
|
||||
{
|
||||
"sub": 5555.44,
|
||||
"sub2": "london"
|
||||
}
|
||||
],
|
||||
"higher": [
|
||||
{
|
||||
"low": {
|
||||
"section": "WebServer",
|
||||
"admin": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"dosguard": {
|
||||
"whitelist": [
|
||||
"125.5.5.1", "204.2.2.1"
|
||||
],
|
||||
"port" : 44444
|
||||
},
|
||||
"optional" : {
|
||||
"withDefault" : 0.0
|
||||
},
|
||||
"requireValue" : "required"
|
||||
}
|
||||
)JSON";
|
||||
|
||||
/* After parsing jsonValue and populating it into ClioConfig, It will look like this below in json format;
|
||||
{
|
||||
"header": {
|
||||
"text1": "value",
|
||||
"port": 321,
|
||||
"admin": false,
|
||||
"sub": {
|
||||
"sub2Value": "TSM"
|
||||
}
|
||||
},
|
||||
"ip": 444.22,
|
||||
"array": [
|
||||
{
|
||||
"sub": 111.11,
|
||||
"sub2": "subCategory"
|
||||
},
|
||||
{
|
||||
"sub": 4321.55,
|
||||
"sub2": "temporary"
|
||||
},
|
||||
{
|
||||
"sub": 5555.44,
|
||||
"sub2": "london"
|
||||
}
|
||||
],
|
||||
"higher": [
|
||||
{
|
||||
"low": {
|
||||
"section": "WebServer",
|
||||
"admin": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"dosguard": {
|
||||
"whitelist": [
|
||||
"125.5.5.1", "204.2.2.1"
|
||||
],
|
||||
"port" : 44444
|
||||
}
|
||||
},
|
||||
"optional" : {
|
||||
"withDefault" : 0.0
|
||||
},
|
||||
"requireValue" : "required"
|
||||
}
|
||||
*/
|
||||
|
||||
// Invalid Json key/values
|
||||
constexpr static auto invalidJSONData = R"JSON(
|
||||
{
|
||||
"header": {
|
||||
"port": "999",
|
||||
"admin": "true"
|
||||
},
|
||||
"dosguard": {
|
||||
"whitelist": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"idk": true,
|
||||
"requireValue" : "required",
|
||||
"optional" : {
|
||||
"withDefault" : "0.0"
|
||||
}
|
||||
}
|
||||
)JSON";
|
||||
|
||||
@@ -133,12 +133,13 @@ target_sources(
|
||||
web/RPCServerHandlerTests.cpp
|
||||
web/ServerTests.cpp
|
||||
# New Config
|
||||
util/newconfig/ArrayViewTests.cpp
|
||||
util/newconfig/ObjectViewTests.cpp
|
||||
util/newconfig/ValueViewTests.cpp
|
||||
util/newconfig/ArrayTests.cpp
|
||||
util/newconfig/ConfigValueTests.cpp
|
||||
util/newconfig/ArrayViewTests.cpp
|
||||
util/newconfig/ClioConfigDefinitionTests.cpp
|
||||
util/newconfig/ConfigValueTests.cpp
|
||||
util/newconfig/ObjectViewTests.cpp
|
||||
util/newconfig/JsonConfigFileTests.cpp
|
||||
util/newconfig/ValueViewTests.cpp
|
||||
)
|
||||
|
||||
configure_file(test_data/cert.pem ${CMAKE_BINARY_DIR}/tests/unit/test_data/cert.pem COPYONLY)
|
||||
|
||||
@@ -18,33 +18,69 @@
|
||||
//==============================================================================
|
||||
|
||||
#include "util/newconfig/Array.hpp"
|
||||
#include "util/newconfig/ConfigConstraints.hpp"
|
||||
#include "util/newconfig/ConfigValue.hpp"
|
||||
#include "util/newconfig/Types.hpp"
|
||||
#include "util/newconfig/ValueView.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
using namespace util::config;
|
||||
|
||||
TEST(ArrayTest, testConfigArray)
|
||||
TEST(ArrayTest, addSingleValue)
|
||||
{
|
||||
auto arr = Array{
|
||||
ConfigValue{ConfigType::Boolean}.defaultValue(false),
|
||||
ConfigValue{ConfigType::Integer}.defaultValue(1234),
|
||||
ConfigValue{ConfigType::Double}.defaultValue(22.22),
|
||||
};
|
||||
auto cv = arr.at(0);
|
||||
ValueView const vv{cv};
|
||||
EXPECT_EQ(vv.asBool(), false);
|
||||
auto arr = Array{ConfigValue{ConfigType::Double}};
|
||||
arr.addValue(111.11);
|
||||
EXPECT_EQ(arr.size(), 1);
|
||||
}
|
||||
|
||||
auto cv2 = arr.at(1);
|
||||
ValueView const vv2{cv2};
|
||||
EXPECT_EQ(vv2.asIntType<int>(), 1234);
|
||||
TEST(ArrayTest, addAndCheckMultipleValues)
|
||||
{
|
||||
auto arr = Array{ConfigValue{ConfigType::Double}};
|
||||
arr.addValue(111.11);
|
||||
arr.addValue(222.22);
|
||||
arr.addValue(333.33);
|
||||
EXPECT_EQ(arr.size(), 3);
|
||||
|
||||
auto const cv = arr.at(0);
|
||||
ValueView vv{cv};
|
||||
EXPECT_EQ(vv.asDouble(), 111.11);
|
||||
|
||||
auto const cv2 = arr.at(1);
|
||||
ValueView vv2{cv2};
|
||||
EXPECT_EQ(vv2.asDouble(), 222.22);
|
||||
|
||||
EXPECT_EQ(arr.size(), 3);
|
||||
arr.emplaceBack(ConfigValue{ConfigType::String}.defaultValue("false"));
|
||||
arr.addValue(444.44);
|
||||
|
||||
EXPECT_EQ(arr.size(), 4);
|
||||
auto cv4 = arr.at(3);
|
||||
ValueView const vv4{cv4};
|
||||
EXPECT_EQ(vv4.asString(), "false");
|
||||
auto const cv4 = arr.at(3);
|
||||
ValueView vv4{cv4};
|
||||
EXPECT_EQ(vv4.asDouble(), 444.44);
|
||||
}
|
||||
|
||||
TEST(ArrayTest, testArrayPattern)
|
||||
{
|
||||
auto const arr = Array{ConfigValue{ConfigType::String}};
|
||||
auto const arrPattern = arr.getArrayPattern();
|
||||
EXPECT_EQ(arrPattern.type(), ConfigType::String);
|
||||
}
|
||||
|
||||
TEST(ArrayTest, iterateValueArray)
|
||||
{
|
||||
auto arr = Array{ConfigValue{ConfigType::Integer}.withConstraint(validateUint16)};
|
||||
std::vector<int64_t> const expected{543, 123, 909};
|
||||
|
||||
for (auto const num : expected)
|
||||
arr.addValue(num);
|
||||
|
||||
std::vector<int64_t> actual;
|
||||
for (auto it = arr.begin(); it != arr.end(); ++it)
|
||||
actual.emplace_back(std::get<int64_t>(it->getValue()));
|
||||
|
||||
EXPECT_TRUE(std::ranges::equal(expected, actual));
|
||||
}
|
||||
|
||||
@@ -19,11 +19,13 @@
|
||||
|
||||
#include "util/newconfig/ArrayView.hpp"
|
||||
#include "util/newconfig/ConfigDefinition.hpp"
|
||||
#include "util/newconfig/ConfigValue.hpp"
|
||||
#include "util/newconfig/ConfigFileJson.hpp"
|
||||
#include "util/newconfig/FakeConfigData.hpp"
|
||||
#include "util/newconfig/ObjectView.hpp"
|
||||
#include "util/newconfig/Types.hpp"
|
||||
#include "util/newconfig/ValueView.hpp"
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstddef>
|
||||
@@ -31,33 +33,65 @@
|
||||
using namespace util::config;
|
||||
|
||||
struct ArrayViewTest : testing::Test {
|
||||
ClioConfigDefinition const configData = generateConfig();
|
||||
ArrayViewTest()
|
||||
{
|
||||
ConfigFileJson const jsonFileObj{boost::json::parse(JSONData).as_object()};
|
||||
auto const errors = configData.parse(jsonFileObj);
|
||||
EXPECT_TRUE(!errors.has_value());
|
||||
}
|
||||
ClioConfigDefinition configData = generateConfig();
|
||||
};
|
||||
|
||||
TEST_F(ArrayViewTest, ArrayValueTest)
|
||||
// Array View tests can only be tested after the values are populated from user Config
|
||||
// into ConfigClioDefinition
|
||||
TEST_F(ArrayViewTest, ArrayGetValueDouble)
|
||||
{
|
||||
ArrayView const arrVals = configData.getArray("array.[].sub");
|
||||
auto valIt = arrVals.begin<ValueView>();
|
||||
auto const precision = 1e-9;
|
||||
EXPECT_NEAR((*valIt++).asDouble(), 111.11, precision);
|
||||
EXPECT_NEAR((*valIt++).asDouble(), 4321.55, precision);
|
||||
EXPECT_EQ(valIt, arrVals.end<ValueView>());
|
||||
ArrayView const arrVals = configData.getArray("array.[].sub");
|
||||
|
||||
EXPECT_NEAR(111.11, arrVals.valueAt(0).asDouble(), precision);
|
||||
auto const firstVal = arrVals.valueAt(0);
|
||||
EXPECT_EQ(firstVal.type(), ConfigType::Double);
|
||||
EXPECT_TRUE(firstVal.hasValue());
|
||||
EXPECT_FALSE(firstVal.isOptional());
|
||||
|
||||
EXPECT_NEAR(111.11, firstVal.asDouble(), precision);
|
||||
EXPECT_NEAR(4321.55, arrVals.valueAt(1).asDouble(), precision);
|
||||
|
||||
ArrayView const arrVals2 = configData.getArray("array.[].sub2");
|
||||
auto val2It = arrVals2.begin<ValueView>();
|
||||
EXPECT_EQ((*val2It++).asString(), "subCategory");
|
||||
EXPECT_EQ((*val2It++).asString(), "temporary");
|
||||
EXPECT_EQ(val2It, arrVals2.end<ValueView>());
|
||||
|
||||
ValueView const tempVal = arrVals2.valueAt(0);
|
||||
EXPECT_EQ(tempVal.type(), ConfigType::String);
|
||||
EXPECT_EQ("subCategory", tempVal.asString());
|
||||
}
|
||||
|
||||
TEST_F(ArrayViewTest, ArrayWithObjTest)
|
||||
TEST_F(ArrayViewTest, ArrayGetValueString)
|
||||
{
|
||||
ArrayView const arrVals = configData.getArray("array.[].sub2");
|
||||
ValueView const firstVal = arrVals.valueAt(0);
|
||||
|
||||
EXPECT_EQ(firstVal.type(), ConfigType::String);
|
||||
EXPECT_EQ("subCategory", firstVal.asString());
|
||||
EXPECT_EQ("london", arrVals.valueAt(2).asString());
|
||||
}
|
||||
|
||||
TEST_F(ArrayViewTest, IterateValuesDouble)
|
||||
{
|
||||
auto const precision = 1e-9;
|
||||
ArrayView const arrVals = configData.getArray("array.[].sub");
|
||||
|
||||
auto valIt = arrVals.begin<ValueView>();
|
||||
EXPECT_NEAR((*valIt++).asDouble(), 111.11, precision);
|
||||
EXPECT_NEAR((*valIt++).asDouble(), 4321.55, precision);
|
||||
EXPECT_NEAR((*valIt++).asDouble(), 5555.44, precision);
|
||||
EXPECT_EQ(valIt, arrVals.end<ValueView>());
|
||||
}
|
||||
|
||||
TEST_F(ArrayViewTest, IterateValuesString)
|
||||
{
|
||||
ArrayView const arrVals = configData.getArray("array.[].sub2");
|
||||
|
||||
auto val2It = arrVals.begin<ValueView>();
|
||||
EXPECT_EQ((*val2It++).asString(), "subCategory");
|
||||
EXPECT_EQ((*val2It++).asString(), "temporary");
|
||||
EXPECT_EQ((*val2It++).asString(), "london");
|
||||
EXPECT_EQ(val2It, arrVals.end<ValueView>());
|
||||
}
|
||||
|
||||
TEST_F(ArrayViewTest, ArrayWithObj)
|
||||
{
|
||||
ArrayView const arrVals = configData.getArray("array.[]");
|
||||
ArrayView const arrValAlt = configData.getArray("array");
|
||||
@@ -73,20 +107,19 @@ TEST_F(ArrayViewTest, IterateArray)
|
||||
{
|
||||
auto arr = configData.getArray("dosguard.whitelist");
|
||||
EXPECT_EQ(2, arr.size());
|
||||
EXPECT_EQ(arr.valueAt(0).asString(), "125.5.5.2");
|
||||
EXPECT_EQ(arr.valueAt(1).asString(), "204.2.2.2");
|
||||
EXPECT_EQ(arr.valueAt(0).asString(), "125.5.5.1");
|
||||
EXPECT_EQ(arr.valueAt(1).asString(), "204.2.2.1");
|
||||
|
||||
auto it = arr.begin<ValueView>();
|
||||
EXPECT_EQ((*it++).asString(), "125.5.5.2");
|
||||
EXPECT_EQ((*it++).asString(), "204.2.2.2");
|
||||
EXPECT_EQ((*it++).asString(), "125.5.5.1");
|
||||
EXPECT_EQ((*it++).asString(), "204.2.2.1");
|
||||
EXPECT_EQ((it), arr.end<ValueView>());
|
||||
}
|
||||
|
||||
TEST_F(ArrayViewTest, DifferentArrayIterators)
|
||||
TEST_F(ArrayViewTest, CompareDifferentArrayIterators)
|
||||
{
|
||||
auto const subArray = configData.getArray("array.[].sub");
|
||||
auto const dosguardArray = configData.getArray("dosguard.whitelist.[]");
|
||||
ASSERT_EQ(subArray.size(), dosguardArray.size());
|
||||
|
||||
auto itArray = subArray.begin<ValueView>();
|
||||
auto itDosguard = dosguardArray.begin<ValueView>();
|
||||
@@ -98,7 +131,7 @@ TEST_F(ArrayViewTest, DifferentArrayIterators)
|
||||
TEST_F(ArrayViewTest, IterateObject)
|
||||
{
|
||||
auto arr = configData.getArray("array");
|
||||
EXPECT_EQ(2, arr.size());
|
||||
EXPECT_EQ(3, arr.size());
|
||||
|
||||
auto it = arr.begin<ObjectView>();
|
||||
EXPECT_EQ(111.11, (*it).getValue("sub").asDouble());
|
||||
@@ -107,33 +140,37 @@ TEST_F(ArrayViewTest, IterateObject)
|
||||
EXPECT_EQ(4321.55, (*it).getValue("sub").asDouble());
|
||||
EXPECT_EQ("temporary", (*it++).getValue("sub2").asString());
|
||||
|
||||
EXPECT_EQ(5555.44, (*it).getValue("sub").asDouble());
|
||||
EXPECT_EQ("london", (*it++).getValue("sub2").asString());
|
||||
|
||||
EXPECT_EQ(it, arr.end<ObjectView>());
|
||||
}
|
||||
|
||||
struct ArrayViewDeathTest : ArrayViewTest {};
|
||||
|
||||
TEST_F(ArrayViewDeathTest, IncorrectAccess)
|
||||
TEST_F(ArrayViewDeathTest, AccessArrayOutOfBounce)
|
||||
{
|
||||
ArrayView const arr = configData.getArray("higher");
|
||||
// dies because higher only has 1 object (trying to access 2nd element)
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto _ = configData.getArray("higher").objectAt(1); }, ".*");
|
||||
}
|
||||
|
||||
// dies because higher only has 1 object
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto _ = arr.objectAt(1); }, ".*");
|
||||
|
||||
ArrayView const arrVals2 = configData.getArray("array.[].sub2");
|
||||
ValueView const tempVal = arrVals2.valueAt(0);
|
||||
|
||||
// dies because array.[].sub2 only has 2 config values
|
||||
EXPECT_DEATH([[maybe_unused]] auto _ = arrVals2.valueAt(2), ".*");
|
||||
TEST_F(ArrayViewDeathTest, AccessIndexOfWrongType)
|
||||
{
|
||||
auto const& arrVals2 = configData.getArray("array.[].sub2");
|
||||
auto const& tempVal = arrVals2.valueAt(0);
|
||||
|
||||
// dies as value is not of type int
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto _ = tempVal.asIntType<int>(); }, ".*");
|
||||
}
|
||||
|
||||
TEST_F(ArrayViewDeathTest, IncorrectIterateAccess)
|
||||
TEST_F(ArrayViewDeathTest, GetValueWhenItIsObject)
|
||||
{
|
||||
ArrayView const arr = configData.getArray("higher");
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto _ = arr.begin<ValueView>(); }, ".*");
|
||||
}
|
||||
|
||||
TEST_F(ArrayViewDeathTest, GetObjectWhenItIsValue)
|
||||
{
|
||||
ArrayView const dosguardWhitelist = configData.getArray("dosguard.whitelist");
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto _ = dosguardWhitelist.begin<ObjectView>(); }, ".*");
|
||||
}
|
||||
|
||||
@@ -20,17 +20,27 @@
|
||||
#include "util/newconfig/ArrayView.hpp"
|
||||
#include "util/newconfig/ConfigDefinition.hpp"
|
||||
#include "util/newconfig/ConfigDescription.hpp"
|
||||
#include "util/newconfig/ConfigValue.hpp"
|
||||
#include "util/newconfig/ConfigFileJson.hpp"
|
||||
#include "util/newconfig/FakeConfigData.hpp"
|
||||
#include "util/newconfig/Types.hpp"
|
||||
#include "util/newconfig/ValueView.hpp"
|
||||
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <boost/json/value_to.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
using namespace util::config;
|
||||
|
||||
// TODO: parsing config file and populating into config will be here once implemented
|
||||
struct NewConfigTest : testing::Test {
|
||||
ClioConfigDefinition const configData = generateConfig();
|
||||
};
|
||||
@@ -45,12 +55,9 @@ TEST_F(NewConfigTest, fetchValues)
|
||||
EXPECT_EQ(true, configData.getValue("header.admin").asBool());
|
||||
EXPECT_EQ("TSM", configData.getValue("header.sub.sub2Value").asString());
|
||||
EXPECT_EQ(444.22, configData.getValue("ip").asDouble());
|
||||
|
||||
auto const v2 = configData.getValueInArray("dosguard.whitelist", 0);
|
||||
EXPECT_EQ(v2.asString(), "125.5.5.2");
|
||||
}
|
||||
|
||||
TEST_F(NewConfigTest, fetchObject)
|
||||
TEST_F(NewConfigTest, fetchObjectDirectly)
|
||||
{
|
||||
auto const obj = configData.getObject("header");
|
||||
EXPECT_TRUE(obj.containsKey("sub.sub2Value"));
|
||||
@@ -58,27 +65,6 @@ TEST_F(NewConfigTest, fetchObject)
|
||||
auto const obj2 = obj.getObject("sub");
|
||||
EXPECT_TRUE(obj2.containsKey("sub2Value"));
|
||||
EXPECT_EQ(obj2.getValue("sub2Value").asString(), "TSM");
|
||||
|
||||
auto const objInArr = configData.getObject("array", 0);
|
||||
auto const obj2InArr = configData.getObject("array", 1);
|
||||
EXPECT_EQ(objInArr.getValue("sub").asDouble(), 111.11);
|
||||
EXPECT_EQ(objInArr.getValue("sub2").asString(), "subCategory");
|
||||
EXPECT_EQ(obj2InArr.getValue("sub").asDouble(), 4321.55);
|
||||
EXPECT_EQ(obj2InArr.getValue("sub2").asString(), "temporary");
|
||||
}
|
||||
|
||||
TEST_F(NewConfigTest, fetchArray)
|
||||
{
|
||||
auto const obj = configData.getObject("dosguard");
|
||||
EXPECT_TRUE(obj.containsKey("whitelist.[]"));
|
||||
|
||||
auto const arr = obj.getArray("whitelist");
|
||||
EXPECT_EQ(2, arr.size());
|
||||
|
||||
auto const sameArr = configData.getArray("dosguard.whitelist");
|
||||
EXPECT_EQ(2, sameArr.size());
|
||||
EXPECT_EQ(sameArr.valueAt(0).asString(), arr.valueAt(0).asString());
|
||||
EXPECT_EQ(sameArr.valueAt(1).asString(), arr.valueAt(1).asString());
|
||||
}
|
||||
|
||||
TEST_F(NewConfigTest, CheckKeys)
|
||||
@@ -91,9 +77,11 @@ TEST_F(NewConfigTest, CheckKeys)
|
||||
EXPECT_TRUE(configData.hasItemsWithPrefix("dosguard"));
|
||||
EXPECT_TRUE(configData.hasItemsWithPrefix("ip"));
|
||||
|
||||
EXPECT_EQ(configData.arraySize("array"), 2);
|
||||
EXPECT_EQ(configData.arraySize("higher"), 1);
|
||||
EXPECT_EQ(configData.arraySize("dosguard.whitelist"), 2);
|
||||
// all arrays currently not populated, only has "itemPattern_" that defines
|
||||
// the type/constraint each configValue will have later on
|
||||
EXPECT_EQ(configData.arraySize("array"), 0);
|
||||
EXPECT_EQ(configData.arraySize("higher"), 0);
|
||||
EXPECT_EQ(configData.arraySize("dosguard.whitelist"), 0);
|
||||
}
|
||||
|
||||
TEST_F(NewConfigTest, CheckAllKeys)
|
||||
@@ -110,7 +98,10 @@ TEST_F(NewConfigTest, CheckAllKeys)
|
||||
"higher.[].low.section",
|
||||
"higher.[].low.admin",
|
||||
"dosguard.whitelist.[]",
|
||||
"dosguard.port"
|
||||
"dosguard.port",
|
||||
"optional.withDefault",
|
||||
"optional.withNoDefault",
|
||||
"requireValue"
|
||||
};
|
||||
|
||||
for (auto i = configData.begin(); i != configData.end(); ++i) {
|
||||
@@ -121,31 +112,42 @@ TEST_F(NewConfigTest, CheckAllKeys)
|
||||
|
||||
struct NewConfigDeathTest : NewConfigTest {};
|
||||
|
||||
TEST_F(NewConfigDeathTest, IncorrectGetValues)
|
||||
TEST_F(NewConfigDeathTest, GetNonExistentKeys)
|
||||
{
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getValue("head"); }, ".*");
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getValue("head."); }, ".*");
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getValue("asdf"); }, ".*");
|
||||
}
|
||||
|
||||
TEST_F(NewConfigDeathTest, GetValueButIsArray)
|
||||
{
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getValue("dosguard.whitelist"); }, ".*");
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getValue("dosguard.whitelist.[]"); }, ".*");
|
||||
}
|
||||
|
||||
TEST_F(NewConfigDeathTest, IncorrectGetObject)
|
||||
TEST_F(NewConfigDeathTest, GetNonExistentObjectKey)
|
||||
{
|
||||
ASSERT_FALSE(configData.contains("head"));
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getObject("head"); }, ".*");
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getObject("array"); }, ".*");
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getObject("array", 2); }, ".*");
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getObject("doesNotExist"); }, ".*");
|
||||
}
|
||||
|
||||
TEST_F(NewConfigDeathTest, IncorrectGetArray)
|
||||
TEST_F(NewConfigDeathTest, GetObjectButIsArray)
|
||||
{
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getObject("array"); }, ".*");
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getObject("array", 2); }, ".*");
|
||||
}
|
||||
|
||||
TEST_F(NewConfigDeathTest, GetArrayButIsValue)
|
||||
{
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getArray("header.text1"); }, ".*");
|
||||
}
|
||||
|
||||
TEST_F(NewConfigDeathTest, GetNonExistentArrayKey)
|
||||
{
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getArray("asdf"); }, ".*");
|
||||
}
|
||||
|
||||
TEST(ConfigDescription, getValues)
|
||||
TEST(ConfigDescription, GetValues)
|
||||
{
|
||||
ClioConfigDescription const definition{};
|
||||
|
||||
@@ -154,10 +156,150 @@ TEST(ConfigDescription, getValues)
|
||||
EXPECT_EQ(definition.get("prometheus.enabled"), "Enable or disable Prometheus metrics.");
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionAssertDeathTest, nonExistingKeyTest)
|
||||
TEST(ConfigDescriptionAssertDeathTest, NonExistingKeyTest)
|
||||
{
|
||||
ClioConfigDescription const definition{};
|
||||
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a = definition.get("data"); }, ".*");
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a = definition.get("etl_source.[]"); }, ".*");
|
||||
}
|
||||
|
||||
/** @brief Testing override the default values with the ones in Json */
|
||||
struct OverrideConfigVals : testing::Test {
|
||||
OverrideConfigVals()
|
||||
{
|
||||
ConfigFileJson const jsonFileObj{boost::json::parse(JSONData).as_object()};
|
||||
auto const errors = configData.parse(jsonFileObj);
|
||||
EXPECT_TRUE(!errors.has_value());
|
||||
}
|
||||
ClioConfigDefinition configData = generateConfig();
|
||||
};
|
||||
|
||||
TEST_F(OverrideConfigVals, ValidateValuesStrings)
|
||||
{
|
||||
// make sure the values in configData are overriden
|
||||
EXPECT_TRUE(configData.contains("header.text1"));
|
||||
EXPECT_EQ(configData.getValue("header.text1").asString(), "value");
|
||||
|
||||
EXPECT_FALSE(configData.contains("header.sub"));
|
||||
EXPECT_TRUE(configData.contains("header.sub.sub2Value"));
|
||||
EXPECT_EQ(configData.getValue("header.sub.sub2Value").asString(), "TSM");
|
||||
|
||||
EXPECT_TRUE(configData.contains("requireValue"));
|
||||
EXPECT_EQ(configData.getValue("requireValue").asString(), "required");
|
||||
}
|
||||
|
||||
TEST_F(OverrideConfigVals, ValidateValuesDouble)
|
||||
{
|
||||
EXPECT_TRUE(configData.contains("optional.withDefault"));
|
||||
EXPECT_EQ(configData.getValue("optional.withDefault").asDouble(), 0.0);
|
||||
|
||||
// make sure the values not overwritten, (default values) are there too
|
||||
EXPECT_TRUE(configData.contains("ip"));
|
||||
EXPECT_EQ(configData.getValue("ip").asDouble(), 444.22);
|
||||
}
|
||||
|
||||
TEST_F(OverrideConfigVals, ValidateValuesInteger)
|
||||
{
|
||||
EXPECT_TRUE(configData.contains("dosguard.port"));
|
||||
EXPECT_EQ(configData.getValue("dosguard.port").asIntType<int>(), 44444);
|
||||
|
||||
EXPECT_TRUE(configData.contains("header.port"));
|
||||
EXPECT_EQ(configData.getValue("header.port").asIntType<int64_t>(), 321);
|
||||
}
|
||||
|
||||
TEST_F(OverrideConfigVals, ValidateValuesBool)
|
||||
{
|
||||
EXPECT_TRUE(configData.contains("header.admin"));
|
||||
EXPECT_EQ(configData.getValue("header.admin").asBool(), false);
|
||||
}
|
||||
|
||||
TEST_F(OverrideConfigVals, ValidateIntegerValuesInArrays)
|
||||
{
|
||||
// Check array values (sub)
|
||||
EXPECT_TRUE(configData.contains("array.[].sub"));
|
||||
auto const arrSub = configData.getArray("array.[].sub");
|
||||
|
||||
std::vector<double> expectedArrSubVal{111.11, 4321.55, 5555.44};
|
||||
std::vector<double> actualArrSubVal{};
|
||||
for (auto it = arrSub.begin<ValueView>(); it != arrSub.end<ValueView>(); ++it) {
|
||||
actualArrSubVal.emplace_back((*it).asDouble());
|
||||
}
|
||||
EXPECT_TRUE(std::ranges::equal(expectedArrSubVal, actualArrSubVal));
|
||||
}
|
||||
|
||||
TEST_F(OverrideConfigVals, ValidateStringValuesInArrays)
|
||||
{
|
||||
// Check array values (sub2)
|
||||
EXPECT_TRUE(configData.contains("array.[].sub2"));
|
||||
auto const arrSub2 = configData.getArray("array.[].sub2");
|
||||
|
||||
std::vector<std::string> expectedArrSub2Val{"subCategory", "temporary", "london"};
|
||||
std::vector<std::string> actualArrSub2Val{};
|
||||
for (auto it = arrSub2.begin<ValueView>(); it != arrSub2.end<ValueView>(); ++it) {
|
||||
actualArrSub2Val.emplace_back((*it).asString());
|
||||
}
|
||||
EXPECT_TRUE(std::ranges::equal(expectedArrSub2Val, actualArrSub2Val));
|
||||
|
||||
// Check dosguard values
|
||||
EXPECT_TRUE(configData.contains("dosguard.whitelist.[]"));
|
||||
auto const dosguard = configData.getArray("dosguard.whitelist.[]");
|
||||
EXPECT_EQ("125.5.5.1", dosguard.valueAt(0).asString());
|
||||
EXPECT_EQ("204.2.2.1", dosguard.valueAt(1).asString());
|
||||
}
|
||||
|
||||
TEST_F(OverrideConfigVals, FetchArray)
|
||||
{
|
||||
auto const obj = configData.getObject("dosguard");
|
||||
EXPECT_TRUE(obj.containsKey("whitelist.[]"));
|
||||
|
||||
auto const arr = obj.getArray("whitelist");
|
||||
EXPECT_EQ(2, arr.size());
|
||||
|
||||
auto const sameArr = configData.getArray("dosguard.whitelist");
|
||||
EXPECT_EQ(2, sameArr.size());
|
||||
EXPECT_EQ(sameArr.valueAt(0).asString(), arr.valueAt(0).asString());
|
||||
EXPECT_EQ(sameArr.valueAt(1).asString(), arr.valueAt(1).asString());
|
||||
}
|
||||
|
||||
TEST_F(OverrideConfigVals, FetchObjectByArray)
|
||||
{
|
||||
auto const objInArr = configData.getObject("array", 0);
|
||||
auto const obj2InArr = configData.getObject("array", 1);
|
||||
auto const obj3InArr = configData.getObject("array", 2);
|
||||
|
||||
EXPECT_EQ(objInArr.getValue("sub").asDouble(), 111.11);
|
||||
EXPECT_EQ(objInArr.getValue("sub2").asString(), "subCategory");
|
||||
EXPECT_EQ(obj2InArr.getValue("sub").asDouble(), 4321.55);
|
||||
EXPECT_EQ(obj2InArr.getValue("sub2").asString(), "temporary");
|
||||
EXPECT_EQ(obj3InArr.getValue("sub").asDouble(), 5555.44);
|
||||
EXPECT_EQ(obj3InArr.getValue("sub2").asString(), "london");
|
||||
}
|
||||
|
||||
struct IncorrectOverrideValues : testing::Test {
|
||||
ClioConfigDefinition configData = generateConfig();
|
||||
};
|
||||
|
||||
TEST_F(IncorrectOverrideValues, InvalidJsonErrors)
|
||||
{
|
||||
ConfigFileJson const jsonFileObj{boost::json::parse(invalidJSONData).as_object()};
|
||||
auto const errors = configData.parse(jsonFileObj);
|
||||
EXPECT_TRUE(errors.has_value());
|
||||
|
||||
// Expected error messages
|
||||
std::unordered_set<std::string_view> expectedErrors{
|
||||
"dosguard.whitelist.[] value does not match type string",
|
||||
"higher.[].low.section key is required in user Config",
|
||||
"higher.[].low.admin key is required in user Config",
|
||||
"array.[].sub key is required in user Config",
|
||||
"header.port value does not match type integer",
|
||||
"header.admin value does not match type boolean",
|
||||
"optional.withDefault value does not match type double"
|
||||
};
|
||||
|
||||
std::unordered_set<std::string_view> actualErrors;
|
||||
for (auto const& error : errors.value()) {
|
||||
actualErrors.insert(error.error);
|
||||
}
|
||||
EXPECT_EQ(expectedErrors, actualErrors);
|
||||
}
|
||||
|
||||
@@ -17,24 +17,207 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include "util/newconfig/ConfigConstraints.hpp"
|
||||
#include "util/newconfig/ConfigValue.hpp"
|
||||
#include "util/newconfig/Types.hpp"
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
using namespace util::config;
|
||||
|
||||
TEST(ConfigValue, testConfigValue)
|
||||
TEST(ConfigValue, GetSetString)
|
||||
{
|
||||
auto cvStr = ConfigValue{ConfigType::String}.defaultValue("12345");
|
||||
auto const cvStr = ConfigValue{ConfigType::String}.defaultValue("12345");
|
||||
EXPECT_EQ(cvStr.type(), ConfigType::String);
|
||||
EXPECT_TRUE(cvStr.hasValue());
|
||||
EXPECT_FALSE(cvStr.isOptional());
|
||||
}
|
||||
|
||||
auto cvInt = ConfigValue{ConfigType::Integer}.defaultValue(543);
|
||||
TEST(ConfigValue, GetSetInteger)
|
||||
{
|
||||
auto const cvInt = ConfigValue{ConfigType::Integer}.defaultValue(543);
|
||||
EXPECT_EQ(cvInt.type(), ConfigType::Integer);
|
||||
EXPECT_TRUE(cvStr.hasValue());
|
||||
EXPECT_FALSE(cvStr.isOptional());
|
||||
EXPECT_TRUE(cvInt.hasValue());
|
||||
EXPECT_FALSE(cvInt.isOptional());
|
||||
|
||||
auto cvOpt = ConfigValue{ConfigType::Integer}.optional();
|
||||
auto const cvOpt = ConfigValue{ConfigType::Integer}.optional();
|
||||
EXPECT_TRUE(cvOpt.isOptional());
|
||||
}
|
||||
|
||||
// A test for each constraint so it's easy to change in the future
|
||||
TEST(ConfigValue, PortConstraint)
|
||||
{
|
||||
auto const portConstraint{PortConstraint{}};
|
||||
EXPECT_FALSE(portConstraint.checkConstraint(4444).has_value());
|
||||
EXPECT_TRUE(portConstraint.checkConstraint(99999).has_value());
|
||||
}
|
||||
|
||||
TEST(ConfigValue, SetValuesOnPortConstraint)
|
||||
{
|
||||
auto cvPort = ConfigValue{ConfigType::Integer}.defaultValue(4444).withConstraint(validatePort);
|
||||
auto const err = cvPort.setValue(99999);
|
||||
EXPECT_TRUE(err.has_value());
|
||||
EXPECT_EQ(err->error, "Port does not satisfy the constraint bounds");
|
||||
EXPECT_TRUE(cvPort.setValue(33.33).has_value());
|
||||
EXPECT_TRUE(cvPort.setValue(33.33).value().error == "value does not match type integer");
|
||||
EXPECT_FALSE(cvPort.setValue(1).has_value());
|
||||
|
||||
auto cvPort2 = ConfigValue{ConfigType::String}.defaultValue("4444").withConstraint(validatePort);
|
||||
auto const strPortError = cvPort2.setValue("100000");
|
||||
EXPECT_TRUE(strPortError.has_value());
|
||||
EXPECT_EQ(strPortError->error, "Port does not satisfy the constraint bounds");
|
||||
}
|
||||
|
||||
TEST(ConfigValue, OneOfConstraintOneValue)
|
||||
{
|
||||
std::array<char const*, 1> const arr = {"tracer"};
|
||||
auto const databaseConstraint{OneOf{"database.type", arr}};
|
||||
EXPECT_FALSE(databaseConstraint.checkConstraint("tracer").has_value());
|
||||
|
||||
EXPECT_TRUE(databaseConstraint.checkConstraint(345).has_value());
|
||||
EXPECT_EQ(databaseConstraint.checkConstraint(345)->error, R"(Key "database.type"'s value must be a string)");
|
||||
|
||||
EXPECT_TRUE(databaseConstraint.checkConstraint("123.44").has_value());
|
||||
EXPECT_EQ(
|
||||
databaseConstraint.checkConstraint("123.44")->error,
|
||||
R"(You provided value "123.44". Key "database.type"'s value must be one of the following: tracer)"
|
||||
);
|
||||
}
|
||||
|
||||
TEST(ConfigValue, OneOfConstraint)
|
||||
{
|
||||
std::array<char const*, 3> const arr = {"123", "trace", "haha"};
|
||||
auto const oneOfCons{OneOf{"log_level", arr}};
|
||||
|
||||
EXPECT_FALSE(oneOfCons.checkConstraint("trace").has_value());
|
||||
|
||||
EXPECT_TRUE(oneOfCons.checkConstraint(345).has_value());
|
||||
EXPECT_EQ(oneOfCons.checkConstraint(345)->error, R"(Key "log_level"'s value must be a string)");
|
||||
|
||||
EXPECT_TRUE(oneOfCons.checkConstraint("PETER_WAS_HERE").has_value());
|
||||
EXPECT_EQ(
|
||||
oneOfCons.checkConstraint("PETER_WAS_HERE")->error,
|
||||
R"(You provided value "PETER_WAS_HERE". Key "log_level"'s value must be one of the following: 123, trace, haha)"
|
||||
);
|
||||
}
|
||||
|
||||
TEST(ConfigValue, IpConstraint)
|
||||
{
|
||||
auto ip = ConfigValue{ConfigType::String}.defaultValue("127.0.0.1").withConstraint(validateIP);
|
||||
EXPECT_FALSE(ip.setValue("http://127.0.0.1").has_value());
|
||||
EXPECT_FALSE(ip.setValue("http://127.0.0.1.com").has_value());
|
||||
auto const err = ip.setValue("123.44");
|
||||
EXPECT_TRUE(err.has_value());
|
||||
EXPECT_EQ(err->error, "Ip is not a valid ip address");
|
||||
EXPECT_FALSE(ip.setValue("126.0.0.2"));
|
||||
|
||||
EXPECT_TRUE(ip.setValue("644.3.3.0"));
|
||||
EXPECT_TRUE(ip.setValue("127.0.0.1.0"));
|
||||
EXPECT_TRUE(ip.setValue(""));
|
||||
EXPECT_TRUE(ip.setValue("http://example..com"));
|
||||
EXPECT_FALSE(ip.setValue("localhost"));
|
||||
EXPECT_FALSE(ip.setValue("http://example.com:8080/path"));
|
||||
}
|
||||
|
||||
TEST(ConfigValue, positiveNumConstraint)
|
||||
{
|
||||
auto const numCons{NumberValueConstraint{0, 5}};
|
||||
EXPECT_FALSE(numCons.checkConstraint(0));
|
||||
EXPECT_FALSE(numCons.checkConstraint(5));
|
||||
|
||||
EXPECT_TRUE(numCons.checkConstraint(true));
|
||||
EXPECT_EQ(numCons.checkConstraint(true)->error, fmt::format("Number must be of type integer"));
|
||||
|
||||
EXPECT_TRUE(numCons.checkConstraint(8));
|
||||
EXPECT_EQ(numCons.checkConstraint(8)->error, fmt::format("Number must be between {} and {}", 0, 5));
|
||||
}
|
||||
|
||||
TEST(ConfigValue, SetValuesOnNumberConstraint)
|
||||
{
|
||||
auto positiveNum = ConfigValue{ConfigType::Integer}.defaultValue(20u).withConstraint(validateUint16);
|
||||
auto const err = positiveNum.setValue(-22, "key");
|
||||
EXPECT_TRUE(err.has_value());
|
||||
EXPECT_EQ(err->error, fmt::format("key Number must be between {} and {}", 0, 65535));
|
||||
EXPECT_FALSE(positiveNum.setValue(99, "key"));
|
||||
}
|
||||
|
||||
TEST(ConfigValue, PositiveDoubleConstraint)
|
||||
{
|
||||
auto const doubleCons{PositiveDouble{}};
|
||||
EXPECT_FALSE(doubleCons.checkConstraint(0.2));
|
||||
EXPECT_FALSE(doubleCons.checkConstraint(5.54));
|
||||
EXPECT_TRUE(doubleCons.checkConstraint("-5"));
|
||||
EXPECT_EQ(doubleCons.checkConstraint("-5")->error, "Double number must be of type int or double");
|
||||
EXPECT_EQ(doubleCons.checkConstraint(-5.6)->error, "Double number must be greater than 0");
|
||||
EXPECT_FALSE(doubleCons.checkConstraint(12.1));
|
||||
}
|
||||
|
||||
struct ConstraintTestBundle {
|
||||
std::string name;
|
||||
Constraint const& cons_;
|
||||
};
|
||||
|
||||
struct ConstraintDeathTest : public testing::Test, public testing::WithParamInterface<ConstraintTestBundle> {};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
EachConstraints,
|
||||
ConstraintDeathTest,
|
||||
testing::Values(
|
||||
ConstraintTestBundle{"logTagConstraint", validateLogTag},
|
||||
ConstraintTestBundle{"portConstraint", validatePort},
|
||||
ConstraintTestBundle{"ipConstraint", validateIP},
|
||||
ConstraintTestBundle{"channelConstraint", validateChannelName},
|
||||
ConstraintTestBundle{"logLevelConstraint", validateLogLevelName},
|
||||
ConstraintTestBundle{"cannsandraNameCnstraint", validateCassandraName},
|
||||
ConstraintTestBundle{"loadModeConstraint", validateLoadMode},
|
||||
ConstraintTestBundle{"ChannelNameConstraint", validateChannelName},
|
||||
ConstraintTestBundle{"ApiVersionConstraint", validateApiVersion},
|
||||
ConstraintTestBundle{"Uint16Constraint", validateUint16},
|
||||
ConstraintTestBundle{"Uint32Constraint", validateUint32},
|
||||
ConstraintTestBundle{"PositiveDoubleConstraint", validatePositiveDouble}
|
||||
),
|
||||
[](testing::TestParamInfo<ConstraintTestBundle> const& info) { return info.param.name; }
|
||||
);
|
||||
|
||||
TEST_P(ConstraintDeathTest, TestEachConstraint)
|
||||
{
|
||||
EXPECT_DEATH(
|
||||
{
|
||||
[[maybe_unused]] auto const a =
|
||||
ConfigValue{ConfigType::Boolean}.defaultValue(true).withConstraint(GetParam().cons_);
|
||||
},
|
||||
".*"
|
||||
);
|
||||
}
|
||||
|
||||
TEST(ConfigValueDeathTest, SetInvalidValueTypeStringAndBool)
|
||||
{
|
||||
EXPECT_DEATH(
|
||||
{
|
||||
[[maybe_unused]] auto a = ConfigValue{ConfigType::String}.defaultValue(33).withConstraint(validateLoadMode);
|
||||
},
|
||||
".*"
|
||||
);
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto a = ConfigValue{ConfigType::Boolean}.defaultValue(-66); }, ".*");
|
||||
}
|
||||
|
||||
TEST(ConfigValueDeathTest, OutOfBounceIntegerConstraint)
|
||||
{
|
||||
EXPECT_DEATH(
|
||||
{
|
||||
[[maybe_unused]] auto a =
|
||||
ConfigValue{ConfigType::Integer}.defaultValue(999999).withConstraint(validateUint16);
|
||||
},
|
||||
".*"
|
||||
);
|
||||
EXPECT_DEATH(
|
||||
{
|
||||
[[maybe_unused]] auto a = ConfigValue{ConfigType::Integer}.defaultValue(-66).withConstraint(validateUint32);
|
||||
},
|
||||
".*"
|
||||
);
|
||||
}
|
||||
|
||||
113
tests/unit/util/newconfig/JsonConfigFileTests.cpp
Normal file
113
tests/unit/util/newconfig/JsonConfigFileTests.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include "util/TmpFile.hpp"
|
||||
#include "util/newconfig/ConfigFileJson.hpp"
|
||||
#include "util/newconfig/FakeConfigData.hpp"
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
TEST(CreateConfigFile, filePath)
|
||||
{
|
||||
auto const jsonFileObj = ConfigFileJson::make_ConfigFileJson(TmpFile(JSONData).path);
|
||||
EXPECT_TRUE(jsonFileObj.has_value());
|
||||
|
||||
EXPECT_TRUE(jsonFileObj->containsKey("array.[].sub"));
|
||||
auto const arrSub = jsonFileObj->getArray("array.[].sub");
|
||||
EXPECT_EQ(arrSub.size(), 3);
|
||||
}
|
||||
|
||||
TEST(CreateConfigFile, incorrectFilePath)
|
||||
{
|
||||
auto const jsonFileObj = util::config::ConfigFileJson::make_ConfigFileJson("123/clio");
|
||||
EXPECT_FALSE(jsonFileObj.has_value());
|
||||
}
|
||||
|
||||
struct ParseJson : testing::Test {
|
||||
ParseJson() : jsonFileObj{boost::json::parse(JSONData).as_object()}
|
||||
{
|
||||
}
|
||||
|
||||
ConfigFileJson const jsonFileObj;
|
||||
};
|
||||
|
||||
TEST_F(ParseJson, validateValues)
|
||||
{
|
||||
EXPECT_TRUE(jsonFileObj.containsKey("header.text1"));
|
||||
EXPECT_EQ(std::get<std::string>(jsonFileObj.getValue("header.text1")), "value");
|
||||
|
||||
EXPECT_TRUE(jsonFileObj.containsKey("header.sub.sub2Value"));
|
||||
EXPECT_EQ(std::get<std::string>(jsonFileObj.getValue("header.sub.sub2Value")), "TSM");
|
||||
|
||||
EXPECT_TRUE(jsonFileObj.containsKey("dosguard.port"));
|
||||
EXPECT_EQ(std::get<int64_t>(jsonFileObj.getValue("dosguard.port")), 44444);
|
||||
|
||||
EXPECT_FALSE(jsonFileObj.containsKey("idk"));
|
||||
EXPECT_FALSE(jsonFileObj.containsKey("optional.withNoDefault"));
|
||||
}
|
||||
|
||||
TEST_F(ParseJson, validateArrayValue)
|
||||
{
|
||||
// validate array.[].sub matches expected values
|
||||
EXPECT_TRUE(jsonFileObj.containsKey("array.[].sub"));
|
||||
auto const arrSub = jsonFileObj.getArray("array.[].sub");
|
||||
EXPECT_EQ(arrSub.size(), 3);
|
||||
|
||||
std::vector<double> expectedArrSubVal{111.11, 4321.55, 5555.44};
|
||||
std::vector<double> actualArrSubVal{};
|
||||
|
||||
for (auto it = arrSub.begin(); it != arrSub.end(); ++it) {
|
||||
ASSERT_TRUE(std::holds_alternative<double>(*it));
|
||||
actualArrSubVal.emplace_back(std::get<double>(*it));
|
||||
}
|
||||
EXPECT_TRUE(std::ranges::equal(expectedArrSubVal, actualArrSubVal));
|
||||
|
||||
// validate array.[].sub2 matches expected values
|
||||
EXPECT_TRUE(jsonFileObj.containsKey("array.[].sub2"));
|
||||
auto const arrSub2 = jsonFileObj.getArray("array.[].sub2");
|
||||
EXPECT_EQ(arrSub2.size(), 3);
|
||||
std::vector<std::string> expectedArrSub2Val{"subCategory", "temporary", "london"};
|
||||
std::vector<std::string> actualArrSub2Val{};
|
||||
|
||||
for (auto it = arrSub2.begin(); it != arrSub2.end(); ++it) {
|
||||
ASSERT_TRUE(std::holds_alternative<std::string>(*it));
|
||||
actualArrSub2Val.emplace_back(std::get<std::string>(*it));
|
||||
}
|
||||
EXPECT_TRUE(std::ranges::equal(expectedArrSub2Val, actualArrSub2Val));
|
||||
|
||||
EXPECT_TRUE(jsonFileObj.containsKey("dosguard.whitelist.[]"));
|
||||
auto const whitelistArr = jsonFileObj.getArray("dosguard.whitelist.[]");
|
||||
EXPECT_EQ(whitelistArr.size(), 2);
|
||||
EXPECT_EQ("125.5.5.1", std::get<std::string>(whitelistArr.at(0)));
|
||||
EXPECT_EQ("204.2.2.1", std::get<std::string>(whitelistArr.at(1)));
|
||||
}
|
||||
|
||||
struct JsonValueDeathTest : ParseJson {};
|
||||
|
||||
TEST_F(JsonValueDeathTest, invalidGetArray)
|
||||
{
|
||||
EXPECT_DEATH([[maybe_unused]] auto a = jsonFileObj.getArray("header.text1"), ".*");
|
||||
}
|
||||
@@ -19,15 +19,23 @@
|
||||
|
||||
#include "util/newconfig/ArrayView.hpp"
|
||||
#include "util/newconfig/ConfigDefinition.hpp"
|
||||
#include "util/newconfig/ConfigFileJson.hpp"
|
||||
#include "util/newconfig/FakeConfigData.hpp"
|
||||
#include "util/newconfig/ObjectView.hpp"
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace util::config;
|
||||
|
||||
struct ObjectViewTest : testing::Test {
|
||||
ClioConfigDefinition const configData = generateConfig();
|
||||
ObjectViewTest()
|
||||
{
|
||||
ConfigFileJson const jsonFileObj{boost::json::parse(JSONData).as_object()};
|
||||
auto const errors = configData.parse(jsonFileObj);
|
||||
EXPECT_TRUE(!errors.has_value());
|
||||
}
|
||||
ClioConfigDefinition configData = generateConfig();
|
||||
};
|
||||
|
||||
TEST_F(ObjectViewTest, ObjectValueTest)
|
||||
@@ -39,14 +47,14 @@ TEST_F(ObjectViewTest, ObjectValueTest)
|
||||
EXPECT_TRUE(headerObj.containsKey("admin"));
|
||||
|
||||
EXPECT_EQ("value", headerObj.getValue("text1").asString());
|
||||
EXPECT_EQ(123, headerObj.getValue("port").asIntType<int>());
|
||||
EXPECT_EQ(true, headerObj.getValue("admin").asBool());
|
||||
EXPECT_EQ(321, headerObj.getValue("port").asIntType<int>());
|
||||
EXPECT_EQ(false, headerObj.getValue("admin").asBool());
|
||||
}
|
||||
|
||||
TEST_F(ObjectViewTest, ObjectInArray)
|
||||
TEST_F(ObjectViewTest, ObjectValuesInArray)
|
||||
{
|
||||
ArrayView const arr = configData.getArray("array");
|
||||
EXPECT_EQ(arr.size(), 2);
|
||||
EXPECT_EQ(arr.size(), 3);
|
||||
ObjectView const firstObj = arr.objectAt(0);
|
||||
ObjectView const secondObj = arr.objectAt(1);
|
||||
EXPECT_TRUE(firstObj.containsKey("sub"));
|
||||
@@ -62,7 +70,7 @@ TEST_F(ObjectViewTest, ObjectInArray)
|
||||
EXPECT_EQ(secondObj.getValue("sub2").asString(), "temporary");
|
||||
}
|
||||
|
||||
TEST_F(ObjectViewTest, ObjectInArrayMoreComplex)
|
||||
TEST_F(ObjectViewTest, GetObjectsInDifferentWays)
|
||||
{
|
||||
ArrayView const arr = configData.getArray("higher");
|
||||
ASSERT_EQ(1, arr.size());
|
||||
@@ -78,7 +86,7 @@ TEST_F(ObjectViewTest, ObjectInArrayMoreComplex)
|
||||
ObjectView const objLow = firstObj.getObject("low");
|
||||
EXPECT_TRUE(objLow.containsKey("section"));
|
||||
EXPECT_TRUE(objLow.containsKey("admin"));
|
||||
EXPECT_EQ(objLow.getValue("section").asString(), "true");
|
||||
EXPECT_EQ(objLow.getValue("section").asString(), "WebServer");
|
||||
EXPECT_EQ(objLow.getValue("admin").asBool(), false);
|
||||
}
|
||||
|
||||
@@ -90,18 +98,25 @@ TEST_F(ObjectViewTest, getArrayInObject)
|
||||
auto const arr = obj.getArray("whitelist");
|
||||
EXPECT_EQ(2, arr.size());
|
||||
|
||||
EXPECT_EQ("125.5.5.2", arr.valueAt(0).asString());
|
||||
EXPECT_EQ("204.2.2.2", arr.valueAt(1).asString());
|
||||
EXPECT_EQ("125.5.5.1", arr.valueAt(0).asString());
|
||||
EXPECT_EQ("204.2.2.1", arr.valueAt(1).asString());
|
||||
}
|
||||
|
||||
struct ObjectViewDeathTest : ObjectViewTest {};
|
||||
|
||||
TEST_F(ObjectViewDeathTest, incorrectKeys)
|
||||
TEST_F(ObjectViewDeathTest, KeyDoesNotExist)
|
||||
{
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto _ = configData.getObject("head"); }, ".*");
|
||||
}
|
||||
|
||||
TEST_F(ObjectViewDeathTest, KeyIsValueView)
|
||||
{
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto _ = configData.getObject("header.text1"); }, ".*");
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto _ = configData.getObject("head"); }, ".*");
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto _ = configData.getArray("header"); }, ".*");
|
||||
}
|
||||
|
||||
TEST_F(ObjectViewDeathTest, KeyisArrayView)
|
||||
{
|
||||
// dies because only 1 object in higher.[].low
|
||||
EXPECT_DEATH({ [[maybe_unused]] auto _ = configData.getObject("higher.[].low", 1); }, ".*");
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "util/newconfig/ConfigDefinition.hpp"
|
||||
#include "util/newconfig/ConfigValue.hpp"
|
||||
#include "util/newconfig/FakeConfigData.hpp"
|
||||
#include "util/newconfig/Types.hpp"
|
||||
#include "util/newconfig/ValueView.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
Reference in New Issue
Block a user