Files
clio/tests/unit/util/config/ConfigValueTests.cpp
2026-03-24 15:25:32 +00:00

312 lines
11 KiB
C++

#include "util/MockAssert.hpp"
#include "util/config/ConfigConstraints.hpp"
#include "util/config/ConfigValue.hpp"
#include "util/config/Error.hpp"
#include "util/config/Types.hpp"
#include <fmt/format.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <array>
#include <optional>
#include <ostream>
#include <string>
#include <string_view>
using namespace util::config;
struct ConfigValueTest : common::util::WithMockAssert {};
TEST_F(ConfigValueTest, construct)
{
ConfigValue const cv{ConfigType::Integer};
EXPECT_FALSE(cv.hasValue());
EXPECT_FALSE(cv.isOptional());
EXPECT_EQ(cv.type(), ConfigType::Integer);
}
TEST_F(ConfigValueTest, optional)
{
auto const cv = ConfigValue{ConfigType::Integer}.optional();
EXPECT_FALSE(cv.hasValue());
EXPECT_TRUE(cv.isOptional());
EXPECT_EQ(cv.type(), ConfigType::Integer);
}
TEST_F(ConfigValueTest, defaultValue)
{
auto const cv = ConfigValue{ConfigType::Integer}.defaultValue(123);
EXPECT_TRUE(cv.hasValue());
EXPECT_FALSE(cv.isOptional());
EXPECT_EQ(cv.type(), ConfigType::Integer);
}
TEST_F(ConfigValueTest, defaultValueWithDescription)
{
auto const cv = ConfigValue{ConfigType::String}.defaultValue("123", "random description");
EXPECT_TRUE(cv.hasValue());
EXPECT_FALSE(cv.isOptional());
EXPECT_EQ(cv.type(), ConfigType::String);
}
TEST_F(ConfigValueTest, invalidDefaultValue)
{
EXPECT_CLIO_ASSERT_FAIL({
[[maybe_unused]] auto const a = ConfigValue{ConfigType::String}.defaultValue(33);
});
}
TEST_F(ConfigValueTest, setValueWrongType)
{
auto cv = ConfigValue{ConfigType::Integer};
auto const err = cv.setValue("123");
EXPECT_TRUE(err.has_value());
}
TEST_F(ConfigValueTest, setValueNormalPath)
{
auto cv = ConfigValue{ConfigType::Integer};
auto const err = cv.setValue(123);
EXPECT_FALSE(err.has_value());
EXPECT_EQ(cv.getValue(), Value{123});
}
struct ConfigValueConstraintTest : ConfigValueTest {
struct MockConstraint : Constraint {
MOCK_METHOD(std::optional<Error>, checkTypeImpl, (Value const&), (const, override));
MOCK_METHOD(std::optional<Error>, checkValueImpl, (Value const&), (const, override));
MOCK_METHOD(void, print, (std::ostream&), (const, override));
};
testing::StrictMock<MockConstraint> constraint;
};
TEST_F(ConfigValueConstraintTest, setValueWithConstraint)
{
auto cv = ConfigValue{ConfigType::Integer}.withConstraint(constraint);
auto const value = Value{123};
EXPECT_CALL(constraint, checkTypeImpl).WillOnce(testing::Return(std::nullopt));
EXPECT_CALL(constraint, checkValueImpl).WillOnce(testing::Return(std::nullopt));
auto const err = cv.setValue(value);
EXPECT_FALSE(err.has_value());
EXPECT_EQ(cv.getValue(), value);
}
TEST_F(ConfigValueConstraintTest, setValueWithConstraintTypeCheckError)
{
auto cv = ConfigValue{ConfigType::Integer}.withConstraint(constraint);
auto const value = 123;
EXPECT_CALL(constraint, checkTypeImpl).WillOnce(testing::Return(Error{"type error"}));
auto const err = cv.setValue(value);
EXPECT_TRUE(err.has_value());
EXPECT_EQ(err->error, "Unknown_key type error");
}
TEST_F(ConfigValueConstraintTest, defaultValueWithConstraint)
{
EXPECT_CALL(constraint, checkTypeImpl).WillOnce(testing::Return(std::nullopt));
EXPECT_CALL(constraint, checkValueImpl).WillOnce(testing::Return(std::nullopt));
auto const cv = ConfigValue{ConfigType::Integer}.defaultValue(123).withConstraint(constraint);
EXPECT_EQ(cv.getValue(), Value{123});
}
TEST_F(ConfigValueConstraintTest, defaultValueWithConstraintCheckError)
{
EXPECT_CLIO_ASSERT_FAIL({
EXPECT_CALL(constraint, checkTypeImpl).WillOnce(testing::Return(std::nullopt));
EXPECT_CALL(constraint, checkValueImpl).WillOnce(testing::Return(Error{"value error"}));
[[maybe_unused]] auto const cv =
ConfigValue{ConfigType::Integer}.defaultValue(123).withConstraint(constraint);
});
}
// A test for each constraint so it's easy to change in the future
struct ConstraintTest : public virtual ::testing::Test {};
TEST_F(ConstraintTest, PortConstraint)
{
auto const portConstraint{PortConstraint{}};
EXPECT_FALSE(portConstraint.checkConstraint(4444).has_value());
EXPECT_TRUE(portConstraint.checkConstraint(99999).has_value());
}
TEST_F(ConstraintTest, SetValuesOnPortConstraint)
{
auto cvPort = ConfigValue{ConfigType::Integer}.defaultValue(4444).withConstraint(gValidatePort);
auto const err = cvPort.setValue(99999);
EXPECT_TRUE(err.has_value());
EXPECT_EQ(err->error, "Unknown_key Port does not satisfy the constraint bounds");
EXPECT_TRUE(cvPort.setValue(33.33).has_value());
EXPECT_EQ(
cvPort.setValue(33.33).value().error, "Unknown_key value does not match type integer"
);
EXPECT_FALSE(cvPort.setValue(1).has_value());
auto cvPort2 =
ConfigValue{ConfigType::String}.defaultValue("4444").withConstraint(gValidatePort);
auto const strPortError = cvPort2.setValue("100000");
EXPECT_TRUE(strPortError.has_value());
EXPECT_EQ(strPortError->error, "Unknown_key Port does not satisfy the constraint bounds");
}
TEST_F(ConstraintTest, OneOfConstraintOneValue)
{
std::array<std::string_view, 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_F(ConstraintTest, OneOfConstraint)
{
std::array<std::string_view, 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_F(ConstraintTest, IpConstraint)
{
auto ip = ConfigValue{ConfigType::String}.withConstraint(gValidateIp);
EXPECT_FALSE(ip.setValue("127.0.0.1"));
EXPECT_FALSE(ip.setValue("localhost"));
EXPECT_FALSE(ip.setValue("some-server.com"));
EXPECT_FALSE(ip.setValue("126.0.0.2"));
EXPECT_FALSE(ip.setValue("valid.host-name.com"));
EXPECT_TRUE(ip.setValue("http://127.0.0.1"));
EXPECT_TRUE(ip.setValue(""));
EXPECT_TRUE(ip.setValue("example..com"));
EXPECT_TRUE(ip.setValue("example.com:8080/path"));
EXPECT_TRUE(ip.setValue("-invalid.com"));
EXPECT_TRUE(ip.setValue("invalid-.com"));
auto const err = ip.setValue("extra$@symbols");
ASSERT_TRUE(err.has_value());
EXPECT_EQ(err->error, "Unknown_key Ip is not a valid ip address or hostname");
}
TEST_F(ConstraintTest, 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_F(ConstraintTest, SetValuesOnNumberConstraint)
{
auto positiveNum =
ConfigValue{ConfigType::Integer}.defaultValue(20u).withConstraint(gValidateUint16);
auto const err = positiveNum.setValue(-22, "key");
EXPECT_TRUE(err.has_value());
EXPECT_EQ(err->error, fmt::format("key Number must be between {} and {}", 1, 65535));
EXPECT_FALSE(positiveNum.setValue(99, "key"));
}
TEST_F(ConstraintTest, PositiveDoubleConstraint)
{
auto const doubleCons{PositiveDouble{}};
EXPECT_FALSE(doubleCons.checkConstraint(0.2));
EXPECT_FALSE(doubleCons.checkConstraint(5.54));
EXPECT_FALSE(doubleCons.checkConstraint(3));
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 or equal to 0"
);
EXPECT_FALSE(doubleCons.checkConstraint(12.1));
}
struct ConstraintTestBundle {
std::string name;
Constraint const& constraint;
};
struct ConstraintAssertTest : common::util::WithMockAssert,
testing::WithParamInterface<ConstraintTestBundle> {};
INSTANTIATE_TEST_SUITE_P(
EachConstraints,
ConstraintAssertTest,
testing::Values(
ConstraintTestBundle{"logTagConstraint", gValidateLogTag},
ConstraintTestBundle{"portConstraint", gValidatePort},
ConstraintTestBundle{"ipConstraint", gValidateIp},
ConstraintTestBundle{"channelConstraint", gValidateChannelName},
ConstraintTestBundle{"logLevelConstraint", gValidateLogLevelName},
ConstraintTestBundle{"cassandraNameConstraint", gValidateCassandraName},
ConstraintTestBundle{"loadModeConstraint", gValidateLoadMode},
ConstraintTestBundle{"ChannelNameConstraint", gValidateChannelName},
ConstraintTestBundle{"ApiVersionConstraint", gValidateApiVersion},
ConstraintTestBundle{"Uint16Constraint", gValidateUint16},
ConstraintTestBundle{"Uint32Constraint", gValidateUint32},
ConstraintTestBundle{"PositiveDoubleConstraint", gValidatePositiveDouble}
),
[](testing::TestParamInfo<ConstraintTestBundle> const& info) { return info.param.name; }
);
TEST_P(ConstraintAssertTest, TestEachConstraint)
{
EXPECT_CLIO_ASSERT_FAIL({
[[maybe_unused]] auto const a =
ConfigValue{ConfigType::Boolean}.defaultValue(true).withConstraint(
GetParam().constraint
);
});
}
TEST_F(ConstraintAssertTest, SetInvalidValueTypeStringAndBool)
{
EXPECT_CLIO_ASSERT_FAIL({
[[maybe_unused]] auto a =
ConfigValue{ConfigType::String}.defaultValue(33).withConstraint(gValidateLoadMode);
});
EXPECT_CLIO_ASSERT_FAIL({
[[maybe_unused]] auto a = ConfigValue{ConfigType::Boolean}.defaultValue(-66);
});
}
TEST_F(ConstraintAssertTest, OutOfBounceIntegerConstraint)
{
EXPECT_CLIO_ASSERT_FAIL({
[[maybe_unused]] auto a =
ConfigValue{ConfigType::Integer}.defaultValue(999999).withConstraint(gValidateUint16);
});
EXPECT_CLIO_ASSERT_FAIL({
[[maybe_unused]] auto a =
ConfigValue{ConfigType::Integer}.defaultValue(-66).withConstraint(gValidateUint32);
});
}