Allow custom 404 handlers

Different HTTP protocols have different http responses for 404.  This
commit adds support for registering a route designed to host a handler
meant for when a response would otherwise return.  This allows
registering a custom 404 handler for Redfish, for which all routes will
now return a Redfish response.

This was in response to the 404 handler not working in all cases (in the
case of POST/PATCH/DELETE).  Allowing an explicit registration helps to
give the intended behavior in all cases.

Tested:
GET /redfish/v1/foo returns 404 Not found
PATCH /redfish/v1/foo returns 404 Not found

GET /redfish/v1 returns 200 OK, and content
PATCH /redfish/v1 returns 405 Method Not Allowed

With Redfish Aggregation:
GET /redfish/v1/foo gets forwarded to satellite BMC
PATCH /redfish/v1/foo does not get forwarded and returns 404
PATCH /redfish/v1/foo/5B247A_bar gets forwarded

Unit tests pass

Redfish-service-validator passes

Redfish-Protocol-Validator fails 7 tests (same as before)

Signed-off-by: Ed Tanous <edtanous@google.com>
Change-Id: I731a5b4e736a2480700d8f3e81f9c9c6cbe6efca
Signed-off-by: Carson Labrado <clabrado@google.com>
diff --git a/http/ut/router_test.cpp b/http/ut/router_test.cpp
index 4d1a744..b43b659 100644
--- a/http/ut/router_test.cpp
+++ b/http/ut/router_test.cpp
@@ -42,17 +42,53 @@
 
     // No route should return no methods.
     router.validate();
-    EXPECT_EQ(router.buildAllowHeader(req), "");
+    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.buildAllowHeader(req), "GET");
+    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.buildAllowHeader(req), "GET, PATCH");
+    EXPECT_EQ(router.findRoute(req).allowHeader, "GET, PATCH");
+    EXPECT_NE(router.findRoute(req).route.rule, nullptr);
+    EXPECT_NE(router.findRoute(patchReq).route.rule, nullptr);
+}
+
+TEST(Router, 404)
+{
+    bool notFoundCalled = false;
+    // Callback handler that does nothing
+    auto nullCallback =
+        [&notFoundCalled](const Request&,
+                          const std::shared_ptr<bmcweb::AsyncResp>&) {
+        notFoundCalled = true;
+    };
+
+    Router router;
+    std::error_code ec;
+
+    constexpr const std::string_view url = "/foo/bar";
+
+    Request req{{boost::beast::http::verb::get, url, 11}, ec};
+
+    router.newRuleTagged<getParameterTag(url)>("/foo/<path>")
+        .notFound()(nullCallback);
+    router.validate();
+    {
+        std::shared_ptr<bmcweb::AsyncResp> asyncResp =
+            std::make_shared<bmcweb::AsyncResp>();
+
+        router.handle(req, asyncResp);
+    }
+    EXPECT_TRUE(notFoundCalled);
 }
 } // namespace
-} // namespace crow
\ No newline at end of file
+} // namespace crow