Files
clio/tests/unit/rpc/common/SpecsTests.cpp
2026-03-24 15:25:32 +00:00

283 lines
9.0 KiB
C++

#include "rpc/Errors.hpp"
#include "rpc/common/Checkers.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include <boost/json/array.hpp>
#include <boost/json/value.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
using namespace rpc;
using testing::StrictMock;
struct SpecsTests : testing::Test {
struct RequirementMock {
MOCK_METHOD(MaybeError, verify, (boost::json::value const&, std::string));
};
struct RequirementMockRef {
RequirementMockRef(StrictMock<RequirementMock>& ref) : ref(ref)
{
}
MaybeError
verify(boost::json::value& value, std::string key) const
{
return ref.verify(value, key);
}
StrictMock<RequirementMock>& ref;
};
struct CheckMock {
MOCK_METHOD(std::optional<check::Warning>, check, (boost::json::value const&, std::string));
};
struct CheckMockRef {
CheckMockRef(StrictMock<CheckMock>& ref) : ref(ref)
{
}
std::optional<check::Warning>
check(boost::json::value const& value, std::string key) const
{
return ref.check(value, key);
}
StrictMock<CheckMock>& ref;
};
StrictMock<RequirementMock> requirementMock;
StrictMock<RequirementMock> anotherRequirementMock;
StrictMock<CheckMock> checkMock;
StrictMock<CheckMock> anotherCheckMock;
};
struct ProcessorTestBundle {
std::string name;
MaybeError requirementResult;
std::optional<MaybeError> otherRequirementResult;
MaybeError expectedResult;
};
struct FieldProcessorTests : SpecsTests, testing::WithParamInterface<ProcessorTestBundle> {
protected:
FieldSpec spec_{
"key",
RequirementMockRef(requirementMock),
RequirementMockRef(anotherRequirementMock)
};
boost::json::value json_;
};
INSTANTIATE_TEST_SUITE_P(
FieldSpecTestGroup,
FieldProcessorTests,
testing::Values(
ProcessorTestBundle{"NoErrors", MaybeError{}, MaybeError{}, MaybeError{}},
ProcessorTestBundle{
"FirstError",
Error{Status{"error1"}},
std::nullopt,
Error{Status{"error1"}},
},
ProcessorTestBundle{
"SecondError",
MaybeError{},
Error{Status{"error2"}},
Error{Status{"error2"}},
}
),
[](testing::TestParamInfo<ProcessorTestBundle> const& info) { return info.param.name; }
);
TEST_P(FieldProcessorTests, FieldSpecWithRequirementProcess)
{
EXPECT_CALL(requirementMock, verify).WillOnce(testing::Return(GetParam().requirementResult));
if (GetParam().otherRequirementResult) {
EXPECT_CALL(anotherRequirementMock, verify)
.WillOnce(testing::Return(GetParam().otherRequirementResult.value()));
}
auto const result = spec_.process(json_);
EXPECT_EQ(result, GetParam().expectedResult);
}
TEST_F(FieldProcessorTests, FieldSpecWithRequirementCheck)
{
auto const result = spec_.check(json_);
EXPECT_EQ(result, check::Warnings{});
}
struct FieldCheckerTestBundle {
std::string name;
std::optional<check::Warning> checkResult;
std::optional<check::Warning> otherCheckResult;
check::Warnings expectedWarnings;
};
struct FieldCheckerTests : SpecsTests, testing::WithParamInterface<FieldCheckerTestBundle> {
protected:
FieldSpec spec_{"key", CheckMockRef(checkMock), CheckMockRef(anotherCheckMock)};
boost::json::value json_;
};
INSTANTIATE_TEST_SUITE_P(
FieldSpecTestGroup,
FieldCheckerTests,
testing::Values(
FieldCheckerTestBundle{"NoWarnings", std::nullopt, std::nullopt, check::Warnings{}},
FieldCheckerTestBundle{
"FirstWarning",
check::Warning{WarningCode::WarnUnknown, "error1"},
std::nullopt,
check::Warnings{check::Warning{WarningCode::WarnUnknown, "error1"}}
},
FieldCheckerTestBundle{
"SecondWarning",
std::nullopt,
check::Warning{WarningCode::WarnUnknown, "error2"},
check::Warnings{check::Warning{WarningCode::WarnUnknown, "error2"}}
},
FieldCheckerTestBundle{
"BothWarnings",
check::Warning{WarningCode::WarnUnknown, "error1"},
check::Warning{WarningCode::WarnUnknown, "error2"},
check::Warnings{
check::Warning{WarningCode::WarnUnknown, "error1"},
check::Warning{WarningCode::WarnUnknown, "error2"}
}
}
),
[](testing::TestParamInfo<FieldCheckerTestBundle> const& info) { return info.param.name; }
);
TEST_F(FieldCheckerTests, FieldSpecWithCheckProcess)
{
auto const result = spec_.process(json_);
EXPECT_EQ(result, MaybeError{});
}
TEST_P(FieldCheckerTests, FieldSpecWithCheck)
{
EXPECT_CALL(checkMock, check).WillOnce(testing::Return(GetParam().checkResult));
EXPECT_CALL(anotherCheckMock, check).WillOnce(testing::Return(GetParam().otherCheckResult));
auto const result = spec_.check(json_);
EXPECT_EQ(result, GetParam().expectedWarnings);
}
struct RpcSpecProcessTests : SpecsTests, testing::WithParamInterface<ProcessorTestBundle> {
RpcSpec spec{
{"key1", RequirementMockRef(requirementMock)},
{"key2", RequirementMockRef(anotherRequirementMock)}
};
boost::json::value json;
};
INSTANTIATE_TEST_SUITE_P(
RpcSpecProcessTestGroup,
RpcSpecProcessTests,
testing::Values(
ProcessorTestBundle{"NoErrors", MaybeError{}, MaybeError{}, MaybeError{}},
ProcessorTestBundle{
"FirstError",
Error{Status{"error1"}},
std::nullopt,
Error{Status{"error1"}},
},
ProcessorTestBundle{
"SecondError",
MaybeError{},
Error{Status{"error2"}},
Error{Status{"error2"}},
}
),
[](testing::TestParamInfo<ProcessorTestBundle> const& info) { return info.param.name; }
);
TEST_P(RpcSpecProcessTests, Process)
{
EXPECT_CALL(requirementMock, verify).WillOnce(testing::Return(GetParam().requirementResult));
if (GetParam().otherRequirementResult) {
EXPECT_CALL(anotherRequirementMock, verify)
.WillOnce(testing::Return(GetParam().otherRequirementResult.value()));
}
auto const result = spec.process(json);
EXPECT_EQ(result, GetParam().expectedResult);
}
struct RpcSpecCheckTestBundle {
std::string name;
std::optional<check::Warning> checkResult;
std::optional<check::Warning> otherCheckResult;
std::unordered_map<int, std::vector<std::string>> expectedWarnings;
};
struct RpcSpecCheckTests : SpecsTests, testing::WithParamInterface<RpcSpecCheckTestBundle> {
protected:
RpcSpec spec_{{"key1", CheckMockRef(checkMock)}, {"key2", CheckMockRef(anotherCheckMock)}};
boost::json::value json_;
};
INSTANTIATE_TEST_SUITE_P(
RpcSpecCheckTestGroup,
RpcSpecCheckTests,
testing::Values(
RpcSpecCheckTestBundle{"NoWarnings", std::nullopt, std::nullopt, {}},
RpcSpecCheckTestBundle{
"FirstWarning",
check::Warning{WarningCode::WarnUnknown, "error1"},
std::nullopt,
{{WarningCode::WarnUnknown, {"error1"}}}
},
RpcSpecCheckTestBundle{
"SecondWarning",
std::nullopt,
check::Warning{WarningCode::WarnUnknown, "error2"},
{{WarningCode::WarnUnknown, {"error2"}}}
},
RpcSpecCheckTestBundle{
"BothWarnings",
check::Warning{WarningCode::WarnUnknown, "error1"},
check::Warning{WarningCode::WarnUnknown, "error2"},
{{WarningCode::WarnUnknown, {"error1", "error2"}}}
},
RpcSpecCheckTestBundle{
"DifferentWarningCodes",
check::Warning{WarningCode::WarnUnknown, "error1"},
check::Warning{WarningCode::WarnRpcClio, "error2"},
{{WarningCode::WarnUnknown, {"error1"}}, {WarningCode::WarnRpcClio, {"error2"}}}
}
),
[](testing::TestParamInfo<RpcSpecCheckTestBundle> const& info) { return info.param.name; }
);
TEST_P(RpcSpecCheckTests, Check)
{
EXPECT_CALL(checkMock, check).WillOnce(testing::Return(GetParam().checkResult));
EXPECT_CALL(anotherCheckMock, check).WillOnce(testing::Return(GetParam().otherCheckResult));
auto const result = spec_.check(json_);
ASSERT_EQ(result.size(), GetParam().expectedWarnings.size());
for (auto const& entry : result) {
ASSERT_TRUE(entry.is_object());
auto const& object = entry.as_object();
ASSERT_TRUE(object.contains("id"));
ASSERT_TRUE(object.at("id").is_int64());
ASSERT_TRUE(object.contains("message"));
ASSERT_TRUE(object.at("message").is_string());
auto it = GetParam().expectedWarnings.find(object.at("id").as_int64());
ASSERT_NE(it, GetParam().expectedWarnings.end());
for (auto const& message : it->second) {
EXPECT_NE(object.at("message").as_string().find(message), std::string::npos);
}
}
}