Fix Task Monitor URI

Fixes #272

The TaskMonitor urls we create aren't correct per Redfish.  Per DSP0266
section 12.2, our TaskMonitor URIs should take the form

/redfish/v1/TaskService/TaskMonitors/<id>

Note that even though this appears to be a collection, it is not, and
does not "exist" in the Redfish schema, hence why it is called out
explicitly.

Tested:
Started dump collection task with
POST
```
/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData
```
GET /redfish/v1/Tasks/0

Returned TaskMonitor = /redfish/v1/Tasks/TaskMonitors/0

GET /redfish/v1/Tasks/TaskMonitors/0 returned 200

Change-Id: I9fb1d62090f7787d7649c077b748b51ac3202f8a
Signed-off-by: Ed Tanous <ed@tanous.net>
diff --git a/redfish-core/include/aggregation_utils.hpp b/redfish-core/include/aggregation_utils.hpp
index f0b259e..78ff3c2 100644
--- a/redfish-core/include/aggregation_utils.hpp
+++ b/redfish-core/include/aggregation_utils.hpp
@@ -17,7 +17,7 @@
 {
 // Note that each URI actually begins with "/redfish/v1"
 // They've been omitted to save space and reduce search time
-constexpr std::array<std::string_view, 49> topCollections{
+constexpr std::array<std::string_view, 50> topCollections{
     "/AggregationService/Aggregates",
     "/AggregationService/AggregationSources",
     "/AggregationService/ConnectionMethods",
@@ -52,6 +52,7 @@
     "/StorageServices",
     "/StorageSystems",
     "/Systems",
+    "/TaskService/TaskMonitors",
     "/TaskService/Tasks",
     "/TelemetryService/LogService/Entries",
     "/TelemetryService/MetricDefinitions",
diff --git a/redfish-core/lib/task.hpp b/redfish-core/lib/task.hpp
index 8d81a29..ec92aca 100644
--- a/redfish-core/lib/task.hpp
+++ b/redfish-core/lib/task.hpp
@@ -144,7 +144,8 @@
         {
             res.result(boost::beast::http::status::accepted);
             std::string strIdx = std::to_string(index);
-            std::string uri = "/redfish/v1/TaskService/Tasks/" + strIdx;
+            boost::urls::url uri =
+                boost::urls::format("/redfish/v1/TaskService/Tasks/{}", strIdx);
 
             res.jsonValue["@odata.id"] = uri;
             res.jsonValue["@odata.type"] = "#Task.v1_4_3.Task";
@@ -152,8 +153,11 @@
             res.jsonValue["TaskState"] = state;
             res.jsonValue["TaskStatus"] = status;
 
+            boost::urls::url taskMonitor = boost::urls::format(
+                "/redfish/v1/TaskService/TaskMonitors/{}", strIdx);
+
             res.addHeader(boost::beast::http::field::location,
-                          uri + "/Monitor");
+                          taskMonitor.buffer());
             res.addHeader(boost::beast::http::field::retry_after,
                           std::to_string(retryAfterSeconds));
         }
@@ -313,7 +317,7 @@
 
 inline void requestRoutesTaskMonitor(App& app)
 {
-    BMCWEB_ROUTE(app, "/redfish/v1/TaskService/Tasks/<str>/Monitor/")
+    BMCWEB_ROUTE(app, "/redfish/v1/TaskService/TaskMonitors/<str>/")
         .privileges(redfish::privileges::getTask)
         .methods(boost::beast::http::verb::get)(
             [&app](const crow::Request& req,
@@ -402,8 +406,8 @@
             boost::urls::format("/redfish/v1/TaskService/Tasks/{}", strParam);
         if (!ptr->gave204)
         {
-            asyncResp->res.jsonValue["TaskMonitor"] =
-                "/redfish/v1/TaskService/Tasks/" + strParam + "/Monitor";
+            asyncResp->res.jsonValue["TaskMonitor"] = boost::urls::format(
+                "/redfish/v1/TaskService/TaskMonitors/{}", strParam);
         }
 
         asyncResp->res.jsonValue["HidePayload"] = !ptr->payload;
diff --git a/scripts/generate_schema_collections.py b/scripts/generate_schema_collections.py
index cf1f2f3..039d32f 100755
--- a/scripts/generate_schema_collections.py
+++ b/scripts/generate_schema_collections.py
@@ -261,6 +261,12 @@
         "ServiceRoot", curr_path, top_collections, False, "ServiceRoot_v1.xml"
     )
 
+    # Task service is not called out by CSDL, and is technically not a
+    # collection, but functionally needs to be treated like a collection, per
+    # the Asynchronous operations section of DSP0266
+    # https://www.dmtf.org/sites/default/files/standards/documents/DSP0266_1.20.1.html#asynchronous-operations
+    top_collections.add("/redfish/v1/TaskService/TaskMonitors")
+
     print("Finished traversal!")
 
     TOTAL = len(top_collections)
diff --git a/test/redfish-core/include/redfish_aggregator_test.cpp b/test/redfish-core/include/redfish_aggregator_test.cpp
index 6986dcf..fe80964 100644
--- a/test/redfish-core/include/redfish_aggregator_test.cpp
+++ b/test/redfish-core/include/redfish_aggregator_test.cpp
@@ -61,6 +61,7 @@
                                     "PowerEquipment/FloorPDUs",
                                     "Systems",
                                     "TaskService/Tasks",
+                                    "TaskService/TaskMonitors",
                                     "TelemetryService/LogService/Entries",
                                     "UpdateService/SoftwareInventory"};
 
@@ -214,6 +215,22 @@
               "/redfish/v1/Chassis/5B42_TestChassis");
 }
 
+TEST(addPrefixes, FixHttpTaskMonitor)
+{
+    // Previously bmcweb hosted task monitors incorrectly.
+    // It has been corrected in the next test, but ensure that the "old"
+    // way still produces the correct result.
+    nlohmann::json taskResp = R"(
+    {
+      "TaskMonitor": "/redfish/v1/TaskService/Tasks/0/Monitor"
+    }
+    )"_json;
+
+    addPrefixes(taskResp, "5B247A");
+    EXPECT_EQ(taskResp["TaskMonitor"],
+              "/redfish/v1/TaskService/Tasks/5B247A_0/Monitor");
+}
+
 TEST(addPrefixes, FixHttpHeadersInResponseBody)
 {
     nlohmann::json taskResp = nlohmann::json::parse(R"(
@@ -230,7 +247,7 @@
         ]
       },
       "PercentComplete": 100,
-      "TaskMonitor": "/redfish/v1/TaskService/Tasks/0/Monitor",
+      "TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
       "TaskState": "Completed",
       "TaskStatus": "OK"
     }
@@ -240,7 +257,7 @@
     addPrefixes(taskResp, "5B247A");
     EXPECT_EQ(taskResp["@odata.id"], "/redfish/v1/TaskService/Tasks/5B247A_0");
     EXPECT_EQ(taskResp["TaskMonitor"],
-              "/redfish/v1/TaskService/Tasks/5B247A_0/Monitor");
+              "/redfish/v1/TaskService/TaskMonitors/5B247A_0");
     nlohmann::json& httpHeaders = taskResp["Payload"]["HttpHeaders"];
     EXPECT_EQ(
         httpHeaders[4],