version-handler: add version-handler, blob handler

Implement version-handler: a blob handler for retrieving version
information about a blob.

Problem: ipmi-flash(firmware-handler) provides a mechanism to transfer
firmware updates, verify them and perform updates but there is no
mechanism to interrogate the firmware for its version.

Solution: version-handler provides handlers for retrieving information
about firmware blobs. Adding "version" syntax to "/flash/blob" entries
in the json configuration file enables this feature.
The mechanism to retrieve version is identical to the mechanism used by
firmware-handler to perform preparation, verification and updates (kick
off systemd targets).

Signed-off-by: Jason Ling <jasonling@google.com>
Change-Id: I28868ca8dd76d63af668d2e46b9359401d45f0bc
diff --git a/bmc/version-handler/test/version_handler_unittest.cpp b/bmc/version-handler/test/version_handler_unittest.cpp
new file mode 100644
index 0000000..f27f033
--- /dev/null
+++ b/bmc/version-handler/test/version_handler_unittest.cpp
@@ -0,0 +1,267 @@
+#include "version_handler.hpp"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+using ::testing::IsEmpty;
+
+using json = nlohmann::json;
+
+TEST(VersionHandlerCreateTest, VerifyAllBlobHandlersPresent)
+{
+
+    // Need a triggerable mock
+    VersionInfoMap test;
+    test.try_emplace("blob0", "blob0",
+                     std::make_unique<VersionActionPackMock>(),
+                     std::make_unique<ImageHandlerMock>());
+    ASSERT_THAT(h, ::testing::SizeIs(1));
+    EXPECT_THAT(h[0].blobId, "/version/sink_seq");
+    EXPECT_FALSE(h[0].actions == nullptr);
+    EXPECT_FALSE(h[0].handler == nullptr);
+}
+
+TEST(VersionJsonTest, ValidConfigurationVersionBlobName)
+{
+    auto h = VersionHandlersBuilder().buildHandlerFromJson(j2);
+    ASSERT_THAT(h, ::testing::SizeIs(1));
+    EXPECT_THAT(h[0].blobId, "/version/sink_seq");
+    EXPECT_FALSE(h[0].actions == nullptr);
+    EXPECT_FALSE(h[0].handler == nullptr);
+}
+
+TEST(VersionJsonTest, MissingHandlerType)
+{
+    auto j2 = R"(
+        [{
+            "blob" : "/flash/image",
+            "version":{
+                "handler": {
+                   "path" : "/tmp/version_info"
+                 },
+                "actions": {
+                  "open" : {
+                  "type" : "systemd",
+                  "unit" : "absolute"}
+                 }
+            }
+         }]
+    )"_json;
+    EXPECT_THAT(VersionHandlersBuilder().buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(VersionJsonTest, BadBlobName)
+{
+    auto j2 = R"(
+        [{
+            "blob" : "/bad/image",
+            "version":{
+                "handler": {
+                   "type" : "file",
+                   "path" : "/tmp/version_info"
+                 },
+                "actions": {
+                  "open" : {
+                  "type" : "systemd",
+                  "unit" : "absolute"}
+                 }
+            }
+         }]
+    )"_json;
+    EXPECT_THAT(VersionHandlersBuilder().buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(VersionJsonTest, MissingActions)
+{
+    auto j2 = R"(
+        [{
+            "blob" : "/flash/image",
+            "version":{
+                "handler": {
+                   "type" : "file",
+                   "path" : "/tmp/version_info"
+                 }
+            }
+         }]
+    )"_json;
+    EXPECT_THAT(VersionHandlersBuilder().buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(VersionJsonTest, MissingOpenAction)
+{
+    auto j2 = R"(
+        [{
+            "blob" : "/flash/image",
+            "version":{
+                "handler": {
+                   "type" : "file",
+                   "path" : "/tmp/version_info"
+                 },
+                "actions": {}
+            }
+         }]
+    )"_json;
+    EXPECT_THAT(VersionHandlersBuilder().buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(VersionJsonTest, OneInvalidTwoValidSucceeds)
+{
+    auto j2 = R"(
+        [{
+            "blob" : "/flash/sink_seq0",
+            "version":{
+                "handler": {
+                   "type" : "file",
+                   "path" : "/tmp/version_info"
+                 },
+                "actions":{
+                    "open" :{
+                    "type" : "systemd",
+                    "unit" : "absolute"
+                    }
+                 }
+            }
+         },
+         {
+            "blob" : "/version/sink_seq1",
+            "version":{
+                "handler": {
+                   "type" : "file",
+                   "path" : "/tmp/version_info"
+                 },
+                "actions":{
+                    "open" :{
+                    "type" : "systemd",
+                    "unit" : "absolute"
+                    }
+                 }
+            }
+         },
+         {
+            "blob" : "/bad/sink_seq",
+            "version":{
+                "handler": {
+                   "type" : "file",
+                   "path" : "/tmp/version_info"
+                 },
+                "actions":{
+                    "open" :{
+                    "type" : "systemd",
+                    "unit" : "absolute"
+                    }
+                 }
+            }
+         }
+         ]
+    )"_json;
+    auto h = VersionHandlersBuilder().buildHandlerFromJson(j2);
+    ASSERT_THAT(h, ::testing::SizeIs(2));
+    EXPECT_THAT(h[0].blobId, "/version/sink_seq0");
+    EXPECT_THAT(h[1].blobId, "/version/sink_seq1");
+}
+
+TEST(VersionJsonTest, BlobNameIsTooShort)
+{
+    auto j2 = R"(
+        [{
+            "blob" : "/flash/",
+            "version":{
+                "handler": {
+                   "type" : "file",
+                   "path" : "/tmp/version_info"
+                 },
+                "actions":{
+                    "open" :{
+                    "type" : "systemd",
+                    "unit" : "absolute"
+                    }
+                 }
+            }
+         }]
+    )"_json;
+    EXPECT_THAT(VersionHandlersBuilder().buildHandlerFromJson(j2), IsEmpty());
+}
+
+TEST(VersionJsonTest, OpenSkipAction)
+{
+    auto j2 = R"(
+        [{
+            "blob" : "/flash/sink_seqs",
+            "version":{
+                "handler": {
+                   "type" : "file",
+                   "path" : "/tmp/version_info"
+                 },
+                "actions":{
+                    "open" :{
+                    "type" : "skip"
+                    }
+                 }
+            }
+         }]
+    )"_json;
+    auto h = VersionHandlersBuilder().buildHandlerFromJson(j2);
+    EXPECT_THAT(h, ::testing::SizeIs(1));
+    EXPECT_TRUE(h[0].blobId == "/version/sink_seqs");
+    ASSERT_FALSE(h[0].actions == nullptr);
+    EXPECT_FALSE(h[0].actions->onOpen == nullptr);
+}
+
+TEST(VersionJsonTest, OpenActionsWithDifferentModes)
+{
+    auto j2 = R"(
+        [{
+            "blob" : "/flash/blob1",
+            "version":{
+                "handler": {
+                   "type" : "file",
+                   "path" : "/tmp/version_info"
+                 },
+                "actions":{
+                    "open" :{
+                    "type" : "systemd",
+                    "unit" : "absolute",
+                    "mode" : "replace-nope"
+                    }
+                 }
+            }
+         },
+         {
+            "blob" : "/flash/blob2",
+            "version":{
+                "handler": {
+                   "type" : "file",
+                   "path" : "/tmp/version_info"
+                 },
+                "actions":{
+                    "open" :{
+                    "type" : "systemd",
+                    "unit" : "absolute",
+                    "mode" : "replace-fake"
+                    }
+                 }
+            }
+         }
+         ]
+    )"_json;
+    auto h = VersionHandlersBuilder().buildHandlerFromJson(j2);
+    ASSERT_THAT(h, ::testing::SizeIs(2));
+
+    EXPECT_FALSE(h[0].handler == nullptr);
+    EXPECT_FALSE(h[0].actions == nullptr);
+    EXPECT_THAT(h[0].blobId, "/version/blob1");
+    auto onOpen0 = reinterpret_cast<SystemdNoFile*>(h[0].actions->onOpen.get());
+    EXPECT_THAT(onOpen0->getMode(), "replace-nope");
+
+    EXPECT_FALSE(h[1].handler == nullptr);
+    EXPECT_FALSE(h[1].actions == nullptr);
+    EXPECT_THAT(h[1].blobId, "/version/blob2");
+    auto onOpen1 = reinterpret_cast<SystemdNoFile*>(h[1].actions->onOpen.get());
+    EXPECT_THAT(onOpen1->getMode(), "replace-fake");
+}
+} // namespace
+} // namespace ipmi_flash