binarystore: Initial implementation

Dummy BinaryStore class implementation where most functions just return.
Implement getBaseBlobId/getBlobIds/canHandleBlobId and add unit tests
using real objects.

Signed-off-by: Kun Yi <kunyi@google.com>
Change-Id: Iaf8c59f3c4b1bab9de186333074a9cd0160a5764
diff --git a/Makefile.am b/Makefile.am
index 676eceb..796e385 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3,7 +3,8 @@
 libbinarystoredir = ${libdir}/ipmid-providers
 libbinarystore_LTLIBRARIES = libbinarystore.la
 libbinarystore_la_SOURCES = main.cpp \
-			    handler.cpp
+			    handler.cpp \
+			    binarystore.cpp
 libbinarystore_la_LDFLAGS = $(PHOSPHOR_LOGGING_LIBS) \
 			    -version-info 0:0:0 -shared
 libbinarystore_la_CXXFLAGS = $(PHOSPHOR_LOGGING_CFLAGS) \
diff --git a/binarystore.cpp b/binarystore.cpp
new file mode 100644
index 0000000..10aa449
--- /dev/null
+++ b/binarystore.cpp
@@ -0,0 +1,69 @@
+#include "binarystore.hpp"
+
+namespace binstore
+{
+
+std::unique_ptr<BinaryStoreInterface>
+    BinaryStore::createFromConfig(const std::string& baseBlobId,
+                                  const std::string& sysfilePath,
+                                  uint32_t offset, uint32_t maxSize)
+{
+    // TODO: implement sysFile parsing
+    return std::make_unique<BinaryStore>(baseBlobId, 0, offset, maxSize);
+}
+
+std::string BinaryStore::getBaseBlobId() const
+{
+    return baseBlobId_;
+}
+
+std::vector<std::string> BinaryStore::getBlobIds() const
+{
+    std::vector<std::string> result;
+    result.push_back(baseBlobId_);
+
+    for (const auto& blob : blob_.blobs)
+    {
+        result.push_back(blob.id);
+    }
+
+    return result;
+}
+
+bool BinaryStore::openOrCreateBlob(const std::string& blobId, uint16_t flags)
+{
+    return false;
+}
+
+bool BinaryStore::deleteBlob(const std::string& blobId)
+{
+    return false;
+}
+
+std::vector<uint8_t> BinaryStore::read(uint32_t offset, uint32_t requestedSize)
+{
+    std::vector<std::uint8_t> result;
+    return result;
+}
+
+bool BinaryStore::write(uint32_t offset, const std::vector<uint8_t>& data)
+{
+    return false;
+}
+
+bool BinaryStore::commit()
+{
+    return false;
+}
+
+bool BinaryStore::close()
+{
+    return false;
+}
+
+bool BinaryStore::stat()
+{
+    return false;
+}
+
+} // namespace binstore
diff --git a/binarystore.hpp b/binarystore.hpp
index 11ca241..a54eaf5 100644
--- a/binarystore.hpp
+++ b/binarystore.hpp
@@ -1,6 +1,9 @@
 #pragma once
 
+#include <unistd.h>
+
 #include <cstdint>
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -15,25 +18,139 @@
 
 /**
  * @class BinaryStoreInterface is an abstraction for a storage location.
- *        Each instance would be uniquely identified by a baseId string.
+ *     Each instance would be uniquely identified by a baseId string.
  */
 class BinaryStoreInterface
 {
   public:
     virtual ~BinaryStoreInterface() = default;
 
+    /**
+     * @returns baseId string of the storage.
+     */
     virtual std::string getBaseBlobId() const = 0;
+
+    /**
+     * @returns List of all open blob IDs, plus the base.
+     */
     virtual std::vector<std::string> getBlobIds() const = 0;
-    virtual bool canHandleBlob(const std::string& blobId) const = 0;
+
+    /**
+     * Opens a blob given its name. If there is no one, create one.
+     * @param blobId: The blob id to operate on.
+     * @param flags: Either read flag or r/w flag has to be specified.
+     * @returns True if open/create successfully.
+     */
     virtual bool openOrCreateBlob(const std::string& blobId,
                                   uint16_t flags) = 0;
+
+    /**
+     * Deletes a blob given its name. If there is no one,
+     * @param blobId: The blob id to operate on.
+     * @returns True if deleted.
+     */
     virtual bool deleteBlob(const std::string& blobId) = 0;
+
+    /**
+     * Reads data from the currently opened blob.
+     * @param offset: offset into the blob to read
+     * @param requestedSize: how many bytes to read
+     * @returns Bytes able to read. Returns empty if nothing can be read or
+     *          if there is no open blob.
+     */
     virtual std::vector<uint8_t> read(uint32_t offset,
                                       uint32_t requestedSize) = 0;
+
+    /**
+     * Writes data to the currently openend blob.
+     * @param offset: offset into the blob to write
+     * @param data: bytes to write
+     * @returns True if able to write the entire data successfully
+     */
     virtual bool write(uint32_t offset, const std::vector<uint8_t>& data) = 0;
+
+    /**
+     * TODO
+     */
     virtual bool commit() = 0;
+
+    /**
+     * TODO
+     */
     virtual bool close() = 0;
+
+    /**
+     * TODO
+     */
     virtual bool stat() = 0;
 };
 
