diff options
author | Nan Zhou <nanzhoumails@gmail.com> | 2022-09-10 21:11:41 +0300 |
---|---|---|
committer | Nan Zhou <nanzhoumails@gmail.com> | 2022-09-22 02:44:37 +0300 |
commit | c33a039b56fc5789ae71289adaca1e572a48d318 (patch) | |
tree | 8b936ca291537cebebf6bf505e565bff9635f511 /test | |
parent | 6ab9ad5492b9203c7dd8fb3e8c59d37bbc455cde (diff) | |
download | bmcweb-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')
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 = + [¬FoundCalled](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 |