summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorNan Zhou <nanzhoumails@gmail.com>2022-09-10 21:11:41 +0300
committerNan Zhou <nanzhoumails@gmail.com>2022-09-22 02:44:37 +0300
commitc33a039b56fc5789ae71289adaca1e572a48d318 (patch)
tree8b936ca291537cebebf6bf505e565bff9635f511 /test
parent6ab9ad5492b9203c7dd8fb3e8c59d37bbc455cde (diff)
downloadbmcweb-c33a039b56fc5789ae71289adaca1e572a48d318.tar.xz
treewide: reorganize unit tests
Like other C++ projects, unit tests normally are in a separate repo and respect the folder structure of the file under test. This commit deleted all "ut" folder and move tests to a "test" folder. The test folder also has similar structure as the main folder. This commit also made neccessary include changes to make codes compile. Unused tests are untouched. Tested: unit test passed. Reference: [1] https://github.com/grpc/grpc/tree/master/test [2] https://github.com/boostorg/core/tree/414dfb466878af427d33b36e6ccf84d21c0e081b/test [3] Many other OpenBMC repos: https://github.com/openbmc/entity-manager/tree/master/test [4] https://stackoverflow.com/questions/2360734/whats-a-good-directory-structure-for-larger-c-projects-using-makefile Signed-off-by: Nan Zhou <nanzhoumails@gmail.com> Change-Id: I4521c7ef5fa03c47cca5c146d322bbb51365ee96
Diffstat (limited to 'test')
-rw-r--r--test/http/crow_getroutes_test.cpp71
-rw-r--r--test/http/router_test.cpp126
-rw-r--r--test/http/utility_test.cpp226
-rw-r--r--test/include/dbus_utility_test.cpp48
-rw-r--r--test/include/google/google_service_root_test.cpp39
-rw-r--r--test/include/http_utility_test.cpp82
-rw-r--r--test/include/human_sort_test.cpp58
-rw-r--r--test/include/ibm/configfile_test.cpp56
-rw-r--r--test/include/ibm/lock_test.cpp366
-rw-r--r--test/include/multipart_test.cpp256
-rw-r--r--test/include/openbmc_dbus_rest_test.cpp76
-rw-r--r--test/redfish-core/include/privileges_test.cpp138
-rw-r--r--test/redfish-core/include/registries_test.cpp25
-rw-r--r--test/redfish-core/include/utils/hex_utils_test.cpp83
-rw-r--r--test/redfish-core/include/utils/ip_utils_test.cpp38
-rw-r--r--test/redfish-core/include/utils/json_utils_test.cpp358
-rw-r--r--test/redfish-core/include/utils/query_param_test.cpp709
-rw-r--r--test/redfish-core/include/utils/stl_utils_test.cpp34
-rw-r--r--test/redfish-core/include/utils/time_utils_test.cpp143
-rw-r--r--test/redfish-core/lib/chassis_test.cpp60
-rw-r--r--test/redfish-core/lib/log_services_dump_test.cpp117
-rw-r--r--test/redfish-core/lib/service_root_test.cpp122
-rw-r--r--test/redfish-core/lib/thermal_subsystem_test.cpp42
23 files changed, 3273 insertions, 0 deletions
diff --git a/test/http/crow_getroutes_test.cpp b/test/http/crow_getroutes_test.cpp
new file mode 100644
index 0000000000..23a511e735
--- /dev/null
+++ b/test/http/crow_getroutes_test.cpp
@@ -0,0 +1,71 @@
+#include "app.hpp"
+#include "routing.hpp"
+
+#include <boost/beast/http/status.hpp>
+
+#include <memory>
+
+#include <gmock/gmock.h> // IWYU pragma: keep
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+// IWYU pragma: no_include <gmock/gmock-matchers.h>
+// IWYU pragma: no_include <gmock/gmock-more-matchers.h>
+// IWYU pragma: no_include <gtest/gtest-matchers.h>
+
+namespace crow
+{
+namespace
+{
+
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::testing::Pointee;
+using ::testing::UnorderedElementsAre;
+
+TEST(GetRoutes, TestEmptyRoutes)
+{
+ App app;
+ app.validate();
+
+ EXPECT_THAT(app.getRoutes(), IsEmpty());
+}
+
+// Tests that static urls are correctly passed
+TEST(GetRoutes, TestOneRoute)
+{
+ App app;
+
+ BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
+
+ // TODO: "/" doesn't get reported in |getRoutes| today. Uncomment this once
+ // it is fixed
+ // EXPECT_THAT(app.getRoutes(),
+ // testing::ElementsAre(Pointee(Eq("/"))));
+}
+
+// Tests that static urls are correctly passed
+TEST(GetRoutes, TestlotsOfRoutes)
+{
+ App app;
+ BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
+ BMCWEB_ROUTE(app, "/foo")([]() { return boost::beast::http::status::ok; });
+ BMCWEB_ROUTE(app, "/bar")([]() { return boost::beast::http::status::ok; });
+ BMCWEB_ROUTE(app, "/baz")([]() { return boost::beast::http::status::ok; });
+ BMCWEB_ROUTE(app, "/boo")([]() { return boost::beast::http::status::ok; });
+ BMCWEB_ROUTE(app, "/moo")([]() { return boost::beast::http::status::ok; });
+
+ app.validate();
+
+ // TODO: "/" doesn't get reported in |getRoutes| today. Uncomment this once
+ // it is fixed
+ EXPECT_THAT(app.getRoutes(), UnorderedElementsAre(
+ // Pointee(Eq("/")),
+ Pointee(Eq("/foo")), Pointee(Eq("/bar")),
+ Pointee(Eq("/baz")), Pointee(Eq("/boo")),
+ Pointee(Eq("/moo"))));
+}
+} // namespace
+} // namespace crow
diff --git a/test/http/router_test.cpp b/test/http/router_test.cpp
new file mode 100644
index 0000000000..9b5d9bec98
--- /dev/null
+++ b/test/http/router_test.cpp
@@ -0,0 +1,126 @@
+#include "async_resp.hpp" // IWYU pragma: keep
+#include "http_request.hpp"
+#include "routing.hpp"
+#include "utility.hpp"
+
+#include <boost/beast/http/message.hpp> // IWYU pragma: keep
+#include <boost/beast/http/verb.hpp>
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <system_error>
+
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <boost/beast/http/impl/message.hpp>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+// IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp>
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_forward_declare bmcweb::AsyncResp
+
+namespace crow
+{
+namespace
+{
+
+using ::crow::black_magic::getParameterTag;
+
+TEST(Router, AllowHeader)
+{
+ // Callback handler that does nothing
+ auto nullCallback = [](const Request&,
+ const std::shared_ptr<bmcweb::AsyncResp>&) {};
+
+ Router router;
+ std::error_code ec;
+
+ constexpr const std::string_view url = "/foo";
+
+ Request req{{boost::beast::http::verb::get, url, 11}, ec};
+
+ // No route should return no methods.
+ router.validate();
+ EXPECT_EQ(router.findRoute(req).allowHeader, "");
+ EXPECT_EQ(router.findRoute(req).route.rule, nullptr);
+
+ router.newRuleTagged<getParameterTag(url)>(std::string(url))
+ .methods(boost::beast::http::verb::get)(nullCallback);
+ router.validate();
+ EXPECT_EQ(router.findRoute(req).allowHeader, "GET");
+ EXPECT_NE(router.findRoute(req).route.rule, nullptr);
+
+ Request patchReq{{boost::beast::http::verb::patch, url, 11}, ec};
+ EXPECT_EQ(router.findRoute(patchReq).route.rule, nullptr);
+
+ router.newRuleTagged<getParameterTag(url)>(std::string(url))
+ .methods(boost::beast::http::verb::patch)(nullCallback);
+ router.validate();
+ EXPECT_EQ(router.findRoute(req).allowHeader, "GET, PATCH");
+ EXPECT_NE(router.findRoute(req).route.rule, nullptr);
+ EXPECT_NE(router.findRoute(patchReq).route.rule, nullptr);
+}
+
+TEST(Router, 404)
+{
+ bool notFoundCalled = false;
+ // Callback handler that does nothing
+ auto nullCallback =
+ [&notFoundCalled](const Request&,
+ const std::shared_ptr<bmcweb::AsyncResp>&) {
+ notFoundCalled = true;
+ };
+
+ Router router;
+ std::error_code ec;
+
+ constexpr const std::string_view url = "/foo/bar";
+
+ Request req{{boost::beast::http::verb::get, url, 11}, ec};
+
+ router.newRuleTagged<getParameterTag(url)>("/foo/<path>")
+ .notFound()(nullCallback);
+ router.validate();
+ {
+ std::shared_ptr<bmcweb::AsyncResp> asyncResp =
+ std::make_shared<bmcweb::AsyncResp>();
+
+ router.handle(req, asyncResp);
+ }
+ EXPECT_TRUE(notFoundCalled);
+}
+
+TEST(Router, 405)
+{
+ // Callback handler that does nothing
+ auto nullCallback = [](const Request&,
+ const std::shared_ptr<bmcweb::AsyncResp>&) {};
+ bool called = false;
+ auto notAllowedCallback =
+ [&called](const Request&, const std::shared_ptr<bmcweb::AsyncResp>&) {
+ called = true;
+ };
+
+ Router router;
+ std::error_code ec;
+
+ constexpr const std::string_view url = "/foo/bar";
+
+ Request req{{boost::beast::http::verb::patch, url, 11}, ec};
+
+ router.newRuleTagged<getParameterTag(url)>(std::string(url))
+ .methods(boost::beast::http::verb::get)(nullCallback);
+ router.newRuleTagged<getParameterTag(url)>("/foo/<path>")
+ .methodNotAllowed()(notAllowedCallback);
+ router.validate();
+ {
+ std::shared_ptr<bmcweb::AsyncResp> asyncResp =
+ std::make_shared<bmcweb::AsyncResp>();
+
+ router.handle(req, asyncResp);
+ }
+ EXPECT_TRUE(called);
+}
+} // namespace
+} // namespace crow
diff --git a/test/http/utility_test.cpp b/test/http/utility_test.cpp
new file mode 100644
index 0000000000..8eef93f4a3
--- /dev/null
+++ b/test/http/utility_test.cpp
@@ -0,0 +1,226 @@
+#include "bmcweb_config.h"
+
+#include "utility.hpp"
+
+#include <boost/url/error.hpp>
+#include <boost/url/url.hpp>
+#include <boost/url/url_view.hpp>
+#include <nlohmann/json.hpp>
+
+#include <cstdint>
+#include <ctime>
+#include <functional>
+#include <limits>
+#include <string>
+#include <string_view>
+
+#include <gtest/gtest.h> // IWYU pragma: keep
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+
+namespace crow::utility
+{
+namespace
+{
+
+using ::crow::black_magic::getParameterTag;
+
+TEST(Utility, Base64DecodeAuthString)
+{
+ std::string authString("dXNlcm40bWU6cGFzc3cwcmQ=");
+ std::string result;
+ EXPECT_TRUE(base64Decode(authString, result));
+ EXPECT_EQ(result, "usern4me:passw0rd");
+}
+
+TEST(Utility, Base64DecodeNonAscii)
+{
+ std::string junkString("\xff\xee\xdd\xcc\x01\x11\x22\x33");
+ std::string result;
+ EXPECT_FALSE(base64Decode(junkString, result));
+}
+
+TEST(Utility, Base64EncodeString)
+{
+ using namespace std::string_literals;
+ std::string encoded;
+
+ encoded = base64encode("");
+ EXPECT_EQ(encoded, "");
+
+ encoded = base64encode("f");
+ EXPECT_EQ(encoded, "Zg==");
+
+ encoded = base64encode("f0");
+ EXPECT_EQ(encoded, "ZjA=");
+
+ encoded = base64encode("f0\0"s);
+ EXPECT_EQ(encoded, "ZjAA");
+
+ encoded = base64encode("f0\0 "s);
+ EXPECT_EQ(encoded, "ZjAAIA==");
+
+ encoded = base64encode("f0\0 B"s);
+ EXPECT_EQ(encoded, "ZjAAIEI=");
+
+ encoded = base64encode("f0\0 Ba"s);
+ EXPECT_EQ(encoded, "ZjAAIEJh");
+
+ encoded = base64encode("f0\0 Bar"s);
+ EXPECT_EQ(encoded, "ZjAAIEJhcg==");
+}
+
+TEST(Utility, Base64EncodeDecodeString)
+{
+ using namespace std::string_literals;
+ std::string data("Data fr\0m 90 reading a \nFile"s);
+ std::string encoded = base64encode(data);
+ std::string decoded;
+ EXPECT_TRUE(base64Decode(encoded, decoded));
+ EXPECT_EQ(data, decoded);
+}
+
+TEST(Utility, UrlFromPieces)
+{
+ boost::urls::url url = urlFromPieces("redfish", "v1", "foo");
+ EXPECT_EQ(std::string_view(url.data(), url.size()), "/redfish/v1/foo");
+
+ url = urlFromPieces("/", "badString");
+ EXPECT_EQ(std::string_view(url.data(), url.size()), "/%2f/badString");
+
+ url = urlFromPieces("bad?tring");
+ EXPECT_EQ(std::string_view(url.data(), url.size()), "/bad%3ftring");
+
+ url = urlFromPieces("/", "bad&tring");
+ EXPECT_EQ(std::string_view(url.data(), url.size()), "/%2f/bad&tring");
+}
+
+TEST(Utility, readUrlSegments)
+{
+ boost::urls::result<boost::urls::url_view> parsed =
+ boost::urls::parse_relative_ref("/redfish/v1/Chassis#/Fans/0/Reading");
+
+ EXPECT_TRUE(readUrlSegments(*parsed, "redfish", "v1", "Chassis"));
+
+ EXPECT_FALSE(readUrlSegments(*parsed, "FOOBAR", "v1", "Chassis"));
+
+ EXPECT_FALSE(readUrlSegments(*parsed, "redfish", "v1"));
+
+ EXPECT_FALSE(
+ readUrlSegments(*parsed, "redfish", "v1", "Chassis", "FOOBAR"));
+
+ std::string out1;
+ std::string out2;
+ std::string out3;
+ EXPECT_TRUE(readUrlSegments(*parsed, "redfish", "v1", std::ref(out1)));
+ EXPECT_EQ(out1, "Chassis");
+
+ out1 = out2 = out3 = "";
+ EXPECT_TRUE(readUrlSegments(*parsed, std::ref(out1), std::ref(out2),
+ std::ref(out3)));
+ EXPECT_EQ(out1, "redfish");
+ EXPECT_EQ(out2, "v1");
+ EXPECT_EQ(out3, "Chassis");
+
+ out1 = out2 = out3 = "";
+ EXPECT_TRUE(readUrlSegments(*parsed, "redfish", std::ref(out1), "Chassis"));
+ EXPECT_EQ(out1, "v1");
+
+ out1 = out2 = out3 = "";
+ EXPECT_TRUE(readUrlSegments(*parsed, std::ref(out1), "v1", std::ref(out2)));
+ EXPECT_EQ(out1, "redfish");
+ EXPECT_EQ(out2, "Chassis");
+
+ EXPECT_FALSE(readUrlSegments(*parsed, "too", "short"));
+
+ EXPECT_FALSE(readUrlSegments(*parsed, "too", "long", "too", "long"));
+
+ EXPECT_FALSE(
+ readUrlSegments(*parsed, std::ref(out1), "v2", std::ref(out2)));
+
+ EXPECT_FALSE(readUrlSegments(*parsed, "redfish", std::ref(out1),
+ std::ref(out2), std::ref(out3)));
+
+ parsed = boost::urls::parse_relative_ref("/absolute/url");
+ EXPECT_TRUE(readUrlSegments(*parsed, "absolute", "url"));
+
+ parsed = boost::urls::parse_relative_ref("not/absolute/url");
+ EXPECT_FALSE(readUrlSegments(*parsed, "not", "absolute", "url"));
+
+ parsed = boost::urls::parse_relative_ref("/excellent/path");
+
+ EXPECT_TRUE(readUrlSegments(*parsed, "excellent", "path", OrMorePaths()));
+ EXPECT_TRUE(readUrlSegments(*parsed, "excellent", OrMorePaths()));
+ EXPECT_TRUE(readUrlSegments(*parsed, OrMorePaths()));
+}
+
+TEST(Utility, ValidateAndSplitUrlPositive)
+{
+ std::string host;
+ std::string urlProto;
+ uint16_t port = 0;
+ std::string path;
+ ASSERT_TRUE(validateAndSplitUrl("https://foo.com:18080/bar", urlProto, host,
+ port, path));
+ EXPECT_EQ(host, "foo.com");
+ EXPECT_EQ(urlProto, "https");
+ EXPECT_EQ(port, 18080);
+
+ EXPECT_EQ(path, "/bar");
+
+ // query string
+ ASSERT_TRUE(validateAndSplitUrl("https://foo.com:18080/bar?foobar=1",
+ urlProto, host, port, path));
+ EXPECT_EQ(path, "/bar?foobar=1");
+
+ // fragment
+ ASSERT_TRUE(validateAndSplitUrl("https://foo.com:18080/bar#frag", urlProto,
+ host, port, path));
+ EXPECT_EQ(path, "/bar#frag");
+
+ // Missing port
+ ASSERT_TRUE(
+ validateAndSplitUrl("https://foo.com/bar", urlProto, host, port, path));
+ EXPECT_EQ(port, 443);
+
+ // Missing path defaults to "/"
+ ASSERT_TRUE(
+ validateAndSplitUrl("https://foo.com/", urlProto, host, port, path));
+ EXPECT_EQ(path, "/");
+
+ // If http push eventing is allowed, allow http and pick a default port of
+ // 80, if it's not, parse should fail.
+ ASSERT_EQ(
+ validateAndSplitUrl("http://foo.com/bar", urlProto, host, port, path),
+ bmcwebInsecureEnableHttpPushStyleEventing);
+ if constexpr (bmcwebInsecureEnableHttpPushStyleEventing)
+ {
+ EXPECT_EQ(port, 80);
+ }
+}
+
+TEST(Router, ParameterTagging)
+{
+ EXPECT_EQ(6 * 6 + 6 * 3 + 2, getParameterTag("<uint><double><int>"));
+ EXPECT_EQ(1, getParameterTag("<int>"));
+ EXPECT_EQ(2, getParameterTag("<uint>"));
+ EXPECT_EQ(3, getParameterTag("<float>"));
+ EXPECT_EQ(3, getParameterTag("<double>"));
+ EXPECT_EQ(4, getParameterTag("<str>"));
+ EXPECT_EQ(4, getParameterTag("<string>"));
+ EXPECT_EQ(5, getParameterTag("<path>"));
+ EXPECT_EQ(6 * 6 + 6 + 1, getParameterTag("<int><int><int>"));
+ EXPECT_EQ(6 * 6 + 6 + 2, getParameterTag("<uint><int><int>"));
+ EXPECT_EQ(6 * 6 + 6 * 3 + 2, getParameterTag("<uint><double><int>"));
+}
+
+TEST(URL, JsonEncoding)
+{
+ std::string urlString = "/foo";
+ EXPECT_EQ(nlohmann::json(boost::urls::url(urlString)), urlString);
+ EXPECT_EQ(nlohmann::json(boost::urls::url_view(urlString)), urlString);
+}
+
+} // namespace
+} // namespace crow::utility
diff --git a/test/include/dbus_utility_test.cpp b/test/include/dbus_utility_test.cpp
new file mode 100644
index 0000000000..71978d01ba
--- /dev/null
+++ b/test/include/dbus_utility_test.cpp
@@ -0,0 +1,48 @@
+#include "dbus_utility.hpp"
+
+#include <string>
+
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+
+namespace dbus::utility
+{
+namespace
+{
+
+TEST(GetNthStringFromPath, ParsingSucceedsAndReturnsNthArg)
+{
+ std::string path("/0th/1st/2nd/3rd");
+ std::string result;
+ EXPECT_TRUE(getNthStringFromPath(path, 0, result));
+ EXPECT_EQ(result, "0th");
+ EXPECT_TRUE(getNthStringFromPath(path, 1, result));
+ EXPECT_EQ(result, "1st");
+ EXPECT_TRUE(getNthStringFromPath(path, 2, result));
+ EXPECT_EQ(result, "2nd");
+ EXPECT_TRUE(getNthStringFromPath(path, 3, result));
+ EXPECT_EQ(result, "3rd");
+ EXPECT_FALSE(getNthStringFromPath(path, 4, result));
+
+ path = "////0th///1st//\2nd///3rd?/";
+ EXPECT_TRUE(getNthStringFromPath(path, 0, result));
+ EXPECT_EQ(result, "0th");
+ EXPECT_TRUE(getNthStringFromPath(path, 1, result));
+ EXPECT_EQ(result, "1st");
+ EXPECT_TRUE(getNthStringFromPath(path, 2, result));
+ EXPECT_EQ(result, "\2nd");
+ EXPECT_TRUE(getNthStringFromPath(path, 3, result));
+ EXPECT_EQ(result, "3rd?");
+}
+
+TEST(GetNthStringFromPath, InvalidIndexReturnsFalse)
+{
+ std::string path("////0th///1st//\2nd///3rd?/");
+ std::string result;
+ EXPECT_FALSE(getNthStringFromPath(path, -1, result));
+}
+} // namespace
+} // namespace dbus::utility \ No newline at end of file
diff --git a/test/include/google/google_service_root_test.cpp b/test/include/google/google_service_root_test.cpp
new file mode 100644
index 0000000000..32d4e526fb
--- /dev/null
+++ b/test/include/google/google_service_root_test.cpp
@@ -0,0 +1,39 @@
+#include "async_resp.hpp"
+#include "google/google_service_root.hpp"
+#include "http_request.hpp"
+#include "nlohmann/json.hpp"
+
+#include <gtest/gtest.h>
+
+namespace crow::google_api
+{
+namespace
+{
+
+void validateServiceRootGet(crow::Response& res)
+{
+ nlohmann::json& json = res.jsonValue;
+ EXPECT_EQ(json["@odata.id"], "/google/v1");
+ EXPECT_EQ(json["@odata.type"],
+ "#GoogleServiceRoot.v1_0_0.GoogleServiceRoot");
+ EXPECT_EQ(json["@odata.id"], "/google/v1");
+ EXPECT_EQ(json["Id"], "Google Rest RootService");
+ EXPECT_EQ(json["Name"], "Google Service Root");
+ EXPECT_EQ(json["Version"], "1.0.0");
+ EXPECT_EQ(json["RootOfTrustCollection"]["@odata.id"],
+ "/google/v1/RootOfTrustCollection");
+}
+
+TEST(HandleGoogleV1Get, OnSuccess)
+{
+ std::error_code ec;
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ asyncResp->res.setCompleteRequestHandler(validateServiceRootGet);
+
+ crow::Request dummyRequest{{boost::beast::http::verb::get, "", 11}, ec};
+ handleGoogleV1Get(dummyRequest, asyncResp);
+}
+
+} // namespace
+} // namespace crow::google_api \ No newline at end of file
diff --git a/test/include/http_utility_test.cpp b/test/include/http_utility_test.cpp
new file mode 100644
index 0000000000..d1df6d1087
--- /dev/null
+++ b/test/include/http_utility_test.cpp
@@ -0,0 +1,82 @@
+#include "http_utility.hpp"
+
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+
+namespace http_helpers
+{
+namespace
+{
+
+TEST(isContentTypeAllowed, PositiveTest)
+{
+ EXPECT_TRUE(isContentTypeAllowed("*/*, application/octet-stream",
+ ContentType::OctetStream));
+ EXPECT_TRUE(isContentTypeAllowed("application/octet-stream",
+ ContentType::OctetStream));
+ EXPECT_TRUE(isContentTypeAllowed("text/html", ContentType::HTML));
+ EXPECT_TRUE(isContentTypeAllowed("application/json", ContentType::JSON));
+ EXPECT_TRUE(isContentTypeAllowed("application/cbor", ContentType::CBOR));
+ EXPECT_TRUE(
+ isContentTypeAllowed("application/json, text/html", ContentType::HTML));
+}
+
+TEST(isContentTypeAllowed, NegativeTest)
+{
+ EXPECT_FALSE(
+ isContentTypeAllowed("application/octet-stream", ContentType::HTML));
+ EXPECT_FALSE(isContentTypeAllowed("application/html", ContentType::JSON));
+ EXPECT_FALSE(isContentTypeAllowed("application/json", ContentType::CBOR));
+ EXPECT_FALSE(isContentTypeAllowed("application/cbor", ContentType::HTML));
+ EXPECT_FALSE(isContentTypeAllowed("application/json, text/html",
+ ContentType::OctetStream));
+}
+
+TEST(isContentTypeAllowed, ContainsAnyMimeTypeReturnsTrue)
+{
+ EXPECT_TRUE(
+ isContentTypeAllowed("text/html, */*", ContentType::OctetStream));
+}
+
+TEST(isContentTypeAllowed, ContainsQFactorWeightingReturnsTrue)
+{
+ EXPECT_TRUE(
+ isContentTypeAllowed("text/html, */*;q=0.8", ContentType::OctetStream));
+}
+
+TEST(getPreferedContentType, PositiveTest)
+{
+ std::array<ContentType, 1> contentType{ContentType::HTML};
+ EXPECT_EQ(
+ getPreferedContentType("text/html, application/json", contentType),
+ ContentType::HTML);
+
+ std::array<ContentType, 2> htmlJson{ContentType::HTML, ContentType::JSON};
+ EXPECT_EQ(getPreferedContentType("text/html, application/json", htmlJson),
+ ContentType::HTML);
+
+ std::array<ContentType, 2> jsonHtml{ContentType::JSON, ContentType::HTML};
+ EXPECT_EQ(getPreferedContentType("text/html, application/json", jsonHtml),
+ ContentType::HTML);
+
+ std::array<ContentType, 2> cborJson{ContentType::CBOR, ContentType::JSON};
+ EXPECT_EQ(
+ getPreferedContentType("application/cbor, application::json", cborJson),
+ ContentType::CBOR);
+
+ EXPECT_EQ(getPreferedContentType("application/json", cborJson),
+ ContentType::JSON);
+}
+
+TEST(getPreferedContentType, NegativeTest)
+{
+ std::array<ContentType, 1> contentType{ContentType::CBOR};
+ EXPECT_EQ(
+ getPreferedContentType("text/html, application/json", contentType),
+ ContentType::NoMatch);
+}
+} // namespace
+} // namespace http_helpers
diff --git a/test/include/human_sort_test.cpp b/test/include/human_sort_test.cpp
new file mode 100644
index 0000000000..be21a198b0
--- /dev/null
+++ b/test/include/human_sort_test.cpp
@@ -0,0 +1,58 @@
+#include "human_sort.hpp"
+
+#include <set>
+#include <string>
+
+#include <gmock/gmock.h> // IWYU pragma: keep
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+// IWYU pragma: no_include <gmock/gmock-matchers.h>
+
+namespace
+{
+
+using ::testing::ElementsAreArray;
+
+TEST(AlphaNum, NumberTests)
+{
+ // testcases for the algorithm
+ EXPECT_EQ(alphanumComp("", ""), 0);
+ EXPECT_LT(alphanumComp("", "a"), 0);
+ EXPECT_GT(alphanumComp("a", ""), 0);
+ EXPECT_EQ(alphanumComp("a", "a"), 0);
+ EXPECT_LT(alphanumComp("", "9"), 0);
+ EXPECT_GT(alphanumComp("9", ""), 0);
+ EXPECT_EQ(alphanumComp("1", "1"), 0);
+ EXPECT_LT(alphanumComp("1", "2"), 0);
+ EXPECT_GT(alphanumComp("3", "2"), 0);
+ EXPECT_EQ(alphanumComp("a1", "a1"), 0);
+ EXPECT_LT(alphanumComp("a1", "a2"), 0);
+ EXPECT_GT(alphanumComp("a2", "a1"), 0);
+ EXPECT_LT(alphanumComp("a1a2", "a1a3"), 0);
+ EXPECT_GT(alphanumComp("a1a2", "a1a0"), 0);
+ EXPECT_GT(alphanumComp("134", "122"), 0);
+ EXPECT_EQ(alphanumComp("12a3", "12a3"), 0);
+ EXPECT_GT(alphanumComp("12a1", "12a0"), 0);
+ EXPECT_LT(alphanumComp("12a1", "12a2"), 0);
+ EXPECT_LT(alphanumComp("a", "aa"), 0);
+ EXPECT_GT(alphanumComp("aaa", "aa"), 0);
+ EXPECT_EQ(alphanumComp("Alpha 2", "Alpha 2"), 0);
+ EXPECT_LT(alphanumComp("Alpha 2", "Alpha 2A"), 0);
+ EXPECT_GT(alphanumComp("Alpha 2 B", "Alpha 2"), 0);
+
+ std::string str("Alpha 2");
+ EXPECT_EQ(alphanumComp(str, "Alpha 2"), 0);
+ EXPECT_LT(alphanumComp(str, "Alpha 2A"), 0);
+ EXPECT_GT(alphanumComp("Alpha 2 B", str), 0);
+}
+
+TEST(AlphaNum, LessTest)
+{
+ std::set<std::string, AlphanumLess<std::string>> sorted{"Alpha 10",
+ "Alpha 2"};
+ EXPECT_THAT(sorted, ElementsAreArray({"Alpha 2", "Alpha 10"}));
+}
+} // namespace \ No newline at end of file
diff --git a/test/include/ibm/configfile_test.cpp b/test/include/ibm/configfile_test.cpp
new file mode 100644
index 0000000000..1d95a79066
--- /dev/null
+++ b/test/include/ibm/configfile_test.cpp
@@ -0,0 +1,56 @@
+#include "http_response.hpp"
+#include "ibm/management_console_rest.hpp"
+
+#include <string>
+
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+
+namespace crow
+{
+namespace ibm_mc
+{
+
+TEST(IsValidConfigFileName, FileNameValidCharReturnsTrue)
+{
+ crow::Response res;
+
+ EXPECT_TRUE(isValidConfigFileName("GoodConfigFile", res));
+}
+TEST(IsValidConfigFileName, FileNameInvalidCharReturnsFalse)
+{
+ crow::Response res;
+
+ EXPECT_FALSE(isValidConfigFileName("Bad@file", res));
+}
+TEST(IsValidConfigFileName, FileNameInvalidPathReturnsFalse)
+{
+ crow::Response res;
+
+ EXPECT_FALSE(isValidConfigFileName("/../../../../../etc/badpath", res));
+ EXPECT_FALSE(isValidConfigFileName("/../../etc/badpath", res));
+ EXPECT_FALSE(isValidConfigFileName("/mydir/configFile", res));
+}
+
+TEST(IsValidConfigFileName, EmptyFileNameReturnsFalse)
+{
+ crow::Response res;
+ EXPECT_FALSE(isValidConfigFileName("", res));
+}
+
+TEST(IsValidConfigFileName, SlashFileNameReturnsFalse)
+{
+ crow::Response res;
+ EXPECT_FALSE(isValidConfigFileName("/", res));
+}
+TEST(IsValidConfigFileName, FileNameMoreThan20CharReturnsFalse)
+{
+ crow::Response res;
+ EXPECT_FALSE(isValidConfigFileName("BadfileBadfileBadfile", res));
+}
+
+} // namespace ibm_mc
+} // namespace crow
diff --git a/test/include/ibm/lock_test.cpp b/test/include/ibm/lock_test.cpp
new file mode 100644
index 0000000000..33c5354f64
--- /dev/null
+++ b/test/include/ibm/lock_test.cpp
@@ -0,0 +1,366 @@
+#include "ibm/locks.hpp"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include <gmock/gmock.h> // IWYU pragma: keep
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+
+namespace crow::ibm_mc_lock
+{
+namespace
+{
+
+using SType = std::string;
+using LockRequest = std::tuple<SType, SType, SType, uint64_t, SegmentFlags>;
+using LockRequests = std::vector<LockRequest>;
+using Rc =
+ std::pair<bool, std::variant<uint32_t, std::pair<uint32_t, LockRequest>>>;
+using RcRelaseLock = std::pair<bool, std::pair<uint32_t, LockRequest>>;
+using RcGetLockList =
+ std::variant<std::string, std::vector<std::pair<uint32_t, LockRequests>>>;
+using ListOfTransactionIds = std::vector<uint32_t>;
+using RcAcquireLock = std::pair<bool, std::variant<Rc, std::pair<bool, int>>>;
+using RcReleaseLockApi = std::pair<bool, std::variant<bool, RcRelaseLock>>;
+using SessionFlags = std::pair<SType, SType>;
+using ListOfSessionIds = std::vector<std::string>;
+using ::testing::IsEmpty;
+
+class LockTest : public ::testing::Test
+{
+ protected:
+ LockRequests request;
+ LockRequests request1, request2;
+ LockRequest record;
+
+ public:
+ LockTest() :
+ // lockrequest with multiple lockrequests
+ request{{"xxxxx",
+ "hmc-id",
+ "Read",
+ 234,
+ {{"DontLock", 2}, {"DontLock", 4}}},
+ {"xxxxx",
+ "hmc-id",
+ "Read",
+ 234,
+ {{"DontLock", 2}, {"DontLock", 4}}}},
+ request1{{"xxxxx",
+ "hmc-id",
+ "Read",
+ 234,
+ {{"DontLock", 2}, {"DontLock", 4}}}},
+ request2{{"xxxxx",
+ "hmc-id",
+ "Write",
+ 234,
+ {{"LockAll", 2}, {"DontLock", 4}}}},
+ record{
+ "xxxxx", "hmc-id", "Read", 234, {{"DontLock", 2}, {"DontLock", 4}}}
+ {}
+
+ ~LockTest() override = default;
+
+ LockTest(const LockTest&) = delete;
+ LockTest(LockTest&&) = delete;
+ LockTest& operator=(const LockTest&) = delete;
+ LockTest& operator=(const LockTest&&) = delete;
+};
+
+class MockLock : public crow::ibm_mc_lock::Lock
+{
+ public:
+ bool isValidLockRequest(const LockRequest& record1) override
+ {
+ bool status = Lock::isValidLockRequest(record1);
+ return status;
+ }
+ bool isConflictRequest(const LockRequests& request) override
+ {
+ bool status = Lock::isConflictRequest(request);
+ return status;
+ }
+ Rc isConflictWithTable(const LockRequests& request) override
+ {
+ auto conflict = Lock::isConflictWithTable(request);
+ return conflict;
+ }
+ uint32_t generateTransactionId() override
+ {
+ uint32_t tid = Lock::generateTransactionId();
+ return tid;
+ }
+
+ bool validateRids(const ListOfTransactionIds& tids) override
+ {
+ bool status = Lock::validateRids(tids);
+ return status;
+ }
+ RcRelaseLock isItMyLock(const ListOfTransactionIds& tids,
+ const SessionFlags& ids) override
+ {
+ auto status = Lock::isItMyLock(tids, ids);
+ return status;
+ }
+ friend class LockTest;
+};
+
+TEST_F(LockTest, ValidationGoodTestCase)
+{
+ MockLock lockManager;
+ const LockRequest& t = record;
+ EXPECT_TRUE(lockManager.isValidLockRequest(t));
+}
+
+TEST_F(LockTest, ValidationBadTestWithLocktype)
+{
+ MockLock lockManager;
+ // Corrupt the lock type
+ std::get<2>(record) = "rwrite";
+ const LockRequest& t = record;
+ EXPECT_FALSE(lockManager.isValidLockRequest(t));
+}
+
+TEST_F(LockTest, ValidationBadTestWithlockFlags)
+{
+ MockLock lockManager;
+ // Corrupt the lockflag
+ std::get<4>(record)[0].first = "lock";
+ const LockRequest& t = record;
+ EXPECT_FALSE(lockManager.isValidLockRequest(t));
+}
+
+TEST_F(LockTest, ValidationBadTestWithSegmentlength)
+{
+ MockLock lockManager;
+ // Corrupt the Segment length
+ std::get<4>(record)[0].second = 7;
+ const LockRequest& t = record;
+ EXPECT_FALSE(lockManager.isValidLockRequest(t));
+}
+
+TEST_F(LockTest, MultiRequestWithoutConflict)
+{
+ MockLock lockManager;
+ const LockRequests& t = request;
+ EXPECT_FALSE(lockManager.isConflictRequest(t));
+}
+
+TEST_F(LockTest, MultiRequestWithConflictduetoSameSegmentLength)
+{
+ MockLock lockManager;
+ // Corrupt the locktype
+ std::get<2>(request[0]) = "Write";
+ // Match the segment lengths to points them to lock similar kind of
+ // resource
+ std::get<4>(request[0])[0].first = "LockAll";
+ const LockRequests& t = request;
+ EXPECT_TRUE(lockManager.isConflictRequest(t));
+}
+
+TEST_F(LockTest, MultiRequestWithoutConflictduetoDifferentSegmentData)
+{
+ MockLock lockManager;
+ // Corrupt the locktype
+ std::get<2>(request[0]) = "Write";
+ // Match the segment lengths to points them to lock similar kind of
+ // resource
+ std::get<4>(request[0])[0].first = "DontLock";
+ std::get<4>(request[0])[1].first = "LockAll";
+
+ // Change the resource id(2nd byte) of first record, so the locks are
+ // different so no conflict
+ std::get<3>(request[0]) = 216179379183550464; // HEX 03 00 06 00 00 00 00 00
+ std::get<3>(request[1]) = 288236973221478400; // HEX 04 00 06 00 00 00 00 00
+ const LockRequests& t = request;
+ EXPECT_FALSE(lockManager.isConflictRequest(t));
+}
+
+TEST_F(LockTest, MultiRequestWithConflictduetoSameSegmentData)
+{
+ MockLock lockManager;
+ // Corrupt the locktype
+ std::get<2>(request[0]) = "Write";
+ // Match the segment lengths to points them to lock similar kind of
+ // resource
+ std::get<4>(request[0])[0].first = "DontLock";
+ std::get<4>(request[0])[1].first = "LockAll";
+ // Dont Change the resource id(1st & 2nd byte) at all, so that the
+ // conflict occurs from the second segment which is trying to lock all
+ // the resources.
+ std::get<3>(request[0]) = 216173882346831872; // 03 00 01 00 2B 00 00 00
+ std::get<3>(request[1]) = 216173882346831872; // 03 00 01 00 2B 00 00 00
+ const LockRequests& t = request;
+ EXPECT_TRUE(lockManager.isConflictRequest(t));
+}
+
+TEST_F(LockTest, MultiRequestWithoutConflictduetoDifferentSegmentLength)
+{
+ MockLock lockManager;
+ // Corrupt the locktype
+ std::get<2>(request[0]) = "Write";
+ // Match the segment lengths to points them to lock similar kind of
+ // resource
+ std::get<4>(request[0])[0].first = "LockSame";
+ // Change the segment length , so that the requests are trying to lock
+ // two different kind of resources
+ std::get<4>(request[0])[0].second = 3;
+ const LockRequests& t = request;
+ // Return No Conflict
+ EXPECT_FALSE(lockManager.isConflictRequest(t));
+}
+
+TEST_F(LockTest, MultiRequestWithoutConflictduetoReadLocktype)
+{
+ MockLock lockManager;
+ // Match the segment lengths to points them to lock similar kind of
+ // resource
+ std::get<4>(request[0])[0].first = "LockAll";
+ const LockRequests& t = request;
+ // Return No Conflict
+ EXPECT_FALSE(lockManager.isConflictRequest(t));
+}
+
+TEST_F(LockTest, MultiRequestWithoutConflictduetoReadLocktypeAndLockall)
+{
+ MockLock lockManager;
+ // Match the segment lengths to points them to lock similar kind of
+ // resource
+ std::get<4>(request[0])[0].first = "LockAll";
+ std::get<4>(request[0])[1].first = "LockAll";
+ const LockRequests& t = request;
+ // Return No Conflict
+ EXPECT_FALSE(lockManager.isConflictRequest(t));
+}
+
+TEST_F(LockTest, RequestConflictedWithLockTableEntries)
+{
+ MockLock lockManager;
+ const LockRequests& t = request1;
+ auto rc1 = lockManager.isConflictWithTable(t);
+ // Corrupt the lock type
+ std::get<2>(request[0]) = "Write";
+ // Corrupt the lockflag
+ std::get<4>(request[0])[1].first = "LockAll";
+ const LockRequests& p = request;
+ auto rc2 = lockManager.isConflictWithTable(p);
+ // Return a Conflict
+ EXPECT_TRUE(rc2.first);
+}
+
+TEST_F(LockTest, RequestNotConflictedWithLockTableEntries)
+{
+ MockLock lockManager;
+ const LockRequests& t = request1;
+ // Insert the request1 into the lock table
+ auto rc1 = lockManager.isConflictWithTable(t);
+ // Corrupt the lock type
+ std::get<2>(request[0]) = "Read";
+ // Corrupt the lockflag
+ std::get<4>(request[0])[1].first = "LockAll";
+ const LockRequests& p = request;
+ auto rc2 = lockManager.isConflictWithTable(p);
+ // Return No Conflict
+ EXPECT_FALSE(rc2.first);
+}
+
+TEST_F(LockTest, TestGenerateTransactionIDFunction)
+{
+ MockLock lockManager;
+ uint32_t transactionId1 = lockManager.generateTransactionId();
+ uint32_t transactionId2 = lockManager.generateTransactionId();
+ EXPECT_EQ(transactionId2, ++transactionId1);
+}
+
+TEST_F(LockTest, ValidateTransactionIDsGoodTestCase)
+{
+ MockLock lockManager;
+ const LockRequests& t = request1;
+ // Insert the request1 into the lock table
+ auto rc1 = lockManager.isConflictWithTable(t);
+ std::vector<uint32_t> tids = {1};
+ const std::vector<uint32_t>& p = tids;
+ EXPECT_TRUE(lockManager.validateRids(p));
+}
+
+TEST_F(LockTest, ValidateTransactionIDsBadTestCase)
+{
+ MockLock lockManager;
+ // Insert the request1 into the lock table
+ const LockRequests& t = request1;
+ auto rc1 = lockManager.isConflictWithTable(t);
+ std::vector<uint32_t> tids = {10};
+ const std::vector<uint32_t>& p = tids;
+ EXPECT_FALSE(lockManager.validateRids(p));
+}
+
+TEST_F(LockTest, ValidateisItMyLockGoodTestCase)
+{
+ MockLock lockManager;
+ // Insert the request1 into the lock table
+ const LockRequests& t = request1;
+ auto rc1 = lockManager.isConflictWithTable(t);
+ std::vector<uint32_t> tids = {1};
+ const std::vector<uint32_t>& p = tids;
+ std::string hmcid = "hmc-id";
+ std::string sessionid = "xxxxx";
+ std::pair<SType, SType> ids = std::make_pair(hmcid, sessionid);
+ auto rc = lockManager.isItMyLock(p, ids);
+ EXPECT_TRUE(rc.first);
+}
+
+TEST_F(LockTest, ValidateisItMyLockBadTestCase)
+{
+ MockLock lockManager;
+ // Corrupt the client identifier
+ std::get<1>(request1[0]) = "randomid";
+ // Insert the request1 into the lock table
+ const LockRequests& t = request1;
+ auto rc1 = lockManager.isConflictWithTable(t);
+ std::vector<uint32_t> tids = {1};
+ const std::vector<uint32_t>& p = tids;
+ std::string hmcid = "hmc-id";
+ std::string sessionid = "random";
+ std::pair<SType, SType> ids = std::make_pair(hmcid, sessionid);
+ auto rc = lockManager.isItMyLock(p, ids);
+ EXPECT_FALSE(rc.first);
+}
+
+TEST_F(LockTest, ValidateSessionIDForGetlocklistBadTestCase)
+{
+ MockLock lockManager;
+ // Insert the request1 into the lock table
+ const LockRequests& t = request1;
+ auto rc1 = lockManager.isConflictWithTable(t);
+ std::vector<std::string> sessionid = {"random"};
+ auto status = lockManager.getLockList(sessionid);
+ auto result =
+ std::get<std::vector<std::pair<uint32_t, LockRequests>>>(status);
+ EXPECT_THAT(result, IsEmpty());
+}
+
+TEST_F(LockTest, ValidateSessionIDForGetlocklistGoodTestCase)
+{
+ MockLock lockManager;
+ // Insert the request1 into the lock table
+ const LockRequests& t = request1;
+ auto rc1 = lockManager.isConflictWithTable(t);
+ std::vector<std::string> sessionid = {"xxxxx"};
+ auto status = lockManager.getLockList(sessionid);
+ auto result =
+ std::get<std::vector<std::pair<uint32_t, LockRequests>>>(status);
+ EXPECT_EQ(result.size(), 1);
+}
+
+} // namespace
+} // namespace crow::ibm_mc_lock
diff --git a/test/include/multipart_test.cpp b/test/include/multipart_test.cpp
new file mode 100644
index 0000000000..7ad7d98f89
--- /dev/null
+++ b/test/include/multipart_test.cpp
@@ -0,0 +1,256 @@
+#include "http/http_request.hpp"
+#include "multipart_parser.hpp"
+
+#include <boost/beast/http/fields.hpp>
+#include <boost/beast/http/message.hpp>
+#include <boost/beast/http/string_body.hpp>
+
+#include <memory>
+#include <string_view>
+#include <system_error>
+#include <vector>
+
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+// IWYU pragma: no_include <boost/beast/http/impl/fields.hpp>
+// IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp>
+// IWYU pragma: no_include <boost/intrusive/detail/tree_iterator.hpp>
+
+namespace
+{
+using ::testing::Test;
+
+class MultipartTest : public Test
+{
+ public:
+ boost::beast::http::request<boost::beast::http::string_body> req{};
+ MultipartParser parser;
+ std::error_code ec;
+};
+
+TEST_F(MultipartTest, TestGoodMultipartParser)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
+ "111111111111111111111111112222222222222222222222222222222\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "{\r\n-----------------------------d74496d66958873e123456\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test3\"\r\n\r\n"
+ "{\r\n--------d74496d6695887}\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ ParserError rc = parser.parse(reqIn);
+ ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
+
+ EXPECT_EQ(parser.boundary,
+ "\r\n-----------------------------d74496d66958873e");
+ EXPECT_EQ(parser.mime_fields.size(), 3);
+
+ EXPECT_EQ(parser.mime_fields[0].fields.at("Content-Disposition"),
+ "form-data; name=\"Test1\"");
+ EXPECT_EQ(parser.mime_fields[0].content,
+ "111111111111111111111111112222222222222222222222222222222");
+
+ EXPECT_EQ(parser.mime_fields[1].fields.at("Content-Disposition"),
+ "form-data; name=\"Test2\"");
+ EXPECT_EQ(parser.mime_fields[1].content,
+ "{\r\n-----------------------------d74496d66958873e123456");
+ EXPECT_EQ(parser.mime_fields[2].fields.at("Content-Disposition"),
+ "form-data; name=\"Test3\"");
+ EXPECT_EQ(parser.mime_fields[2].content, "{\r\n--------d74496d6695887}");
+}
+
+TEST_F(MultipartTest, TestBadMultipartParser1)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
+ "1234567890\r\n"
+ "-----------------------------d74496d66958873e\r-\r\n";
+
+ crow::Request reqIn(req, ec);
+ ParserError rc = parser.parse(reqIn);
+ ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
+
+ EXPECT_EQ(parser.boundary,
+ "\r\n-----------------------------d74496d66958873e");
+ EXPECT_EQ(parser.mime_fields.size(), 1);
+}
+
+TEST_F(MultipartTest, TestBadMultipartParser2)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
+ "abcd\r\n"
+ "-----------------------------d74496d66958873e-\r\n";
+
+ crow::Request reqIn(req, ec);
+ ParserError rc = parser.parse(reqIn);
+ ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
+
+ EXPECT_EQ(parser.boundary,
+ "\r\n-----------------------------d74496d66958873e");
+ EXPECT_EQ(parser.mime_fields.size(), 1);
+}
+
+TEST_F(MultipartTest, TestErrorBoundaryFormat)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary+=-----------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
+ "{\"Key1\": 11223333333333333333333333333333333333333333}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_BOUNDARY_FORMAT);
+}
+
+TEST_F(MultipartTest, TestErrorBoundaryCR)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r"
+ "{\"Key1\": 112233}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_BOUNDARY_CR);
+}
+
+TEST_F(MultipartTest, TestErrorBoundaryLF)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
+ "{\"Key1\": 112233}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_BOUNDARY_LF);
+}
+
+TEST_F(MultipartTest, TestErrorBoundaryData)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d7449sd6d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
+ "{\"Key1\": 112233}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_BOUNDARY_DATA);
+}
+
+TEST_F(MultipartTest, TestErrorEmptyHeader)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ ": form-data; name=\"Test1\"\r\n"
+ "{\"Key1\": 112233}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_EMPTY_HEADER);
+}
+
+TEST_F(MultipartTest, TestErrorHeaderName)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-!!Disposition: form-data; name=\"Test1\"\r\n"
+ "{\"Key1\": 112233}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_HEADER_NAME);
+}
+
+TEST_F(MultipartTest, TestErrorHeaderValue)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r"
+ "{\"Key1\": 112233}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_HEADER_VALUE);
+}
+
+TEST_F(MultipartTest, TestErrorHeaderEnding)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r"
+ "{\"Key1\": 112233}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_HEADER_ENDING);
+}
+} // namespace \ No newline at end of file
diff --git a/test/include/openbmc_dbus_rest_test.cpp b/test/include/openbmc_dbus_rest_test.cpp
new file mode 100644
index 0000000000..b01e92a505
--- /dev/null
+++ b/test/include/openbmc_dbus_rest_test.cpp
@@ -0,0 +1,76 @@
+#include "openbmc_dbus_rest.hpp"
+
+#include <gmock/gmock.h> // IWYU pragma: keep
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+// IWYU pragma: no_include <gmock/gmock-matchers.h>
+
+namespace crow::openbmc_mapper
+{
+namespace
+{
+
+using ::testing::ElementsAre;
+// Also see redfish-core/ut/configfile_test.cpp
+TEST(OpenbmcDbusRestTest, ValidFilenameGood)
+{
+ EXPECT_TRUE(validateFilename("GoodConfigFile"));
+ EXPECT_TRUE(validateFilename("_Underlines_"));
+ EXPECT_TRUE(validateFilename("8675309"));
+ EXPECT_TRUE(validateFilename("-Dashes-"));
+ EXPECT_TRUE(validateFilename("With Spaces"));
+ EXPECT_TRUE(validateFilename("One.Dot"));
+ EXPECT_TRUE(validateFilename("trailingdot."));
+ EXPECT_TRUE(validateFilename("-_ o _-"));
+ EXPECT_TRUE(validateFilename(" "));
+ EXPECT_TRUE(validateFilename(" ."));
+}
+
+// There is no length test yet because validateFilename() does not care yet
+TEST(OpenbmcDbusRestTest, ValidFilenameBad)
+{
+ EXPECT_FALSE(validateFilename(""));
+ EXPECT_FALSE(validateFilename("Bad@file"));
+ EXPECT_FALSE(validateFilename("/../../../../../etc/badpath"));
+ EXPECT_FALSE(validateFilename("/../../etc/badpath"));
+ EXPECT_FALSE(validateFilename("/mydir/configFile"));
+ EXPECT_FALSE(validateFilename("/"));
+ EXPECT_FALSE(validateFilename(".leadingdot"));
+ EXPECT_FALSE(validateFilename("Two..Dots"));
+ EXPECT_FALSE(validateFilename("../../../../../../etc/shadow"));
+ EXPECT_FALSE(validateFilename("."));
+}
+
+TEST(OpenBmcDbusTest, TestArgSplit)
+{
+ // test the basic types
+ EXPECT_THAT(dbusArgSplit("x"), ElementsAre("x"));
+ EXPECT_THAT(dbusArgSplit("y"), ElementsAre("y"));
+ EXPECT_THAT(dbusArgSplit("b"), ElementsAre("b"));
+ EXPECT_THAT(dbusArgSplit("n"), ElementsAre("n"));
+ EXPECT_THAT(dbusArgSplit("q"), ElementsAre("q"));
+ EXPECT_THAT(dbusArgSplit("i"), ElementsAre("i"));
+ EXPECT_THAT(dbusArgSplit("u"), ElementsAre("u"));
+ EXPECT_THAT(dbusArgSplit("x"), ElementsAre("x"));
+ EXPECT_THAT(dbusArgSplit("t"), ElementsAre("t"));
+ EXPECT_THAT(dbusArgSplit("d"), ElementsAre("d"));
+ EXPECT_THAT(dbusArgSplit("h"), ElementsAre("h"));
+ // test arrays
+ EXPECT_THAT(dbusArgSplit("ai"), ElementsAre("ai"));
+ EXPECT_THAT(dbusArgSplit("ax"), ElementsAre("ax"));
+ // test tuples
+ EXPECT_THAT(dbusArgSplit("(sss)"), ElementsAre("(sss)"));
+ EXPECT_THAT(dbusArgSplit("(sss)b"), ElementsAre("(sss)", "b"));
+ EXPECT_THAT(dbusArgSplit("b(sss)"), ElementsAre("b", "(sss)"));
+
+ // Test nested types
+ EXPECT_THAT(dbusArgSplit("a{si}b"), ElementsAre("a{si}", "b"));
+ EXPECT_THAT(dbusArgSplit("a(sss)b"), ElementsAre("a(sss)", "b"));
+ EXPECT_THAT(dbusArgSplit("aa{si}b"), ElementsAre("aa{si}", "b"));
+ EXPECT_THAT(dbusArgSplit("i{si}b"), ElementsAre("i", "{si}", "b"));
+}
+} // namespace
+} // namespace crow::openbmc_mapper
diff --git a/test/redfish-core/include/privileges_test.cpp b/test/redfish-core/include/privileges_test.cpp
new file mode 100644
index 0000000000..2d0da021eb
--- /dev/null
+++ b/test/redfish-core/include/privileges_test.cpp
@@ -0,0 +1,138 @@
+#include "privileges.hpp"
+
+#include <boost/beast/http/verb.hpp>
+
+#include <array>
+
+#include <gmock/gmock.h> // IWYU pragma: keep
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+// IWYU pragma: no_include <gmock/gmock-matchers.h>
+// IWYU pragma: no_include <gmock/gmock-more-matchers.h>
+
+namespace redfish
+{
+namespace
+{
+
+using ::testing::IsEmpty;
+using ::testing::UnorderedElementsAre;
+
+TEST(PrivilegeTest, PrivilegeConstructor)
+{
+ Privileges privileges{"Login", "ConfigureManager"};
+
+ EXPECT_THAT(privileges.getActivePrivilegeNames(PrivilegeType::BASE),
+ UnorderedElementsAre("Login", "ConfigureManager"));
+}
+
+TEST(PrivilegeTest, PrivilegeCheckForNoPrivilegesRequired)
+{
+ Privileges userPrivileges{"Login"};
+
+ OperationMap entityPrivileges{{boost::beast::http::verb::get, {{"Login"}}}};
+
+ EXPECT_TRUE(isMethodAllowedWithPrivileges(
+ boost::beast::http::verb::get, entityPrivileges, userPrivileges));
+}
+
+TEST(PrivilegeTest, PrivilegeCheckForSingleCaseSuccess)
+{
+ auto userPrivileges = Privileges{"Login"};
+ OperationMap entityPrivileges{{boost::beast::http::verb::get, {}}};
+
+ EXPECT_TRUE(isMethodAllowedWithPrivileges(
+ boost::beast::http::verb::get, entityPrivileges, userPrivileges));
+}
+
+TEST(PrivilegeTest, PrivilegeCheckForSingleCaseFailure)
+{
+ auto userPrivileges = Privileges{"Login"};
+ OperationMap entityPrivileges{
+ {boost::beast::http::verb::get, {{"ConfigureManager"}}}};
+
+ EXPECT_FALSE(isMethodAllowedWithPrivileges(
+ boost::beast::http::verb::get, entityPrivileges, userPrivileges));
+}
+
+TEST(PrivilegeTest, PrivilegeCheckForANDCaseSuccess)
+{
+ auto userPrivileges =
+ Privileges{"Login", "ConfigureManager", "ConfigureSelf"};
+ OperationMap entityPrivileges{
+ {boost::beast::http::verb::get,
+ {{"Login", "ConfigureManager", "ConfigureSelf"}}}};
+
+ EXPECT_TRUE(isMethodAllowedWithPrivileges(
+ boost::beast::http::verb::get, entityPrivileges, userPrivileges));
+}
+
+TEST(PrivilegeTest, PrivilegeCheckForANDCaseFailure)
+{
+ auto userPrivileges = Privileges{"Login", "ConfigureManager"};
+ OperationMap entityPrivileges{
+ {boost::beast::http::verb::get,
+ {{"Login", "ConfigureManager", "ConfigureSelf"}}}};
+
+ EXPECT_FALSE(isMethodAllowedWithPrivileges(
+ boost::beast::http::verb::get, entityPrivileges, userPrivileges));
+}
+
+TEST(PrivilegeTest, PrivilegeCheckForORCaseSuccess)
+{
+ auto userPrivileges = Privileges{"ConfigureManager"};
+ OperationMap entityPrivileges{
+ {boost::beast::http::verb::get, {{"Login"}, {"ConfigureManager"}}}};
+
+ EXPECT_TRUE(isMethodAllowedWithPrivileges(
+ boost::beast::http::verb::get, entityPrivileges, userPrivileges));
+}
+
+TEST(PrivilegeTest, PrivilegeCheckForORCaseFailure)
+{
+ auto userPrivileges = Privileges{"ConfigureComponents"};
+ OperationMap entityPrivileges = OperationMap(
+ {{boost::beast::http::verb::get, {{"Login"}, {"ConfigureManager"}}}});
+
+ EXPECT_FALSE(isMethodAllowedWithPrivileges(
+ boost::beast::http::verb::get, entityPrivileges, userPrivileges));
+}
+
+TEST(PrivilegeTest, DefaultPrivilegeBitsetsAreEmpty)
+{
+ Privileges privileges;
+
+ EXPECT_THAT(privileges.getActivePrivilegeNames(PrivilegeType::BASE),
+ IsEmpty());
+
+ EXPECT_THAT(privileges.getActivePrivilegeNames(PrivilegeType::OEM),
+ IsEmpty());
+}
+
+TEST(PrivilegeTest, GetActivePrivilegeNames)
+{
+ Privileges privileges;
+
+ EXPECT_THAT(privileges.getActivePrivilegeNames(PrivilegeType::BASE),
+ IsEmpty());
+
+ std::array<const char*, 5> expectedPrivileges{
+ "Login", "ConfigureManager", "ConfigureUsers", "ConfigureComponents",
+ "ConfigureSelf"};
+
+ for (const auto& privilege : expectedPrivileges)
+ {
+ EXPECT_TRUE(privileges.setSinglePrivilege(privilege));
+ }
+
+ EXPECT_THAT(
+ privileges.getActivePrivilegeNames(PrivilegeType::BASE),
+ UnorderedElementsAre(expectedPrivileges[0], expectedPrivileges[1],
+ expectedPrivileges[2], expectedPrivileges[3],
+ expectedPrivileges[4]));
+}
+} // namespace
+} // namespace redfish \ No newline at end of file
diff --git a/test/redfish-core/include/registries_test.cpp b/test/redfish-core/include/registries_test.cpp
new file mode 100644
index 0000000000..95a093e54d
--- /dev/null
+++ b/test/redfish-core/include/registries_test.cpp
@@ -0,0 +1,25 @@
+#include "registries.hpp"
+
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+
+namespace redfish::registries
+{
+namespace
+{
+
+TEST(FillMessageArgs, ArgsAreFilledCorrectly)
+{
+ EXPECT_EQ(fillMessageArgs({{"foo"}}, "%1"), "foo");
+ EXPECT_EQ(fillMessageArgs({}, ""), "");
+ EXPECT_EQ(fillMessageArgs({{"foo", "bar"}}, "%1, %2"), "foo, bar");
+ EXPECT_EQ(fillMessageArgs({{"foo"}}, "%1 bar"), "foo bar");
+ EXPECT_EQ(fillMessageArgs({}, "%1"), "");
+ EXPECT_EQ(fillMessageArgs({}, "%"), "");
+ EXPECT_EQ(fillMessageArgs({}, "%foo"), "");
+}
+} // namespace
+} // namespace redfish::registries
diff --git a/test/redfish-core/include/utils/hex_utils_test.cpp b/test/redfish-core/include/utils/hex_utils_test.cpp
new file mode 100644
index 0000000000..8fd4638d09
--- /dev/null
+++ b/test/redfish-core/include/utils/hex_utils_test.cpp
@@ -0,0 +1,83 @@
+#include "utils/hex_utils.hpp"
+
+#include <cctype>
+#include <limits>
+
+#include <gmock/gmock.h> // IWYU pragma: keep
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+
+namespace
+{
+
+using ::testing::IsEmpty;
+
+TEST(IntToHexString, ReturnsCorrectHexForUint64)
+{
+ EXPECT_EQ(intToHexString(0xFFFFFFFFFFFFFFFFULL, 16), "FFFFFFFFFFFFFFFF");
+
+ EXPECT_EQ(intToHexString(0, 4), "0000");
+ EXPECT_EQ(intToHexString(0, 8), "00000000");
+ EXPECT_EQ(intToHexString(0, 12), "000000000000");
+ EXPECT_EQ(intToHexString(0, 16), "0000000000000000");
+
+ // uint64_t sized ints
+ EXPECT_EQ(intToHexString(0xDEADBEEFBAD4F00DULL, 4), "F00D");
+ EXPECT_EQ(intToHexString(0xDEADBEEFBAD4F00DULL, 8), "BAD4F00D");
+ EXPECT_EQ(intToHexString(0xDEADBEEFBAD4F00DULL, 12), "BEEFBAD4F00D");
+ EXPECT_EQ(intToHexString(0xDEADBEEFBAD4F00DULL, 16), "DEADBEEFBAD4F00D");
+
+ // uint16_t sized ints
+ EXPECT_EQ(intToHexString(0xBEEF, 1), "F");
+ EXPECT_EQ(intToHexString(0xBEEF, 2), "EF");
+ EXPECT_EQ(intToHexString(0xBEEF, 3), "EEF");
+ EXPECT_EQ(intToHexString(0xBEEF, 4), "BEEF");
+}
+
+TEST(BytesToHexString, OnSuccess)
+{
+ EXPECT_EQ(bytesToHexString({0x1a, 0x2b}), "1A2B");
+}
+
+TEST(HexCharToNibble, ReturnsCorrectNibbleForEveryHexChar)
+{
+ for (char c = 0; c < std::numeric_limits<char>::max(); ++c)
+ {
+ uint8_t expected = 16;
+ if (isdigit(c) != 0)
+ {
+ expected = static_cast<uint8_t>(c) - '0';
+ }
+ else if (c >= 'A' && c <= 'F')
+ {
+ expected = static_cast<uint8_t>(c) - 'A' + 10;
+ }
+ else if (c >= 'a' && c <= 'f')
+ {
+ expected = static_cast<uint8_t>(c) - 'a' + 10;
+ }
+
+ EXPECT_EQ(hexCharToNibble(c), expected);
+ }
+}
+
+TEST(HexStringToBytes, Success)
+{
+ std::vector<uint8_t> hexBytes = {0x01, 0x23, 0x45, 0x67,
+ 0x89, 0xAB, 0xCD, 0xEF};
+ EXPECT_EQ(hexStringToBytes("0123456789ABCDEF"), hexBytes);
+ EXPECT_THAT(hexStringToBytes(""), IsEmpty());
+}
+
+TEST(HexStringToBytes, Failure)
+{
+ EXPECT_THAT(hexStringToBytes("Hello"), IsEmpty());
+ EXPECT_THAT(hexStringToBytes("`"), IsEmpty());
+ EXPECT_THAT(hexStringToBytes("012"), IsEmpty());
+}
+
+} // namespace \ No newline at end of file
diff --git a/test/redfish-core/include/utils/ip_utils_test.cpp b/test/redfish-core/include/utils/ip_utils_test.cpp
new file mode 100644
index 0000000000..f358c51477
--- /dev/null
+++ b/test/redfish-core/include/utils/ip_utils_test.cpp
@@ -0,0 +1,38 @@
+#include "utils/ip_utils.hpp"
+
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+
+namespace redfish::ip_util
+{
+namespace
+{
+
+using ::boost::asio::ip::make_address;
+
+TEST(IpToString, ReturnsCorrectIpStringForIpv4Addresses)
+{
+ EXPECT_EQ(toString(make_address("127.0.0.1")), "127.0.0.1");
+ EXPECT_EQ(toString(make_address("192.168.1.1")), "192.168.1.1");
+ EXPECT_EQ(toString(make_address("::1")), "::1");
+}
+
+TEST(IpToString, ReturnsCorrectIpStringForIpv6Addresses)
+{
+ EXPECT_EQ(toString(make_address("fd03:f9ab:25de:89ec::0001")),
+ "fd03:f9ab:25de:89ec::1");
+ EXPECT_EQ(toString(make_address("fd03:f9ab:25de:89ec::1234:abcd")),
+ "fd03:f9ab:25de:89ec::1234:abcd");
+ EXPECT_EQ(toString(make_address("fd03:f9ab:25de:89ec:1234:5678:90ab:cdef")),
+ "fd03:f9ab:25de:89ec:1234:5678:90ab:cdef");
+}
+
+TEST(IpToString, ReturnsCorrectIpStringForIpv4MappedIpv6Addresses)
+{
+ EXPECT_EQ(toString(make_address("::ffff:127.0.0.1")), "127.0.0.1");
+}
+} // namespace
+} // namespace redfish::ip_util \ No newline at end of file
diff --git a/test/redfish-core/include/utils/json_utils_test.cpp b/test/redfish-core/include/utils/json_utils_test.cpp
new file mode 100644
index 0000000000..826d4375a6
--- /dev/null
+++ b/test/redfish-core/include/utils/json_utils_test.cpp
@@ -0,0 +1,358 @@
+#include "http_request.hpp"
+#include "http_response.hpp"
+#include "utils/json_utils.hpp"
+
+#include <boost/beast/http/status.hpp>
+#include <nlohmann/json.hpp>
+
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <system_error>
+#include <vector>
+
+#include <gmock/gmock.h> // IWYU pragma: keep
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+// IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp>
+
+namespace redfish::json_util
+{
+namespace
+{
+
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+using ::testing::Not;
+
+TEST(ReadJson, ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly)
+{
+ crow::Response res;
+ nlohmann::json jsonRequest = {{"integer", 1},
+ {"string", "hello"},
+ {"vector", std::vector<uint64_t>{1, 2, 3}}};
+
+ int64_t integer = 0;
+ std::string str;
+ std::vector<uint64_t> vec;
+ ASSERT_TRUE(readJson(jsonRequest, res, "integer", integer, "string", str,
+ "vector", vec));
+ EXPECT_EQ(res.result(), boost::beast::http::status::ok);
+ EXPECT_THAT(res.jsonValue, IsEmpty());
+
+ EXPECT_EQ(integer, 1);
+ EXPECT_EQ(str, "hello");
+ EXPECT_THAT(vec, ElementsAre(1, 2, 3));
+}
+
+TEST(readJson, ExtraElementsReturnsFalseReponseIsBadRequest)
+{
+ crow::Response res;
+ nlohmann::json jsonRequest = {{"integer", 1}, {"string", "hello"}};
+
+ std::optional<int> integer;
+ std::optional<std::string> str;
+
+ ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
+ EXPECT_EQ(integer, 1);
+
+ ASSERT_FALSE(readJson(jsonRequest, res, "string", str));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
+ EXPECT_EQ(str, "hello");
+}
+
+TEST(ReadJson, WrongElementTypeReturnsFalseReponseIsBadRequest)
+{
+ crow::Response res;
+ nlohmann::json jsonRequest = {{"integer", 1}, {"string0", "hello"}};
+
+ int64_t integer = 0;
+ std::string str0;
+ ASSERT_FALSE(readJson(jsonRequest, res, "integer", str0));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
+
+ ASSERT_FALSE(readJson(jsonRequest, res, "string0", integer));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
+
+ ASSERT_FALSE(
+ readJson(jsonRequest, res, "integer", str0, "string0", integer));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
+}
+
+TEST(ReadJson, MissingElementReturnsFalseReponseIsBadRequest)
+{
+ crow::Response res;
+ nlohmann::json jsonRequest = {{"integer", 1}, {"string0", "hello"}};
+
+ int64_t integer = 0;
+ std::string str0;
+ std::string str1;
+ std::vector<uint8_t> vec;
+ ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string0", str0,
+ "vector", vec));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
+
+ ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string0", str0,
+ "string1", str1));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
+}
+
+TEST(ReadJson, JsonArrayAreUnpackedCorrectly)
+{
+ crow::Response res;
+ nlohmann::json jsonRequest = R"(
+ {
+ "TestJson": [{"hello": "yes"}, [{"there": "no"}, "nice"]]
+ }
+ )"_json;
+
+ std::vector<nlohmann::json> jsonVec;
+ ASSERT_TRUE(readJson(jsonRequest, res, "TestJson", jsonVec));
+ EXPECT_EQ(res.result(), boost::beast::http::status::ok);
+ EXPECT_THAT(res.jsonValue, IsEmpty());
+ EXPECT_EQ(jsonVec, R"([{"hello": "yes"}, [{"there": "no"}, "nice"]])"_json);
+}
+
+TEST(ReadJson, JsonSubElementValueAreUnpackedCorrectly)
+{
+ crow::Response res;
+ nlohmann::json jsonRequest = R"(
+ {
+ "json": {"integer": 42}
+ }
+ )"_json;
+
+ int integer = 0;
+ ASSERT_TRUE(readJson(jsonRequest, res, "json/integer", integer));
+ EXPECT_EQ(integer, 42);
+ EXPECT_EQ(res.result(), boost::beast::http::status::ok);
+ EXPECT_THAT(res.jsonValue, IsEmpty());
+}
+
+TEST(ReadJson, JsonDeeperSubElementValueAreUnpackedCorrectly)
+{
+ crow::Response res;
+ nlohmann::json jsonRequest = R"(
+ {
+ "json": {
+ "json2": {"string": "foobar"}
+ }
+ }
+ )"_json;
+
+ std::string foobar;
+ ASSERT_TRUE(readJson(jsonRequest, res, "json/json2/string", foobar));
+ EXPECT_EQ(foobar, "foobar");
+ EXPECT_EQ(res.result(), boost::beast::http::status::ok);
+ EXPECT_THAT(res.jsonValue, IsEmpty());
+}
+
+TEST(ReadJson, MultipleJsonSubElementValueAreUnpackedCorrectly)
+{
+ crow::Response res;
+ nlohmann::json jsonRequest = R"(
+ {
+ "json": {
+ "integer": 42,
+ "string": "foobar"
+ },
+ "string": "bazbar"
+ }
+ )"_json;
+
+ int integer = 0;
+ std::string foobar;
+ std::string bazbar;
+ ASSERT_TRUE(readJson(jsonRequest, res, "json/integer", integer,
+ "json/string", foobar, "string", bazbar));
+ EXPECT_EQ(integer, 42);
+ EXPECT_EQ(foobar, "foobar");
+ EXPECT_EQ(bazbar, "bazbar");
+ EXPECT_EQ(res.result(), boost::beast::http::status::ok);
+ EXPECT_THAT(res.jsonValue, IsEmpty());
+}
+
+TEST(ReadJson, ExtraElement)
+{
+ crow::Response res;
+ nlohmann::json jsonRequest = {{"integer", 1}, {"string", "hello"}};
+
+ std::optional<int> integer;
+ std::optional<std::string> str;
+
+ EXPECT_FALSE(readJson(jsonRequest, res, "integer", integer));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_FALSE(res.jsonValue.empty());
+ EXPECT_EQ(integer, 1);
+
+ EXPECT_FALSE(readJson(jsonRequest, res, "string", str));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_FALSE(res.jsonValue.empty());
+ EXPECT_EQ(str, "hello");
+}
+
+TEST(ReadJson, ValidMissingElementReturnsTrue)
+{
+ crow::Response res;
+ nlohmann::json jsonRequest = {{"integer", 1}};
+
+ std::optional<int> integer;
+ int requiredInteger = 0;
+ std::optional<std::string> str0;
+ std::optional<std::string> str1;
+ std::optional<std::vector<uint8_t>> vec;
+ ASSERT_TRUE(readJson(jsonRequest, res, "missing_integer", integer,
+ "integer", requiredInteger));
+ EXPECT_EQ(res.result(), boost::beast::http::status::ok);
+ EXPECT_TRUE(res.jsonValue.empty());
+ EXPECT_EQ(integer, std::nullopt);
+
+ ASSERT_TRUE(readJson(jsonRequest, res, "missing_string", str0, "integer",
+ requiredInteger));
+ EXPECT_EQ(res.result(), boost::beast::http::status::ok);
+ EXPECT_THAT(res.jsonValue, IsEmpty());
+ EXPECT_EQ(str0, std::nullopt);
+
+ ASSERT_TRUE(readJson(jsonRequest, res, "integer", integer, "string", str0,
+ "vector", vec));
+ EXPECT_EQ(res.result(), boost::beast::http::status::ok);
+ EXPECT_THAT(res.jsonValue, IsEmpty());
+ EXPECT_EQ(integer, 1);
+ EXPECT_EQ(str0, std::nullopt);
+ EXPECT_EQ(vec, std::nullopt);
+
+ ASSERT_TRUE(readJson(jsonRequest, res, "integer", integer, "string0", str0,
+ "missing_string", str1));
+ EXPECT_EQ(res.result(), boost::beast::http::status::ok);
+ EXPECT_THAT(res.jsonValue, IsEmpty());
+ EXPECT_EQ(str1, std::nullopt);
+}
+
+TEST(ReadJson, InvalidMissingElementReturnsFalse)
+{
+ crow::Response res;
+ nlohmann::json jsonRequest = {{"integer", 1}, {"string", "hello"}};
+
+ int integer = 0;
+ std::string str0;
+ std::string str1;
+ std::vector<uint8_t> vec;
+ ASSERT_FALSE(readJson(jsonRequest, res, "missing_integer", integer));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
+
+ ASSERT_FALSE(readJson(jsonRequest, res, "missing_string", str0));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
+
+ ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string", str0,
+ "vector", vec));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
+
+ ASSERT_FALSE(readJson(jsonRequest, res, "integer", integer, "string0", str0,
+ "missing_string", str1));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
+}
+
+TEST(ReadJsonPatch, ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly)
+{
+ crow::Response res;
+ std::error_code ec;
+ crow::Request req({}, ec);
+ // Ignore errors intentionally
+ req.body = "{\"integer\": 1}";
+
+ int64_t integer = 0;
+ ASSERT_TRUE(readJsonPatch(req, res, "integer", integer));
+ EXPECT_EQ(res.result(), boost::beast::http::status::ok);
+ EXPECT_THAT(res.jsonValue, IsEmpty());
+ EXPECT_EQ(integer, 1);
+}
+
+TEST(ReadJsonPatch, EmptyObjectReturnsFalseResponseBadRequest)
+{
+ crow::Response res;
+ std::error_code ec;
+ crow::Request req({}, ec);
+ // Ignore errors intentionally
+ req.body = "{}";
+
+ std::optional<int64_t> integer = 0;
+ ASSERT_FALSE(readJsonPatch(req, res, "integer", integer));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
+}
+
+TEST(ReadJsonPatch, OdataIgnored)
+{
+ crow::Response res;
+ std::error_code ec;
+ crow::Request req({}, ec);
+ // Ignore errors intentionally
+ req.body = R"({"@odata.etag": "etag", "integer": 1})";
+
+ std::optional<int64_t> integer = 0;
+ ASSERT_TRUE(readJsonPatch(req, res, "integer", integer));
+ EXPECT_EQ(res.result(), boost::beast::http::status::ok);
+ EXPECT_THAT(res.jsonValue, IsEmpty());
+ EXPECT_EQ(integer, 1);
+}
+
+TEST(ReadJsonPatch, OnlyOdataGivesNoOperation)
+{
+ crow::Response res;
+ std::error_code ec;
+ crow::Request req({}, ec);
+ // Ignore errors intentionally
+ req.body = R"({"@odata.etag": "etag"})";
+
+ std::optional<int64_t> integer = 0;
+ ASSERT_FALSE(readJsonPatch(req, res, "integer", integer));
+ EXPECT_EQ(res.result(), boost::beast::http::status::bad_request);
+ EXPECT_THAT(res.jsonValue, Not(IsEmpty()));
+}
+
+TEST(ReadJsonAction, ValidElementsReturnsTrueResponseOkValuesUnpackedCorrectly)
+{
+ crow::Response res;
+ std::error_code ec;
+ crow::Request req({}, ec);
+ // Ignore errors intentionally
+ req.body = "{\"integer\": 1}";
+
+ int64_t integer = 0;
+ ASSERT_TRUE(readJsonAction(req, res, "integer", integer));
+ EXPECT_EQ(res.result(), boost::beast::http::status::ok);
+ EXPECT_THAT(res.jsonValue, IsEmpty());
+ EXPECT_EQ(integer, 1);
+}
+
+TEST(ReadJsonAction, EmptyObjectReturnsTrueResponseOk)
+{
+ crow::Response res;
+ std::error_code ec;
+ crow::Request req({}, ec);
+ // Ignore errors intentionally
+ req.body = "{}";
+
+ std::optional<int64_t> integer = 0;
+ ASSERT_TRUE(readJsonAction(req, res, "integer", integer));
+ EXPECT_EQ(res.result(), boost::beast::http::status::ok);
+ EXPECT_THAT(res.jsonValue, IsEmpty());
+}
+
+} // namespace
+} // namespace redfish::json_util \ No newline at end of file
diff --git a/test/redfish-core/include/utils/query_param_test.cpp b/test/redfish-core/include/utils/query_param_test.cpp
new file mode 100644
index 0000000000..51f0b7101f
--- /dev/null
+++ b/test/redfish-core/include/utils/query_param_test.cpp
@@ -0,0 +1,709 @@
+#include "bmcweb_config.h"
+
+#include "utils/query_param.hpp"
+
+#include <boost/system/result.hpp>
+#include <boost/url/url_view.hpp>
+#include <nlohmann/json.hpp>
+
+#include <new>
+#include <span>
+
+#include <gmock/gmock.h> // IWYU pragma: keep
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+// IWYU pragma: no_include <boost/url/impl/url_view.hpp>
+// IWYU pragma: no_include <gmock/gmock-matchers.h>
+// IWYU pragma: no_include <gtest/gtest-matchers.h>
+
+namespace redfish::query_param
+{
+namespace
+{
+
+using ::testing::UnorderedElementsAre;
+
+TEST(Delegate, OnlyPositive)
+{
+ Query query{
+ .isOnly = true,
+ };
+ QueryCapabilities capabilities{
+ .canDelegateOnly = true,
+ };
+ Query delegated = delegate(capabilities, query);
+ EXPECT_TRUE(delegated.isOnly);
+ EXPECT_FALSE(query.isOnly);
+}
+
+TEST(Delegate, ExpandPositive)
+{
+ Query query{
+ .isOnly = false,
+ .expandLevel = 5,
+ .expandType = ExpandType::Both,
+ };
+ QueryCapabilities capabilities{
+ .canDelegateExpandLevel = 3,
+ };
+ Query delegated = delegate(capabilities, query);
+ EXPECT_FALSE(delegated.isOnly);
+ EXPECT_EQ(delegated.expandLevel, capabilities.canDelegateExpandLevel);
+ EXPECT_EQ(delegated.expandType, ExpandType::Both);
+ EXPECT_EQ(query.expandLevel, 2);
+}
+
+TEST(Delegate, OnlyNegative)
+{
+ Query query{
+ .isOnly = true,
+ };
+ QueryCapabilities capabilities{
+ .canDelegateOnly = false,
+ };
+ Query delegated = delegate(capabilities, query);
+ EXPECT_FALSE(delegated.isOnly);
+ EXPECT_EQ(query.isOnly, true);
+}
+
+TEST(Delegate, ExpandNegative)
+{
+ Query query{
+ .isOnly = false,
+ .expandType = ExpandType::None,
+ };
+ Query delegated = delegate(QueryCapabilities{}, query);
+ EXPECT_EQ(delegated.expandType, ExpandType::None);
+}
+
+TEST(Delegate, TopNegative)
+{
+ Query query{
+ .top = 42,
+ };
+ Query delegated = delegate(QueryCapabilities{}, query);
+ EXPECT_EQ(delegated.top, std::nullopt);
+ EXPECT_EQ(query.top, 42);
+}
+
+TEST(Delegate, TopPositive)
+{
+ Query query{
+ .top = 42,
+ };
+ QueryCapabilities capabilities{
+ .canDelegateTop = true,
+ };
+ Query delegated = delegate(capabilities, query);
+ EXPECT_EQ(delegated.top, 42);
+ EXPECT_EQ(query.top, std::nullopt);
+}
+
+TEST(Delegate, SkipNegative)
+{
+ Query query{
+ .skip = 42,
+ };
+ Query delegated = delegate(QueryCapabilities{}, query);
+ EXPECT_EQ(delegated.skip, std::nullopt);
+ EXPECT_EQ(query.skip, 42);
+}
+
+TEST(Delegate, SkipPositive)
+{
+ Query query{
+ .skip = 42,
+ };
+ QueryCapabilities capabilities{
+ .canDelegateSkip = true,
+ };
+ Query delegated = delegate(capabilities, query);
+ EXPECT_EQ(delegated.skip, 42);
+ EXPECT_EQ(query.skip, 0);
+}
+
+TEST(FormatQueryForExpand, NoSubQueryWhenQueryIsEmpty)
+{
+ EXPECT_EQ(formatQueryForExpand(Query{}), "");
+}
+
+TEST(FormatQueryForExpand, NoSubQueryWhenExpandLevelsLeOne)
+{
+ EXPECT_EQ(formatQueryForExpand(
+ Query{.expandLevel = 1, .expandType = ExpandType::Both}),
+ "");
+ EXPECT_EQ(formatQueryForExpand(Query{.expandType = ExpandType::Links}), "");
+ EXPECT_EQ(formatQueryForExpand(Query{.expandType = ExpandType::NotLinks}),
+ "");
+}
+
+TEST(FormatQueryForExpand, NoSubQueryWhenExpandTypeIsNone)
+{
+ EXPECT_EQ(formatQueryForExpand(
+ Query{.expandLevel = 2, .expandType = ExpandType::None}),
+ "");
+}
+
+TEST(FormatQueryForExpand, DelegatedSubQueriesHaveSameTypeAndOneLessLevels)
+{
+ EXPECT_EQ(formatQueryForExpand(
+ Query{.expandLevel = 3, .expandType = ExpandType::Both}),
+ "?$expand=*($levels=2)");
+ EXPECT_EQ(formatQueryForExpand(
+ Query{.expandLevel = 4, .expandType = ExpandType::Links}),
+ "?$expand=~($levels=3)");
+ EXPECT_EQ(formatQueryForExpand(
+ Query{.expandLevel = 2, .expandType = ExpandType::NotLinks}),
+ "?$expand=.($levels=1)");
+}
+
+TEST(IsSelectedPropertyAllowed, NotAllowedCharactersReturnsFalse)
+{
+ EXPECT_FALSE(isSelectedPropertyAllowed("?"));
+ EXPECT_FALSE(isSelectedPropertyAllowed("!"));
+ EXPECT_FALSE(isSelectedPropertyAllowed("-"));
+ EXPECT_FALSE(isSelectedPropertyAllowed("/"));
+}
+
+TEST(IsSelectedPropertyAllowed, EmptyStringReturnsFalse)
+{
+ EXPECT_FALSE(isSelectedPropertyAllowed(""));
+}
+
+TEST(IsSelectedPropertyAllowed, TooLongStringReturnsFalse)
+{
+ std::string strUnderTest = "ab";
+ // 2^10
+ for (int i = 0; i < 10; ++i)
+ {
+ strUnderTest += strUnderTest;
+ }
+ EXPECT_FALSE(isSelectedPropertyAllowed(strUnderTest));
+}
+
+TEST(IsSelectedPropertyAllowed, ValidPropertReturnsTrue)
+{
+ EXPECT_TRUE(isSelectedPropertyAllowed("Chassis"));
+ EXPECT_TRUE(isSelectedPropertyAllowed("@odata.type"));
+ EXPECT_TRUE(isSelectedPropertyAllowed("#ComputerSystem.Reset"));
+ EXPECT_TRUE(isSelectedPropertyAllowed(
+ "BootSourceOverrideTarget@Redfish.AllowableValues"));
+}
+
+TEST(GetSelectParam, EmptyValueReturnsError)
+{
+ Query query;
+ EXPECT_FALSE(getSelectParam("", query));
+}
+
+TEST(GetSelectParam, EmptyPropertyReturnsError)
+{
+ Query query;
+ EXPECT_FALSE(getSelectParam(",", query));
+ EXPECT_FALSE(getSelectParam(",,", query));
+}
+
+TEST(GetSelectParam, InvalidPathPropertyReturnsError)
+{
+ Query query;
+ EXPECT_FALSE(getSelectParam("\0,\0", query));
+ EXPECT_FALSE(getSelectParam("%%%", query));
+}
+
+TEST(GetSelectParam, TrieNodesRespectAllProperties)
+{
+ Query query;
+ ASSERT_TRUE(getSelectParam("foo/bar,bar", query));
+ ASSERT_FALSE(query.selectTrie.root.empty());
+
+ const SelectTrieNode* child = query.selectTrie.root.find("foo");
+ ASSERT_NE(child, nullptr);
+ EXPECT_FALSE(child->isSelected());
+ ASSERT_NE(child->find("bar"), nullptr);
+ EXPECT_TRUE(child->find("bar")->isSelected());
+
+ ASSERT_NE(query.selectTrie.root.find("bar"), nullptr);
+ EXPECT_TRUE(query.selectTrie.root.find("bar")->isSelected());
+}
+
+SelectTrie getTrie(std::span<std::string_view> properties)
+{
+ SelectTrie trie;
+ for (auto const& property : properties)
+ {
+ EXPECT_TRUE(trie.insertNode(property));
+ }
+ return trie;
+}
+
+TEST(RecursiveSelect, ExpectedKeysAreSelectInSimpleObject)
+{
+ std::vector<std::string_view> properties = {"SelectMe"};
+ SelectTrie trie = getTrie(properties);
+ nlohmann::json root = R"({"SelectMe" : "foo", "OmitMe" : "bar"})"_json;
+ nlohmann::json expected = R"({"SelectMe" : "foo"})"_json;
+ recursiveSelect(root, trie.root);
+ EXPECT_EQ(root, expected);
+}
+
+TEST(RecursiveSelect, ExpectedKeysAreSelectInNestedObject)
+{
+ std::vector<std::string_view> properties = {
+ "SelectMe", "Prefix0/ExplicitSelectMe", "Prefix1", "Prefix2",
+ "Prefix4/ExplicitSelectMe"};
+ SelectTrie trie = getTrie(properties);
+ nlohmann::json root = R"(
+{
+ "SelectMe":[
+ "foo"
+ ],
+ "OmitMe":"bar",
+ "Prefix0":{
+ "ExplicitSelectMe":"123",
+ "OmitMe":"456"
+ },
+ "Prefix1":{
+ "ImplicitSelectMe":"123"
+ },
+ "Prefix2":[
+ {
+ "ImplicitSelectMe":"123"
+ }
+ ],
+ "Prefix3":[
+ "OmitMe"
+ ],
+ "Prefix4":[
+ {
+ "ExplicitSelectMe":"123",
+ "OmitMe": "456"
+ }
+ ]
+}
+)"_json;
+ nlohmann::json expected = R"(
+{
+ "SelectMe":[
+ "foo"
+ ],
+ "Prefix0":{
+ "ExplicitSelectMe":"123"
+ },
+ "Prefix1":{
+ "ImplicitSelectMe":"123"
+ },
+ "Prefix2":[
+ {
+ "ImplicitSelectMe":"123"
+ }
+ ],
+ "Prefix4":[
+ {
+ "ExplicitSelectMe":"123"
+ }
+ ]
+}
+)"_json;
+ recursiveSelect(root, trie.root);
+ EXPECT_EQ(root, expected);
+}
+
+TEST(RecursiveSelect, ReservedPropertiesAreSelected)
+{
+ nlohmann::json root = R"(
+{
+ "OmitMe":"bar",
+ "@odata.id":1,
+ "@odata.type":2,
+ "@odata.context":3,
+ "@odata.etag":4,
+ "Prefix1":{
+ "OmitMe":"bar",
+ "@odata.id":1,
+ "ExplicitSelectMe": 1
+ },
+ "Prefix2":[1, 2, 3],
+ "Prefix3":[
+ {
+ "OmitMe":"bar",
+ "@odata.id":1,
+ "ExplicitSelectMe": 1
+ }
+ ]
+}
+)"_json;
+ nlohmann::json expected = R"(
+{
+ "@odata.id":1,
+ "@odata.type":2,
+ "@odata.context":3,
+ "@odata.etag":4,
+ "Prefix1":{
+ "@odata.id":1,
+ "ExplicitSelectMe": 1
+ },
+ "Prefix3":[
+ {
+ "@odata.id":1,
+ "ExplicitSelectMe": 1
+ }
+ ]
+}
+)"_json;
+ auto ret = boost::urls::parse_relative_ref(
+ "/redfish/v1?$select=Prefix1/ExplicitSelectMe,Prefix3/ExplicitSelectMe");
+ ASSERT_TRUE(ret);
+ crow::Response res;
+ std::optional<Query> query = parseParameters(ret->params(), res);
+
+ ASSERT_NE(query, std::nullopt);
+ recursiveSelect(root, query->selectTrie.root);
+ EXPECT_EQ(root, expected);
+}
+
+TEST(PropogateErrorCode, 500IsWorst)
+{
+ constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400,
+ 401, 500, 501};
+ for (auto code : codes)
+ {
+ EXPECT_EQ(propogateErrorCode(500, code), 500);
+ EXPECT_EQ(propogateErrorCode(code, 500), 500);
+ }
+}
+
+TEST(PropogateErrorCode, 5xxAreWorseThanOthers)
+{
+ constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400,
+ 401, 501, 502};
+ for (auto code : codes)
+ {
+ EXPECT_EQ(propogateErrorCode(code, 505), 505);
+ EXPECT_EQ(propogateErrorCode(505, code), 505);
+ }
+ EXPECT_EQ(propogateErrorCode(502, 501), 502);
+ EXPECT_EQ(propogateErrorCode(501, 502), 502);
+ EXPECT_EQ(propogateErrorCode(503, 502), 503);
+}
+
+TEST(PropogateErrorCode, 401IsWorseThanOthers)
+{
+ constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400, 401};
+ for (auto code : codes)
+ {
+ EXPECT_EQ(propogateErrorCode(code, 401), 401);
+ EXPECT_EQ(propogateErrorCode(401, code), 401);
+ }
+}
+
+TEST(PropogateErrorCode, 4xxIsWorseThanOthers)
+{
+ constexpr std::array<unsigned, 7> codes = {100, 200, 300, 400, 402};
+ for (auto code : codes)
+ {
+ EXPECT_EQ(propogateErrorCode(code, 405), 405);
+ EXPECT_EQ(propogateErrorCode(405, code), 405);
+ }
+ EXPECT_EQ(propogateErrorCode(400, 402), 402);
+ EXPECT_EQ(propogateErrorCode(402, 403), 403);
+ EXPECT_EQ(propogateErrorCode(403, 402), 403);
+}
+
+TEST(PropogateError, IntermediateNoErrorMessageMakesNoChange)
+{
+ crow::Response intermediate;
+ intermediate.result(boost::beast::http::status::ok);
+
+ crow::Response finalRes;
+ finalRes.result(boost::beast::http::status::ok);
+ propogateError(finalRes, intermediate);
+ EXPECT_EQ(finalRes.result(), boost::beast::http::status::ok);
+ EXPECT_EQ(finalRes.jsonValue.find("error"), finalRes.jsonValue.end());
+}
+
+TEST(PropogateError, ErrorsArePropergatedWithErrorInRoot)
+{
+ nlohmann::json root = R"(
+{
+ "@odata.type": "#Message.v1_1_1.Message",
+ "Message": "The request failed due to an internal service error. The service is still operational.",
+ "MessageArgs": [],
+ "MessageId": "Base.1.13.0.InternalError",
+ "MessageSeverity": "Critical",
+ "Resolution": "Resubmit the request. If the problem persists, consider resetting the service."
+}
+)"_json;
+ crow::Response intermediate;
+ intermediate.result(boost::beast::http::status::internal_server_error);
+ intermediate.jsonValue = root;
+
+ crow::Response final;
+ final.result(boost::beast::http::status::ok);
+
+ propogateError(final, intermediate);
+
+ EXPECT_EQ(final.jsonValue["error"]["code"].get<std::string>(),
+ "Base.1.13.0.InternalError");
+ EXPECT_EQ(
+ final.jsonValue["error"]["message"].get<std::string>(),
+ "The request failed due to an internal service error. The service is still operational.");
+ EXPECT_EQ(intermediate.jsonValue, R"({})"_json);
+ EXPECT_EQ(final.result(),
+ boost::beast::http::status::internal_server_error);
+}
+
+TEST(PropogateError, ErrorsArePropergatedWithErrorCode)
+{
+ crow::Response intermediate;
+ intermediate.result(boost::beast::http::status::internal_server_error);
+
+ nlohmann::json error = R"(
+{
+ "error": {
+ "@Message.ExtendedInfo": [],
+ "code": "Base.1.13.0.InternalError",
+ "message": "The request failed due to an internal service error. The service is still operational."
+ }
+}
+)"_json;
+ nlohmann::json extendedInfo = R"(
+{
+ "@odata.type": "#Message.v1_1_1.Message",
+ "Message": "The request failed due to an internal service error. The service is still operational.",
+ "MessageArgs": [],
+ "MessageId": "Base.1.13.0.InternalError",
+ "MessageSeverity": "Critical",
+ "Resolution": "Resubmit the request. If the problem persists, consider resetting the service."
+}
+)"_json;
+
+ for (int i = 0; i < 10; ++i)
+ {
+ error["error"][messages::messageAnnotation].push_back(extendedInfo);
+ }
+ intermediate.jsonValue = error;
+ crow::Response final;
+ final.result(boost::beast::http::status::ok);
+
+ propogateError(final, intermediate);
+ EXPECT_EQ(final.jsonValue["error"][messages::messageAnnotation],
+ error["error"][messages::messageAnnotation]);
+ std::string errorCode = messages::messageVersionPrefix;
+ errorCode += "GeneralError";
+ std::string errorMessage =
+ "A general error has occurred. See Resolution for "
+ "information on how to resolve the error.";
+ EXPECT_EQ(final.jsonValue["error"]["code"].get<std::string>(), errorCode);
+ EXPECT_EQ(final.jsonValue["error"]["message"].get<std::string>(),
+ errorMessage);
+ EXPECT_EQ(intermediate.jsonValue, R"({})"_json);
+ EXPECT_EQ(final.result(),
+ boost::beast::http::status::internal_server_error);
+}
+
+TEST(QueryParams, ParseParametersOnly)
+{
+ auto ret = boost::urls::parse_relative_ref("/redfish/v1?only");
+ ASSERT_TRUE(ret);
+
+ crow::Response res;
+ std::optional<Query> query = parseParameters(ret->params(), res);
+ ASSERT_TRUE(query != std::nullopt);
+ EXPECT_TRUE(query->isOnly);
+}
+
+TEST(QueryParams, ParseParametersExpand)
+{
+ auto ret = boost::urls::parse_relative_ref("/redfish/v1?$expand=*");
+ ASSERT_TRUE(ret);
+
+ crow::Response res;
+
+ std::optional<Query> query = parseParameters(ret->params(), res);
+ if constexpr (bmcwebInsecureEnableQueryParams)
+ {
+ ASSERT_NE(query, std::nullopt);
+ EXPECT_TRUE(query->expandType ==
+ redfish::query_param::ExpandType::Both);
+ }
+ else
+ {
+ ASSERT_EQ(query, std::nullopt);
+ }
+}
+
+TEST(QueryParams, ParseParametersTop)
+{
+ auto ret = boost::urls::parse_relative_ref("/redfish/v1?$top=1");
+ ASSERT_TRUE(ret);
+
+ crow::Response res;
+
+ std::optional<Query> query = parseParameters(ret->params(), res);
+ ASSERT_TRUE(query != std::nullopt);
+ EXPECT_EQ(query->top, 1);
+}
+
+TEST(QueryParams, ParseParametersTopOutOfRangeNegative)
+{
+ auto ret = boost::urls::parse_relative_ref("/redfish/v1?$top=-1");
+ ASSERT_TRUE(ret);
+
+ crow::Response res;
+
+ std::optional<Query> query = parseParameters(ret->params(), res);
+ ASSERT_TRUE(query == std::nullopt);
+}
+
+TEST(QueryParams, ParseParametersTopOutOfRangePositive)
+{
+ auto ret = boost::urls::parse_relative_ref("/redfish/v1?$top=1001");
+ ASSERT_TRUE(ret);
+
+ crow::Response res;
+
+ std::optional<Query> query = parseParameters(ret->params(), res);
+ ASSERT_TRUE(query == std::nullopt);
+}
+
+TEST(QueryParams, ParseParametersSkip)
+{
+ auto ret = boost::urls::parse_relative_ref("/redfish/v1?$skip=1");
+ ASSERT_TRUE(ret);
+
+ crow::Response res;
+
+ std::optional<Query> query = parseParameters(ret->params(), res);
+ ASSERT_TRUE(query != std::nullopt);
+ EXPECT_EQ(query->skip, 1);
+}
+TEST(QueryParams, ParseParametersSkipOutOfRange)
+{
+ auto ret = boost::urls::parse_relative_ref(
+ "/redfish/v1?$skip=99999999999999999999");
+ ASSERT_TRUE(ret);
+
+ crow::Response res;
+
+ std::optional<Query> query = parseParameters(ret->params(), res);
+ ASSERT_EQ(query, std::nullopt);
+}
+
+TEST(QueryParams, ParseParametersUnexpectedGetsIgnored)
+{
+ auto ret = boost::urls::parse_relative_ref("/redfish/v1?unexpected_param");
+ ASSERT_TRUE(ret);
+
+ crow::Response res;
+
+ std::optional<Query> query = parseParameters(ret->params(), res);
+ ASSERT_TRUE(query != std::nullopt);
+}
+
+TEST(QueryParams, ParseParametersUnexpectedDollarGetsError)
+{
+ auto ret = boost::urls::parse_relative_ref("/redfish/v1?$unexpected_param");
+ ASSERT_TRUE(ret);
+
+ crow::Response res;
+
+ std::optional<Query> query = parseParameters(ret->params(), res);
+ ASSERT_TRUE(query == std::nullopt);
+ EXPECT_EQ(res.result(), boost::beast::http::status::not_implemented);
+}
+
+TEST(QueryParams, GetExpandType)
+{
+ Query query{};
+
+ EXPECT_FALSE(getExpandType("", query));
+ EXPECT_FALSE(getExpandType(".(", query));
+ EXPECT_FALSE(getExpandType(".()", query));
+ EXPECT_FALSE(getExpandType(".($levels=1", query));
+
+ EXPECT_TRUE(getExpandType("*", query));
+ EXPECT_EQ(query.expandType, ExpandType::Both);
+ EXPECT_TRUE(getExpandType(".", query));
+ EXPECT_EQ(query.expandType, ExpandType::NotLinks);
+ EXPECT_TRUE(getExpandType("~", query));
+ EXPECT_EQ(query.expandType, ExpandType::Links);
+
+ // Per redfish specification, level defaults to 1
+ EXPECT_TRUE(getExpandType(".", query));
+ EXPECT_EQ(query.expandLevel, 1);
+
+ EXPECT_TRUE(getExpandType(".($levels=42)", query));
+ EXPECT_EQ(query.expandLevel, 42);
+
+ // Overflow
+ EXPECT_FALSE(getExpandType(".($levels=256)", query));
+
+ // Negative
+ EXPECT_FALSE(getExpandType(".($levels=-1)", query));
+
+ // No number
+ EXPECT_FALSE(getExpandType(".($levels=a)", query));
+}
+
+TEST(QueryParams, FindNavigationReferencesNonLink)
+{
+ using nlohmann::json;
+
+ json singleTreeNode = R"({"Foo" : {"@odata.id": "/foobar"}})"_json;
+
+ // Parsing as the root should net one entry
+ EXPECT_THAT(findNavigationReferences(ExpandType::Both, singleTreeNode),
+ UnorderedElementsAre(
+ ExpandNode{json::json_pointer("/Foo"), "/foobar"}));
+
+ // Parsing in Non-hyperlinks mode should net one entry
+ EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, singleTreeNode),
+ UnorderedElementsAre(
+ ExpandNode{json::json_pointer("/Foo"), "/foobar"}));
+
+ // Searching for not types should return empty set
+ EXPECT_TRUE(
+ findNavigationReferences(ExpandType::None, singleTreeNode).empty());
+
+ // Searching for hyperlinks only should return empty set
+ EXPECT_TRUE(
+ findNavigationReferences(ExpandType::Links, singleTreeNode).empty());
+
+ json multiTreeNodes =
+ R"({"Links": {"@odata.id": "/links"}, "Foo" : {"@odata.id": "/foobar"}})"_json;
+ // Should still find Foo
+ EXPECT_THAT(findNavigationReferences(ExpandType::NotLinks, multiTreeNodes),
+ UnorderedElementsAre(
+ ExpandNode{json::json_pointer("/Foo"), "/foobar"}));
+}
+
+TEST(QueryParams, FindNavigationReferencesLink)
+{
+ using nlohmann::json;
+
+ json singleLinkNode =
+ R"({"Links" : {"Sessions": {"@odata.id": "/foobar"}}})"_json;
+
+ // Parsing as the root should net one entry
+ EXPECT_THAT(findNavigationReferences(ExpandType::Both, singleLinkNode),
+ UnorderedElementsAre(ExpandNode{
+ json::json_pointer("/Links/Sessions"), "/foobar"}));
+ // Parsing in hyperlinks mode should net one entry
+ EXPECT_THAT(findNavigationReferences(ExpandType::Links, singleLinkNode),
+ UnorderedElementsAre(ExpandNode{
+ json::json_pointer("/Links/Sessions"), "/foobar"}));
+
+ // Searching for not types should return empty set
+ EXPECT_TRUE(
+ findNavigationReferences(ExpandType::None, singleLinkNode).empty());
+
+ // Searching for non-hyperlinks only should return empty set
+ EXPECT_TRUE(
+ findNavigationReferences(ExpandType::NotLinks, singleLinkNode).empty());
+}
+
+} // namespace
+} // namespace redfish::query_param
diff --git a/test/redfish-core/include/utils/stl_utils_test.cpp b/test/redfish-core/include/utils/stl_utils_test.cpp
new file mode 100644
index 0000000000..f1febd088d
--- /dev/null
+++ b/test/redfish-core/include/utils/stl_utils_test.cpp
@@ -0,0 +1,34 @@
+#include "utils/stl_utils.hpp"
+
+#include <string>
+
+#include <gmock/gmock.h> // IWYU pragma: keep
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+
+namespace redfish::stl_utils
+{
+namespace
+{
+using ::testing::ElementsAre;
+
+TEST(FirstDuplicate, ReturnsIteratorToFirstDuplicate)
+{
+ std::vector<std::string> strVec = {"s1", "s4", "s1", "s2", "", "s3", "s3"};
+ auto iter = firstDuplicate(strVec.begin(), strVec.end());
+ ASSERT_NE(iter, strVec.end());
+ EXPECT_EQ(*iter, "s3");
+}
+
+TEST(RemoveDuplicates, AllDuplicatesAreRempvedInplace)
+{
+ std::vector<std::string> strVec = {"s1", "s4", "s1", "s2", "", "s3", "s3"};
+ removeDuplicate(strVec);
+
+ EXPECT_THAT(strVec, ElementsAre("s1", "s4", "s2", "", "s3"));
+}
+} // namespace
+} // namespace redfish::stl_utils
diff --git a/test/redfish-core/include/utils/time_utils_test.cpp b/test/redfish-core/include/utils/time_utils_test.cpp
new file mode 100644
index 0000000000..035d8ce95b
--- /dev/null
+++ b/test/redfish-core/include/utils/time_utils_test.cpp
@@ -0,0 +1,143 @@
+#include "utils/time_utils.hpp"
+
+#include <gmock/gmock.h> // IWYU pragma: keep
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+// IWYU pragma: no_include <gmock/gmock-matchers.h>
+// IWYU pragma: no_include <gtest/gtest-matchers.h>
+
+namespace redfish::time_utils
+{
+namespace
+{
+
+TEST(FromDurationTest, PositiveTests)
+{
+ EXPECT_EQ(fromDurationString("PT12S"), std::chrono::milliseconds(12000));
+ EXPECT_EQ(fromDurationString("PT0.204S"), std::chrono::milliseconds(204));
+ EXPECT_EQ(fromDurationString("PT0.2S"), std::chrono::milliseconds(200));
+ EXPECT_EQ(fromDurationString("PT50M"), std::chrono::milliseconds(3000000));
+ EXPECT_EQ(fromDurationString("PT23H"), std::chrono::milliseconds(82800000));
+ EXPECT_EQ(fromDurationString("P51D"),
+ std::chrono::milliseconds(4406400000));
+ EXPECT_EQ(fromDurationString("PT2H40M10.1S"),
+ std::chrono::milliseconds(9610100));
+ EXPECT_EQ(fromDurationString("P20DT2H40M10.1S"),
+ std::chrono::milliseconds(1737610100));
+ EXPECT_EQ(fromDurationString(""), std::chrono::milliseconds(0));
+}
+
+TEST(FromDurationTest, NegativeTests)
+{
+ EXPECT_EQ(fromDurationString("PTS"), std::nullopt);
+ EXPECT_EQ(fromDurationString("P1T"), std::nullopt);
+ EXPECT_EQ(fromDurationString("PT100M1000S100"), std::nullopt);
+ EXPECT_EQ(fromDurationString("PDTHMS"), std::nullopt);
+ EXPECT_EQ(fromDurationString("P9999999999999999999999999DT"), std::nullopt);
+ EXPECT_EQ(fromDurationString("PD222T222H222M222.222S"), std::nullopt);
+ EXPECT_EQ(fromDurationString("PT99999H9999999999999999999999M99999999999S"),
+ std::nullopt);
+ EXPECT_EQ(fromDurationString("PT-9H"), std::nullopt);
+}
+TEST(ToDurationTest, PositiveTests)
+{
+ EXPECT_EQ(toDurationString(std::chrono::milliseconds(12000)), "PT12.000S");
+ EXPECT_EQ(toDurationString(std::chrono::milliseconds(204)), "PT0.204S");
+ EXPECT_EQ(toDurationString(std::chrono::milliseconds(200)), "PT0.200S");
+ EXPECT_EQ(toDurationString(std::chrono::milliseconds(3000000)), "PT50M");
+ EXPECT_EQ(toDurationString(std::chrono::milliseconds(82800000)), "PT23H");
+ EXPECT_EQ(toDurationString(std::chrono::milliseconds(4406400000)), "P51DT");
+ EXPECT_EQ(toDurationString(std::chrono::milliseconds(9610100)),
+ "PT2H40M10.100S");
+ EXPECT_EQ(toDurationString(std::chrono::milliseconds(1737610100)),
+ "P20DT2H40M10.100S");
+}
+
+TEST(ToDurationTest, NegativeTests)
+{
+ EXPECT_EQ(toDurationString(std::chrono::milliseconds(-250)), "");
+}
+
+TEST(ToDurationStringFromUintTest, PositiveTests)
+{
+ uint64_t maxAcceptedTimeMs =
+ static_cast<uint64_t>(std::chrono::milliseconds::max().count());
+
+ EXPECT_NE(toDurationStringFromUint(maxAcceptedTimeMs), std::nullopt);
+ EXPECT_EQ(toDurationStringFromUint(0), "PT");
+ EXPECT_EQ(toDurationStringFromUint(250), "PT0.250S");
+ EXPECT_EQ(toDurationStringFromUint(5000), "PT5.000S");
+}
+
+TEST(ToDurationStringFromUintTest, NegativeTests)
+{
+ uint64_t minNotAcceptedTimeMs =
+ static_cast<uint64_t>(std::chrono::milliseconds::max().count()) + 1;
+
+ EXPECT_EQ(toDurationStringFromUint(minNotAcceptedTimeMs), std::nullopt);
+ EXPECT_EQ(toDurationStringFromUint(static_cast<uint64_t>(-1)),
+ std::nullopt);
+}
+
+TEST(GetDateTimeStdtime, ConversionTests)
+{
+ // some time before the epoch
+ EXPECT_EQ(getDateTimeStdtime(std::time_t{-1234567}),
+ "1970-01-01T00:00:00+00:00");
+
+ // epoch
+ EXPECT_EQ(getDateTimeStdtime(std::time_t{0}), "1970-01-01T00:00:00+00:00");
+
+ // Limits
+ EXPECT_EQ(getDateTimeStdtime(std::numeric_limits<std::time_t>::max()),
+ "9999-12-31T23:59:59+00:00");
+ EXPECT_EQ(getDateTimeStdtime(std::numeric_limits<std::time_t>::min()),
+ "1970-01-01T00:00:00+00:00");
+}
+
+TEST(GetDateTimeUint, ConversionTests)
+{
+ EXPECT_EQ(getDateTimeUint(uint64_t{1638312095}),
+ "2021-11-30T22:41:35+00:00");
+ // some time in the future, beyond 2038
+ EXPECT_EQ(getDateTimeUint(uint64_t{41638312095}),
+ "3289-06-18T21:48:15+00:00");
+ // the maximum time we support
+ EXPECT_EQ(getDateTimeUint(uint64_t{253402300799}),
+ "9999-12-31T23:59:59+00:00");
+
+ // returns the maximum Redfish date
+ EXPECT_EQ(getDateTimeUint(std::numeric_limits<uint64_t>::max()),
+ "9999-12-31T23:59:59+00:00");
+
+ EXPECT_EQ(getDateTimeUint(std::numeric_limits<uint64_t>::min()),
+ "1970-01-01T00:00:00+00:00");
+}
+
+TEST(GetDateTimeUintMs, ConverstionTests)
+{
+ EXPECT_EQ(getDateTimeUintMs(uint64_t{1638312095123}),
+ "2021-11-30T22:41:35.123+00:00");
+ // returns the maximum Redfish date
+ EXPECT_EQ(getDateTimeUintMs(std::numeric_limits<uint64_t>::max()),
+ "9999-12-31T23:59:59.999+00:00");
+ EXPECT_EQ(getDateTimeUintMs(std::numeric_limits<uint64_t>::min()),
+ "1970-01-01T00:00:00.000+00:00");
+}
+
+TEST(Utility, GetDateTimeUintUs)
+{
+ EXPECT_EQ(getDateTimeUintUs(uint64_t{1638312095123456}),
+ "2021-11-30T22:41:35.123456+00:00");
+ // returns the maximum Redfish date
+ EXPECT_EQ(getDateTimeUintUs(std::numeric_limits<uint64_t>::max()),
+ "9999-12-31T23:59:59.999999+00:00");
+ EXPECT_EQ(getDateTimeUintUs(std::numeric_limits<uint64_t>::min()),
+ "1970-01-01T00:00:00.000000+00:00");
+}
+
+} // namespace
+} // namespace redfish::time_utils
diff --git a/test/redfish-core/lib/chassis_test.cpp b/test/redfish-core/lib/chassis_test.cpp
new file mode 100644
index 0000000000..a04385984e
--- /dev/null
+++ b/test/redfish-core/lib/chassis_test.cpp
@@ -0,0 +1,60 @@
+#include "app.hpp"
+#include "async_resp.hpp"
+#include "chassis.hpp"
+#include "http_request.hpp"
+#include "http_response.hpp"
+
+#include <boost/beast/core/string_type.hpp>
+#include <boost/beast/http/message.hpp>
+#include <nlohmann/json.hpp>
+
+#include <system_error>
+
+#include <gtest/gtest.h>
+
+namespace redfish
+{
+namespace
+{
+
+void assertChassisResetActionInfoGet(const std::string& chassisId,
+ crow::Response& res)
+{
+ EXPECT_EQ(res.jsonValue["@odata.type"], "#ActionInfo.v1_1_2.ActionInfo");
+ EXPECT_EQ(res.jsonValue["@odata.id"],
+ "/redfish/v1/Chassis/" + chassisId + "/ResetActionInfo");
+ EXPECT_EQ(res.jsonValue["Name"], "Reset Action Info");
+
+ EXPECT_EQ(res.jsonValue["Id"], "ResetActionInfo");
+
+ nlohmann::json::array_t parameters;
+ nlohmann::json::object_t parameter;
+ parameter["Name"] = "ResetType";
+ parameter["Required"] = true;
+ parameter["DataType"] = "String";
+ nlohmann::json::array_t allowed;
+ allowed.push_back("PowerCycle");
+ parameter["AllowableValues"] = std::move(allowed);
+ parameters.push_back(std::move(parameter));
+
+ EXPECT_EQ(res.jsonValue["Parameters"], parameters);
+}
+
+TEST(HandleChassisResetActionInfoGet, StaticAttributesAreExpected)
+{
+
+ auto response = std::make_shared<bmcweb::AsyncResp>();
+ std::error_code err;
+ crow::Request request{{boost::beast::http::verb::get, "/whatever", 11},
+ err};
+
+ std::string fakeChassis = "fakeChassis";
+ response->res.setCompleteRequestHandler(
+ std::bind_front(assertChassisResetActionInfoGet, fakeChassis));
+
+ crow::App app;
+ handleChassisResetActionInfoGet(app, request, response, fakeChassis);
+}
+
+} // namespace
+} // namespace redfish \ No newline at end of file
diff --git a/test/redfish-core/lib/log_services_dump_test.cpp b/test/redfish-core/lib/log_services_dump_test.cpp
new file mode 100644
index 0000000000..192b79dc8d
--- /dev/null
+++ b/test/redfish-core/lib/log_services_dump_test.cpp
@@ -0,0 +1,117 @@
+#include "app.hpp"
+#include "event_service_manager.hpp"
+#include "include/async_resp.hpp"
+#include "redfish-core/lib/health.hpp"
+#include "redfish-core/lib/log_services.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace redfish
+{
+namespace
+{
+
+void assertLogServicesDumpServiceGet(crow::Response& res)
+{
+ nlohmann::json& json = res.jsonValue;
+ EXPECT_EQ(json["@odata.type"], "#LogService.v1_2_0.LogService");
+ EXPECT_EQ(json["Name"], "Dump LogService");
+}
+
+void assertLogServicesBMCDumpServiceGet(crow::Response& res)
+{
+ assertLogServicesDumpServiceGet(res);
+
+ nlohmann::json& json = res.jsonValue;
+ EXPECT_EQ(json["@odata.id"], "/redfish/v1/Managers/bmc/LogServices/Dump");
+ EXPECT_EQ(
+ json["Actions"]["#LogService.ClearLog"]["target"],
+ "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.ClearLog");
+ EXPECT_EQ(
+ json["Actions"]["#LogService.CollectDiagnosticData"]["target"],
+ "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData");
+ EXPECT_EQ(json["Description"], "BMC Dump LogService");
+ EXPECT_EQ(json["Entries"]["@odata.id"],
+ "/redfish/v1/Managers/bmc/LogServices/Dump/Entries");
+ EXPECT_EQ(json["Id"], "Dump");
+ EXPECT_EQ(json["OverWritePolicy"], "WrapsWhenFull");
+}
+
+void assertLogServicesFaultLogDumpServiceGet(crow::Response& res)
+{
+ assertLogServicesDumpServiceGet(res);
+
+ nlohmann::json& json = res.jsonValue;
+ EXPECT_EQ(json["@odata.id"],
+ "/redfish/v1/Managers/bmc/LogServices/FaultLog");
+ EXPECT_EQ(
+ json["Actions"]["#LogService.ClearLog"]["target"],
+ "/redfish/v1/Managers/bmc/LogServices/FaultLog/Actions/LogService.ClearLog");
+ EXPECT_EQ(json["Actions"]["#LogService.CollectDiagnosticData"]["target"],
+ nlohmann::detail::value_t::null);
+ EXPECT_EQ(json["Description"], "FaultLog Dump LogService");
+ EXPECT_EQ(json["Entries"]["@odata.id"],
+ "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries");
+ EXPECT_EQ(json["Id"], "FaultLog");
+ EXPECT_EQ(json["OverWritePolicy"], "Unknown");
+}
+
+void assertLogServicesSystemDumpServiceGet(crow::Response& res)
+{
+ assertLogServicesDumpServiceGet(res);
+
+ nlohmann::json& json = res.jsonValue;
+ EXPECT_EQ(json["@odata.id"], "/redfish/v1/Systems/system/LogServices/Dump");
+ EXPECT_EQ(
+ json["Actions"]["#LogService.ClearLog"]["target"],
+ "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.ClearLog");
+ EXPECT_EQ(
+ json["Actions"]["#LogService.CollectDiagnosticData"]["target"],
+ "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.CollectDiagnosticData");
+ EXPECT_EQ(json["Description"], "System Dump LogService");
+ EXPECT_EQ(json["Entries"]["@odata.id"],
+ "/redfish/v1/Systems/system/LogServices/Dump/Entries");
+ EXPECT_EQ(json["Id"], "Dump");
+ EXPECT_EQ(json["OverWritePolicy"], "WrapsWhenFull");
+}
+
+TEST(LogServicesDumpServiceTest,
+ LogServicesBMCDumpServiceStaticAttributesAreExpected)
+{
+ auto shareAsyncResp = std::make_shared<bmcweb::AsyncResp>();
+ shareAsyncResp->res.setCompleteRequestHandler(
+ assertLogServicesBMCDumpServiceGet);
+ getDumpServiceInfo(shareAsyncResp, "BMC");
+}
+
+TEST(LogServicesDumpServiceTest,
+ LogServicesFaultLogDumpServiceStaticAttributesAreExpected)
+{
+ auto shareAsyncResp = std::make_shared<bmcweb::AsyncResp>();
+ shareAsyncResp->res.setCompleteRequestHandler(
+ assertLogServicesFaultLogDumpServiceGet);
+ getDumpServiceInfo(shareAsyncResp, "FaultLog");
+}
+
+TEST(LogServicesDumpServiceTest,
+ LogServicesSystemDumpServiceStaticAttributesAreExpected)
+{
+ auto shareAsyncResp = std::make_shared<bmcweb::AsyncResp>();
+ shareAsyncResp->res.setCompleteRequestHandler(
+ assertLogServicesSystemDumpServiceGet);
+ getDumpServiceInfo(shareAsyncResp, "System");
+}
+
+TEST(LogServicesDumpServiceTest, LogServicesInvalidDumpServiceGetReturnsError)
+{
+ auto shareAsyncResp = std::make_shared<bmcweb::AsyncResp>();
+ getDumpServiceInfo(shareAsyncResp, "Invalid");
+ EXPECT_EQ(shareAsyncResp->res.result(),
+ boost::beast::http::status::internal_server_error);
+}
+
+} // namespace
+} // namespace redfish
diff --git a/test/redfish-core/lib/service_root_test.cpp b/test/redfish-core/lib/service_root_test.cpp
new file mode 100644
index 0000000000..661f157859
--- /dev/null
+++ b/test/redfish-core/lib/service_root_test.cpp
@@ -0,0 +1,122 @@
+#include "bmcweb_config.h"
+
+#include "http_response.hpp"
+#include "include/async_resp.hpp"
+#include "nlohmann/json.hpp"
+#include "service_root.hpp"
+
+#include <memory>
+#include <vector>
+
+#include <gmock/gmock.h> // IWYU pragma: keep
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+// IWYU pragma: no_include <gtest/gtest-message.h>
+// IWYU pragma: no_include <gtest/gtest-test-part.h>
+// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
+// IWYU pragma: no_include <gmock/gmock-matchers.h>
+// IWYU pragma: no_include <gtest/gtest-matchers.h>
+
+namespace redfish
+{
+namespace
+{
+
+void assertServiceRootGet(crow::Response& res)
+{
+ nlohmann::json& json = res.jsonValue;
+ EXPECT_EQ(json["@odata.id"], "/redfish/v1");
+ EXPECT_EQ(json["@odata.type"], "#ServiceRoot.v1_11_0.ServiceRoot");
+
+ EXPECT_EQ(json["AccountService"]["@odata.id"],
+ "/redfish/v1/AccountService");
+ EXPECT_EQ(json["AccountService"].size(), 1);
+
+ EXPECT_EQ(json["CertificateService"]["@odata.id"],
+ "/redfish/v1/CertificateService");
+ EXPECT_EQ(json["CertificateService"].size(), 1);
+
+ EXPECT_EQ(json["Chassis"]["@odata.id"], "/redfish/v1/Chassis");
+ EXPECT_EQ(json["Chassis"].size(), 1);
+
+ EXPECT_EQ(json["EventService"]["@odata.id"], "/redfish/v1/EventService");
+ EXPECT_EQ(json["EventService"].size(), 1);
+
+ EXPECT_EQ(json["Id"], "RootService");
+ EXPECT_EQ(json["Links"]["Sessions"]["@odata.id"],
+ "/redfish/v1/SessionService/Sessions");
+ EXPECT_EQ(json["Links"].size(), 1);
+ EXPECT_EQ(json["Links"]["Sessions"].size(), 1);
+
+ EXPECT_EQ(json["Managers"]["@odata.id"], "/redfish/v1/Managers");
+ EXPECT_EQ(json["Managers"].size(), 1);
+
+ EXPECT_EQ(json["Name"], "Root Service");
+ EXPECT_EQ(json["RedfishVersion"], "1.9.0");
+
+ EXPECT_EQ(json["Registries"]["@odata.id"], "/redfish/v1/Registries");
+ EXPECT_EQ(json["Registries"].size(), 1);
+
+ EXPECT_EQ(json["SessionService"]["@odata.id"],
+ "/redfish/v1/SessionService");
+ EXPECT_EQ(json["SessionService"].size(), 1);
+
+ EXPECT_EQ(json["Systems"]["@odata.id"], "/redfish/v1/Systems");
+ EXPECT_EQ(json["Systems"].size(), 1);
+
+ EXPECT_EQ(json["Tasks"]["@odata.id"], "/redfish/v1/TaskService");
+ EXPECT_EQ(json["Tasks"].size(), 1);
+
+ EXPECT_EQ(json["TelemetryService"]["@odata.id"],
+ "/redfish/v1/TelemetryService");
+ EXPECT_EQ(json["TelemetryService"].size(), 1);
+
+ EXPECT_THAT(
+ json["UUID"],
+ testing::MatchesRegex("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-"
+ "9a-fA-F]{4}-[0-9a-fA-F]{12}"));
+
+ EXPECT_EQ(json["UpdateService"]["@odata.id"], "/redfish/v1/UpdateService");
+
+ EXPECT_EQ(json["ProtocolFeaturesSupported"].size(), 6);
+ EXPECT_FALSE(json["ProtocolFeaturesSupported"]["ExcerptQuery"]);
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["ExpandAll"],
+ bmcwebInsecureEnableQueryParams);
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["Levels"],
+ bmcwebInsecureEnableQueryParams);
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["Links"],
+ bmcwebInsecureEnableQueryParams);
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["NoLinks"],
+ bmcwebInsecureEnableQueryParams);
+ if (bmcwebInsecureEnableQueryParams)
+ {
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"].size(), 5);
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["MaxLevels"],
+ 6);
+ }
+ else
+ {
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"].size(), 4);
+ }
+ EXPECT_FALSE(json["ProtocolFeaturesSupported"]["FilterQuery"]);
+ EXPECT_TRUE(json["ProtocolFeaturesSupported"]["OnlyMemberQuery"]);
+ EXPECT_TRUE(json["ProtocolFeaturesSupported"]["SelectQuery"]);
+ EXPECT_FALSE(
+ json["ProtocolFeaturesSupported"]["DeepOperations"]["DeepPOST"]);
+ EXPECT_FALSE(
+ json["ProtocolFeaturesSupported"]["DeepOperations"]["DeepPATCH"]);
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["DeepOperations"].size(), 2);
+ EXPECT_EQ(json.size(), 21);
+}
+
+TEST(HandleServiceRootGet, ServiceRootStaticAttributesAreExpected)
+{
+ auto shareAsyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ shareAsyncResp->res.setCompleteRequestHandler(assertServiceRootGet);
+
+ redfish::handleServiceRootGetImpl(shareAsyncResp);
+}
+
+} // namespace
+} // namespace redfish
diff --git a/test/redfish-core/lib/thermal_subsystem_test.cpp b/test/redfish-core/lib/thermal_subsystem_test.cpp
new file mode 100644
index 0000000000..c6880239da
--- /dev/null
+++ b/test/redfish-core/lib/thermal_subsystem_test.cpp
@@ -0,0 +1,42 @@
+#include "include/async_resp.hpp"
+#include "thermal_subsystem.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <optional>
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace redfish
+{
+namespace
+{
+
+constexpr const char* chassisId = "ChassisId";
+constexpr const char* validChassisPath = "ChassisPath";
+
+void assertThemalCollectionGet(crow::Response& res)
+{
+ nlohmann::json& json = res.jsonValue;
+ EXPECT_EQ(json["@odata.type"], "#ThermalSubsystem.v1_0_0.ThermalSubsystem");
+ EXPECT_EQ(json["Name"], "Thermal Subsystem");
+ EXPECT_EQ(json["Id"], "ThermalSubsystem");
+ EXPECT_EQ(json["@odata.id"],
+ "/redfish/v1/Chassis/ChassisId/ThermalSubsystem");
+ EXPECT_EQ(json["Status"]["State"], "Enabled");
+ EXPECT_EQ(json["Status"]["Health"], "OK");
+}
+
+TEST(ThermalSubsystemCollectionTest,
+ ThermalSubsystemCollectionStaticAttributesAreExpected)
+{
+ auto shareAsyncResp = std::make_shared<bmcweb::AsyncResp>();
+ shareAsyncResp->res.setCompleteRequestHandler(assertThemalCollectionGet);
+ doThermalSubsystemCollection(
+ shareAsyncResp, chassisId,
+ std::make_optional<std::string>(validChassisPath));
+}
+
+} // namespace
+} // namespace redfish