+// TODO: move to protobuf definition
+struct BinaryBlobSingle
+{
+    std::string id;
+    std::vector<uint8_t> data;
+};
+
+struct BinaryBlob
+{
+    std::string baseBlobId;
+    std::vector<BinaryBlobSingle> blobs;
+};
+
+/**
+ * @class BinaryStore instantiates a concrete implementation of
+ *     BinaryStoreInterface. The dependency on file is injected through its
+ *     constructor.
+ */
+class BinaryStore : public BinaryStoreInterface
+{
+  public:
+    BinaryStore(const std::string& baseBlobId, int fd, uint32_t offset,
+                uint32_t maxSize) :
+        baseBlobId_(baseBlobId),
+        fd_(fd), offset_(offset), maxSize_(maxSize)
+    {
+    }
+
+    BinaryStore() = delete;
+    ~BinaryStore() = default;
+    BinaryStore(const BinaryStore&) = delete;
+    BinaryStore& operator=(const BinaryStore&) = delete;
+    BinaryStore(BinaryStore&&) = default;
+    BinaryStore& operator=(BinaryStore&&) = default;
+
+    std::string getBaseBlobId() const override;
+    std::vector<std::string> getBlobIds() const override;
+    bool openOrCreateBlob(const std::string& blobId, uint16_t flags) override;
+    bool deleteBlob(const std::string& blobId) override;
+    std::vector<uint8_t> read(uint32_t offset, uint32_t requestedSize) override;
+    bool write(uint32_t offset, const std::vector<uint8_t>& data) override;
+    bool commit() override;
+    bool close() override;
+    bool stat() override;
+
+    /**
+     * Helper factory method to create a BinaryStore instance
+     * @param baseBlobId: base id for the created instance
+     * @param sysfilePath: path to the storage location
+     * @param offset: offset into the file for serializing the final blob
+     * @param maxSize: max size in bytes that this BinaryStore can expand to.
+     *     Writing data more than allowed size will return failure.
+     * @returns unique_ptr to constructed BinaryStore. Caller should take
+     *     ownership of the instance.
+     */
+    static std::unique_ptr<BinaryStoreInterface>
+        createFromConfig(const std::string& baseBlobId,
+                         const std::string& sysfilePath, uint32_t offset,
+                         uint32_t maxSize);
+
+  private:
+    std::string baseBlobId_;
+    int fd_;
+    uint32_t offset_;
+    uint32_t maxSize_;
+    BinaryBlob blob_;
+};
+
 } // namespace binstore
diff --git a/binarystore_mock.hpp b/binarystore_mock.hpp
index 7792e74..eae08fe 100644
--- a/binarystore_mock.hpp
+++ b/binarystore_mock.hpp
@@ -4,14 +4,25 @@
 
 #include <gmock/gmock.h>
 
+using ::testing::Invoke;
+
 namespace binstore
 {
 
 class MockBinaryStore : public BinaryStoreInterface
 {
   public:
+    MockBinaryStore(const std::string& baseBlobId, int fd, uint32_t offset,
+                    uint32_t maxSize) :
+        real_store_(baseBlobId, fd, offset, maxSize)
+    {
+        // Implemented calls in BinaryStore will be directed to the real object.
+        ON_CALL(*this, getBaseBlobId)
+            .WillByDefault(Invoke(&real_store_, &BinaryStore::getBaseBlobId));
+        ON_CALL(*this, getBlobIds)
+            .WillByDefault(Invoke(&real_store_, &BinaryStore::getBlobIds));
+    }
     MOCK_CONST_METHOD0(getBaseBlobId, std::string());
-    MOCK_CONST_METHOD1(canHandleBlob, bool(const std::string&));
     MOCK_CONST_METHOD0(getBlobIds, std::vector<std::string>());
     MOCK_METHOD2(openOrCreateBlob, bool(const std::string&, uint16_t));
     MOCK_METHOD1(deleteBlob, bool(const std::string&));
@@ -20,6 +31,9 @@
     MOCK_METHOD0(commit, bool());
     MOCK_METHOD0(close, bool());
     MOCK_METHOD0(stat, bool());
+
+  private:
+    BinaryStore real_store_;
 };
 
 } // namespace binstore
