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:
Peter Chen
2024-09-19 10:10:04 -04:00
committed by GitHub
parent 0282504f18
commit af4fde9a3a
26 changed files with 2075 additions and 352 deletions

View File

@@ -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";

View File

@@ -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)

View File

@@ -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));
}

View File

@@ -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>(); }, ".*");
}

View File

@@ -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);
}

View File

@@ -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);
},
".*"
);
}

View 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"), ".*");
}

View File

@@ -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); }, ".*");
}

View File

@@ -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>