diff --git a/handler.cpp b/handler.cpp
index ae21ab1..80790ed 100644
--- a/handler.cpp
+++ b/handler.cpp
@@ -8,8 +8,14 @@
 namespace internal
 {
 
-/* Strip the basename till the last '/' */
-std::string getBaseFromId(const std::string& blobId)
+/**
+ * @brief: Get baseId from a blob id string
+ * @param blobId: Input blob id which is expected to only contain alphanumerical
+ *                characters and '/'.
+ * @returns: the baseId containing the blobId, stripping all contents from the
+ *           last '/'. If no '/' is present, an empty string is returned.
+ */
+static std::string getBaseFromId(const std::string& blobId)
 {
     return blobId.substr(0, blobId.find_last_of('/') + 1);
 }
@@ -25,9 +31,16 @@
 
 bool BinaryStoreBlobHandler::canHandleBlob(const std::string& path)
 {
+    auto base = internal::getBaseFromId(path);
+    if (base.empty() || base == path)
+    {
+        /* Operations on baseId itself or an empty base is not allowed */
+        return false;
+    }
+
     return std::any_of(stores_.begin(), stores_.end(),
                        [&](const auto& baseStorePair) {
-                           return baseStorePair.second->canHandleBlob(path);
+                           return base == baseStorePair.second->getBaseBlobId();
                        });
 }
 
diff --git a/test/Makefile.am b/test/Makefile.am
index 5ce51f2..59325f2 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -17,15 +17,15 @@
 
 handler_unittest_SOURCES = handler_unittest.cpp
 handler_unittest_LDADD = $(PHOSPHOR_LOGGING_LIBS) \
-	$(top_builddir)/handler.o
+	$(top_builddir)/handler.o $(top_builddir)/binarystore.o
 handler_unittest_CXXFLAGS = $(PHOSPHOR_LOGGING_CFLAGS)
 
 handler_open_unittest_SOURCES = handler_open_unittest.cpp
 handler_open_unittest_LDADD = $(PHOSPHOR_LOGGING_LIBS) \
-	$(top_builddir)/handler.o
+	$(top_builddir)/handler.o $(top_builddir)/binarystore.o
 handler_open_unittest_CXXFLAGS = $(PHOSPHOR_LOGGING_CFLAGS)
 
 handler_readwrite_unittest_SOURCES = handler_readwrite_unittest.cpp
 handler_readwrite_unittest_LDADD = $(PHOSPHOR_LOGGING_LIBS) \
-	$(top_builddir)/handler.o
+	$(top_builddir)/handler.o  $(top_builddir)/binarystore.o
 handler_readwrite_unittest_CXXFLAGS = $(PHOSPHOR_LOGGING_CFLAGS)
diff --git a/test/handler_open_unittest.cpp b/test/handler_open_unittest.cpp
index 83053ad..c3d10aa 100644
--- a/test/handler_open_unittest.cpp
+++ b/test/handler_open_unittest.cpp
@@ -10,44 +10,47 @@
 namespace blobs
 {
 
+class BinaryStoreBlobHandlerOpenTest : public BinaryStoreBlobHandlerTest
+{
+  protected:
+    static inline std::string openTestBaseId = "/test/"s;
+    static inline std::string openTestBlobId = "/test/blob0"s;
+    static inline std::string openTestInvalidBlobId = "/invalid/blob0"s;
+    static inline uint16_t openTestROFlags = OpenFlags::read;
+    static inline uint16_t openTestRWFlags = OpenFlags::read | OpenFlags::write;
+    static inline uint16_t openTestSessionId = 0;
+};
+
 TEST_F(BinaryStoreBlobHandlerOpenTest, FailWhenCannotHandleId)
 {
-    uint16_t flags = OpenFlags::read, sessionId = 0;
-    EXPECT_FALSE(handler.open(sessionId, flags, "/invalid/blob"s));
+    EXPECT_FALSE(handler.open(openTestSessionId, openTestROFlags,
+                              openTestInvalidBlobId));
 }
 
 TEST_F(BinaryStoreBlobHandlerOpenTest, FailWhenStoreOpenReturnsFailure)
 {
-    auto testBaseId = "/test/"s;
-    auto testBlobId = "/test/blob0"s;
-    uint16_t flags = OpenFlags::read, sessionId = 0;
-    auto bstore = std::make_unique<MockBinaryStore>();
+    auto store = defaultMockStore(openTestBaseId);
 
-    EXPECT_CALL(*bstore, getBaseBlobId()).WillRepeatedly(Return(testBaseId));
-    EXPECT_CALL(*bstore, canHandleBlob(StartsWith(testBaseId)))
-        .WillRepeatedly(Return(true));
-    EXPECT_CALL(*bstore, openOrCreateBlob(_, flags)).WillOnce(Return(false));
+    EXPECT_CALL(*store, openOrCreateBlob(_, openTestROFlags))
+        .WillOnce(Return(false));
 
-    handler.addNewBinaryStore(std::move(bstore));
+    handler.addNewBinaryStore(std::move(store));
 
-    EXPECT_FALSE(handler.open(sessionId, flags, testBlobId));
+    EXPECT_FALSE(
+        handler.open(openTestSessionId, openTestROFlags, openTestBlobId));
 }
 
 TEST_F(BinaryStoreBlobHandlerOpenTest, SucceedWhenStoreOpenReturnsTrue)
 {
-    auto testBaseId = "/test/"s;
-    auto testBlobId = "/test/blob0"s;
-    uint16_t flags = OpenFlags::read, sessionId = 0;
-    auto bstore = std::make_unique<MockBinaryStore>();
+    auto store = defaultMockStore(openTestBaseId);
 
-    EXPECT_CALL(*bstore, getBaseBlobId()).WillRepeatedly(Return(testBaseId));
-    EXPECT_CALL(*bstore, canHandleBlob(StartsWith(testBaseId)))
-        .WillRepeatedly(Return(true));
-    EXPECT_CALL(*bstore, openOrCreateBlob(_, flags)).WillOnce(Return(true));
+    EXPECT_CALL(*store, openOrCreateBlob(_, openTestROFlags))
+        .WillOnce(Return(true));
 
-    handler.addNewBinaryStore(std::move(bstore));
+    handler.addNewBinaryStore(std::move(store));
 
-    EXPECT_TRUE(handler.open(sessionId, flags, testBlobId));
+    EXPECT_TRUE(
+        handler.open(openTestSessionId, openTestROFlags, openTestBlobId));
 }
 
 TEST_F(BinaryStoreBlobHandlerOpenTest, CloseFailForInvalidSession)
@@ -58,60 +61,49 @@
 
 TEST_F(BinaryStoreBlobHandlerOpenTest, CloseFailWhenStoreCloseFails)
 {
-    auto testBaseId = "/test/"s;
-    auto testBlobId = "/test/blob0"s;
-    uint16_t flags = OpenFlags::read, sessionId = 0;
-    auto bstore = std::make_unique<MockBinaryStore>();
+    auto store = defaultMockStore(openTestBaseId);
 
-    EXPECT_CALL(*bstore, getBaseBlobId()).WillRepeatedly(Return(testBaseId));
-    EXPECT_CALL(*bstore, canHandleBlob(StartsWith(testBaseId)))
-        .WillRepeatedly(Return(true));
-    EXPECT_CALL(*bstore, openOrCreateBlob(_, flags)).WillOnce(Return(true));
-    EXPECT_CALL(*bstore, close()).WillOnce(Return(false));
+    EXPECT_CALL(*store, openOrCreateBlob(_, openTestROFlags))
+        .WillOnce(Return(true));
+    EXPECT_CALL(*store, close()).WillOnce(Return(false));
 
-    handler.addNewBinaryStore(std::move(bstore));
+    handler.addNewBinaryStore(std::move(store));
 
-    EXPECT_TRUE(handler.open(sessionId, flags, testBlobId));
-    EXPECT_FALSE(handler.close(sessionId));
+    EXPECT_TRUE(
+        handler.open(openTestSessionId, openTestROFlags, openTestBlobId));
+    EXPECT_FALSE(handler.close(openTestSessionId));
 }
 
 TEST_F(BinaryStoreBlobHandlerOpenTest, CloseSucceedWhenStoreCloseSucceeds)
 {
-    auto testBaseId = "/test/"s;
-    auto testBlobId = "/test/blob0"s;
-    uint16_t flags = OpenFlags::read, sessionId = 0;
-    auto bstore = std::make_unique<MockBinaryStore>();
+    auto store = defaultMockStore(openTestBaseId);
 
-    EXPECT_CALL(*bstore, getBaseBlobId()).WillRepeatedly(Return(testBaseId));
-    EXPECT_CALL(*bstore, canHandleBlob(StartsWith(testBaseId)))
-        .WillRepeatedly(Return(true));
-    EXPECT_CALL(*bstore, openOrCreateBlob(_, flags)).WillOnce(Return(true));
-    EXPECT_CALL(*bstore, close()).WillOnce(Return(true));
+    EXPECT_CALL(*store, openOrCreateBlob(_, openTestROFlags))
+        .WillOnce(Return(true));
+    EXPECT_CALL(*store, close()).WillOnce(Return(true));
 
-    handler.addNewBinaryStore(std::move(bstore));
+    handler.addNewBinaryStore(std::move(store));
 
-    EXPECT_TRUE(handler.open(sessionId, flags, testBlobId));
-    EXPECT_TRUE(handler.close(sessionId));
+    EXPECT_TRUE(
+        handler.open(openTestSessionId, openTestROFlags, openTestBlobId));
+    EXPECT_TRUE(handler.close(openTestSessionId));
 }
 
 TEST_F(BinaryStoreBlobHandlerOpenTest, ClosedSessionCannotBeReclosed)
 {
-    auto testBaseId = "/test/"s;
-    auto testBlobId = "/test/blob0"s;
-    uint16_t flags = OpenFlags::read, sessionId = 0;
-    auto bstore = std::make_unique<MockBinaryStore>();
+    auto store = defaultMockStore(openTestBaseId);
 
-    EXPECT_CALL(*bstore, getBaseBlobId()).WillRepeatedly(Return(testBaseId));
-    EXPECT_CALL(*bstore, canHandleBlob(StartsWith(testBaseId)))
-        .WillRepeatedly(Return(true));
-    EXPECT_CALL(*bstore, openOrCreateBlob(_, flags)).WillOnce(Return(true));
-    EXPECT_CALL(*bstore, close()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*store, openOrCreateBlob(_, openTestROFlags))
+        .WillOnce(Return(true));
+    EXPECT_CALL(*store, close()).WillRepeatedly(Return(true));
 
-    handler.addNewBinaryStore(std::move(bstore));
+    handler.addNewBinaryStore(std::move(store));
 
-    EXPECT_TRUE(handler.open(sessionId, flags, testBlobId));
-    EXPECT_TRUE(handler.close(sessionId));
-    EXPECT_FALSE(handler.close(sessionId));
+    EXPECT_TRUE(
+        handler.open(openTestSessionId, openTestROFlags, openTestBlobId));
+    EXPECT_TRUE(handler.close(openTestSessionId));
+    EXPECT_FALSE(handler.close(openTestSessionId));
+    EXPECT_FALSE(handler.close(openTestSessionId));
 }
 
 } // namespace blobs
diff --git a/test/handler_readwrite_unittest.cpp b/test/handler_readwrite_unittest.cpp
index b335596..17e3cef 100644
--- a/test/handler_readwrite_unittest.cpp
+++ b/test/handler_readwrite_unittest.cpp
@@ -1,5 +1,10 @@
 #include "handler_unittest.hpp"
 
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
 using ::testing::_;
 using ::testing::Return;
 using ::testing::StartsWith;
@@ -23,28 +28,21 @@
 
 TEST_F(BinaryStoreBlobHandlerReadWriteTest, ReadWriteReturnsWhatStoreReturns)
 {
-    auto testBaseId = "/test/"s;
-    auto testBlobId = "/test/blob0"s;
     uint16_t flags = OpenFlags::read;
     const std::vector<uint8_t> emptyData;
-    auto bstore = std::make_unique<MockBinaryStore>();
+    auto store = defaultMockStore(rwTestBaseId);
 
-    EXPECT_CALL(*bstore, getBaseBlobId()).WillRepeatedly(Return(testBaseId));
-    EXPECT_CALL(*bstore, canHandleBlob(StartsWith(testBaseId)))
-        .WillRepeatedly(Return(true));
-    EXPECT_CALL(*bstore, openOrCreateBlob(_, flags)).WillOnce(Return(true));
-    EXPECT_CALL(*bstore, read(rwTestOffset, _))
+    EXPECT_CALL(*store, openOrCreateBlob(_, flags)).WillOnce(Return(true));
+    EXPECT_CALL(*store, read(rwTestOffset, _))
         .WillOnce(Return(emptyData))
         .WillOnce(Return(rwTestData));
 
-    EXPECT_CALL(*bstore, write(rwTestOffset, emptyData))
-        .WillOnce(Return(false));
-    EXPECT_CALL(*bstore, write(rwTestOffset, rwTestData))
-        .WillOnce(Return(true));
+    EXPECT_CALL(*store, write(rwTestOffset, emptyData)).WillOnce(Return(false));
+    EXPECT_CALL(*store, write(rwTestOffset, rwTestData)).WillOnce(Return(true));
 
-    handler.addNewBinaryStore(std::move(bstore));
+    handler.addNewBinaryStore(std::move(store));
 
-    EXPECT_TRUE(handler.open(rwTestSessionId, flags, testBlobId));
+    EXPECT_TRUE(handler.open(rwTestSessionId, flags, rwTestBlobId));
     EXPECT_EQ(emptyData, handler.read(rwTestSessionId, rwTestOffset, 1));
     EXPECT_EQ(rwTestData, handler.read(rwTestSessionId, rwTestOffset, 1));
     EXPECT_FALSE(handler.write(rwTestSessionId, rwTestOffset, emptyData));
diff --git a/test/handler_unittest.cpp b/test/handler_unittest.cpp
index d51818e..ef42ea6 100644
--- a/test/handler_unittest.cpp
+++ b/test/handler_unittest.cpp
@@ -1,9 +1,19 @@
 #include "handler_unittest.hpp"
 
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
 using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::ElementsAreArray;
+using ::testing::IsEmpty;
 using ::testing::Return;
 using ::testing::StrEq;
 using ::testing::StrNe;
+using ::testing::UnorderedElementsAreArray;
+
 using namespace std::string_literals;
 using namespace binstore;
 
@@ -15,112 +25,116 @@
   protected:
     static inline std::string basicTestBaseId = "/test/"s;
     static inline std::string basicTestBlobId = "/test/blob0"s;
+
+    static const std::vector<std::string> basicTestBaseIdList;
     static inline std::string basicTestInvalidBlobId = "/invalid/blob0"s;
+
+    void addAllBaseIds()
+    {
+        for (size_t i = 0; i < basicTestBaseIdList.size(); ++i)
+        {
+            auto store = defaultMockStore(basicTestBaseIdList[i]);
+
+            EXPECT_CALL(*store, getBaseBlobId()).Times(AtLeast(1));
+
+            handler.addNewBinaryStore(std::move(store));
+        }
+    }
 };
 
+const std::vector<std::string>
+    BinaryStoreBlobHandlerBasicTest::basicTestBaseIdList = {
+        BinaryStoreBlobHandlerBasicTest::basicTestBaseId, "/another/"s,
+        "/null\0/"s};
+
 TEST_F(BinaryStoreBlobHandlerBasicTest, CanHandleBlobZeroStoreFail)
 {
     // Cannot handle since there is no store. Shouldn't crash.
     EXPECT_FALSE(handler.canHandleBlob(basicTestInvalidBlobId));
 }
 
-TEST_F(BinaryStoreBlobHandlerBasicTest, CanHandleBlobChecksNameInvalid)
+TEST_F(BinaryStoreBlobHandlerBasicTest, CanHandleBlobChecksName)
 {
-    auto bstore = std::make_unique<MockBinaryStore>();
+    auto store = defaultMockStore(basicTestBaseId);
 
-    handler.addNewBinaryStore(std::move(bstore));
+    EXPECT_CALL(*store, getBaseBlobId()).Times(AtLeast(1));
+
+    handler.addNewBinaryStore(std::move(store));
 
     // Verify canHandleBlob checks and returns false on an invalid name.
     EXPECT_FALSE(handler.canHandleBlob(basicTestInvalidBlobId));
-}
-
-TEST_F(BinaryStoreBlobHandlerBasicTest, CanHandleBlobCanOpenValidBlob)
-{
-    auto bstore = std::make_unique<MockBinaryStore>();
-
-    EXPECT_CALL(*bstore, getBaseBlobId())
-        .WillRepeatedly(Return(basicTestBaseId));
-    EXPECT_CALL(*bstore, canHandleBlob(StrNe(basicTestBlobId)))
-        .WillRepeatedly(Return(false));
-    EXPECT_CALL(*bstore, canHandleBlob(StrEq(basicTestBlobId)))
-        .WillRepeatedly(Return(true));
-    handler.addNewBinaryStore(std::move(bstore));
-
     // Verify canHandleBlob return true for a blob id that it can handle
-    EXPECT_FALSE(handler.canHandleBlob(basicTestInvalidBlobId));
     EXPECT_TRUE(handler.canHandleBlob(basicTestBlobId));
 }
 
-TEST_F(BinaryStoreBlobHandlerBasicTest, CanHandleBlobCanOpenValidBlobMultiple)
+TEST_F(BinaryStoreBlobHandlerBasicTest, GetBlobIdEqualsConcatenationsOfBaseIds)
 {
-    auto bstore = std::make_unique<MockBinaryStore>();
-    auto bstore1 = std::make_unique<MockBinaryStore>();
-    const std::string anotherBaseId = "/another/"s;
-    const std::string anotherBlobId = "/another/blob/id"s;
+    addAllBaseIds();
 
-    EXPECT_CALL(*bstore, getBaseBlobId())
-        .WillRepeatedly(Return(basicTestBaseId));
-    EXPECT_CALL(*bstore, canHandleBlob(StrNe(basicTestBlobId)))
-        .WillRepeatedly(Return(false));
-    EXPECT_CALL(*bstore, canHandleBlob(StrEq(basicTestBlobId)))
-        .WillRepeatedly(Return(true));
-    handler.addNewBinaryStore(std::move(bstore));
-
-    EXPECT_CALL(*bstore1, getBaseBlobId())
-        .WillRepeatedly(Return(anotherBaseId));
-    EXPECT_CALL(*bstore1, canHandleBlob(StrNe(anotherBlobId)))
-        .WillRepeatedly(Return(false));
-    EXPECT_CALL(*bstore1, canHandleBlob(StrEq(anotherBlobId)))
-        .WillRepeatedly(Return(true));
-    handler.addNewBinaryStore(std::move(bstore1));
-
-    // Verify canHandleBlob return true for a blob id that it can handle
-    EXPECT_FALSE(handler.canHandleBlob(basicTestInvalidBlobId));
-    EXPECT_TRUE(handler.canHandleBlob(basicTestBlobId));
-    EXPECT_TRUE(handler.canHandleBlob(anotherBlobId));
+    // When there is no other blob id, ids are just base ids (might be
+    // re-ordered).
+    EXPECT_THAT(handler.getBlobIds(),
+                UnorderedElementsAreArray(basicTestBaseIdList));
 }
 
-TEST_F(BinaryStoreBlobHandlerBasicTest, GetBlobIdEqualsConcatenationsOfIds)
+TEST_F(BinaryStoreBlobHandlerBasicTest, CanHandleBlobInvalidNames)
 {
-    std::string baseId0 = "/test/"s;
-    std::string baseId1 = "/test1/"s;
-    std::vector<std::string> idList0 = {"/test/"s, "/test/0"s};
-    std::vector<std::string> idList1 = {"/test1/"s, "/test1/2"s};
-    auto expectedIdList = idList0;
-    expectedIdList.insert(expectedIdList.end(), idList1.begin(), idList1.end());
+    addAllBaseIds();
 
-    auto bstore0 = std::make_unique<MockBinaryStore>();
-    EXPECT_CALL(*bstore0, getBaseBlobId()).WillOnce(Return(baseId0));
-    EXPECT_CALL(*bstore0, getBlobIds()).WillOnce(Return(idList0));
-    handler.addNewBinaryStore(std::move(bstore0));
+    const std::vector<std::string> invalidNames = {
+        basicTestInvalidBlobId,
+        "/"s,
+        "//"s,
+        "/test"s, // Cannot handle the base path
+        "/test/"s,
+        "/test/this/blob"s, // Cannot handle nested path
+    };
 
-    auto bstore1 = std::make_unique<MockBinaryStore>();
-    EXPECT_CALL(*bstore1, getBaseBlobId()).WillOnce(Return(baseId1));
-    EXPECT_CALL(*bstore1, getBlobIds()).WillOnce(Return(idList1));
-    handler.addNewBinaryStore(std::move(bstore1));
+    // Unary helper for algorithm
+    auto canHandle = [this](const std::string& blobId) {
+        return handler.canHandleBlob(blobId);
+    };
 
-    // Verify canHandleBlob return true for a blob id that it can handle
-    EXPECT_EQ(expectedIdList, handler.getBlobIds());
+    EXPECT_TRUE(
+        std::none_of(invalidNames.cbegin(), invalidNames.cend(), canHandle));
+}
+
+TEST_F(BinaryStoreBlobHandlerBasicTest, CanHandleBlobValidNames)
+{
+    addAllBaseIds();
+
+    const std::vector<std::string> validNames = {
+        basicTestBlobId,  "/test/test"s,          "/test/xyz.abc"s,
+        "/another/blob"s, "/null\0/\0\0zer\0\0"s,
+    };
+
+    // Unary helper for algorithm
+    auto canHandle = [this](const std::string& blobId) {
+        return handler.canHandleBlob(blobId);
+    };
+
+    // Verify canHandleBlob can handle a valid blob under the path.
+    EXPECT_TRUE(std::all_of(validNames.cbegin(), validNames.cend(), canHandle));
 }
 
 TEST_F(BinaryStoreBlobHandlerBasicTest, DeleteReturnsWhatStoreReturns)
 {
-    auto bstore = std::make_unique<MockBinaryStore>();
+    auto store = defaultMockStore(basicTestBaseId);
 
-    EXPECT_CALL(*bstore, getBaseBlobId())
-        .WillRepeatedly(Return(basicTestBaseId));
-    EXPECT_CALL(*bstore, canHandleBlob(StrNe(basicTestBlobId)))
-        .WillRepeatedly(Return(false));
-    EXPECT_CALL(*bstore, canHandleBlob(StrEq(basicTestBlobId)))
-        .WillRepeatedly(Return(true));
-    EXPECT_CALL(*bstore, deleteBlob(StrEq(basicTestBlobId)))
+    EXPECT_CALL(*store, getBaseBlobId()).Times(AtLeast(1));
+    EXPECT_CALL(*store, deleteBlob(StrEq(basicTestBlobId)))
         .WillOnce(Return(false))
         .WillOnce(Return(true));
-    handler.addNewBinaryStore(std::move(bstore));
+    handler.addNewBinaryStore(std::move(store));
+
+    // Unary helper for algorithm
+    auto canHandle = [this](const std::string& blobId) {
+        return handler.canHandleBlob(blobId);
+    };
 
     // Verify canHandleBlob return true for a blob id that it can handle
-    EXPECT_FALSE(handler.canHandleBlob(basicTestInvalidBlobId));
-    EXPECT_TRUE(handler.canHandleBlob(basicTestBlobId));
+    EXPECT_FALSE(canHandle(basicTestInvalidBlobId));
+    EXPECT_TRUE(canHandle(basicTestBlobId));
     EXPECT_FALSE(handler.deleteBlob(basicTestInvalidBlobId));
     EXPECT_FALSE(handler.deleteBlob(basicTestBlobId));
     EXPECT_TRUE(handler.deleteBlob(basicTestBlobId));
diff --git a/test/handler_unittest.hpp b/test/handler_unittest.hpp
index 7f5df42..c35c749 100644
--- a/test/handler_unittest.hpp
+++ b/test/handler_unittest.hpp
@@ -3,8 +3,15 @@
 #include "binarystore_mock.hpp"
 #include "handler.hpp"
 
+#include <memory>
+#include <string>
+
 #include <gtest/gtest.h>
 
+using ::testing::Contains;
+
+using namespace std::string_literals;
+
 namespace blobs
 {
 
@@ -13,10 +20,17 @@
   protected:
     BinaryStoreBlobHandlerTest() = default;
     BinaryStoreBlobHandler handler;
-};
 
-class BinaryStoreBlobHandlerOpenTest : public BinaryStoreBlobHandlerTest
-{
+    std::unique_ptr<binstore::MockBinaryStore>
+        defaultMockStore(const std::string& baseId)
+    {
+        return std::make_unique<binstore::MockBinaryStore>(baseId, 0, 0, 0);
+    }
+
+    void addDefaultStore(const std::string& baseId)
+    {
+        handler.addNewBinaryStore(defaultMockStore(baseId));
+    }
 };
 
 } // namespace blobs