Rename 'tests/unit-tests' directory to 'tests/unit'

Change-Id: I78ea29938259fac288781bed12fb2399ac7eba26
diff --git a/tests/unit/mgmt/control-response.t.cpp b/tests/unit/mgmt/control-response.t.cpp
new file mode 100644
index 0000000..86aa780
--- /dev/null
+++ b/tests/unit/mgmt/control-response.t.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/control-response.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace mgmt {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(TestControlResponse)
+
+static const uint8_t WIRE[] = {
+  0x65, 0x17, // ControlResponse
+        0x66, 0x02, // StatusCode
+              0x01, 0x94,
+        0x67, 0x11, // StatusText
+              0x4e, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x6e, 0x6f, 0x74, 0x20,
+              0x66, 0x6f, 0x75, 0x6e, 0x64};
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  ControlResponse cr(404, "Nothing not found");
+  const Block& wire = cr.wireEncode();
+  BOOST_CHECK_EQUAL_COLLECTIONS(WIRE, WIRE + sizeof(WIRE),
+                                wire.begin(), wire.end());
+}
+
+BOOST_AUTO_TEST_CASE(Decode)
+{
+  ControlResponse cr(Block(WIRE, sizeof(WIRE)));
+  BOOST_CHECK_EQUAL(cr.getCode(), 404);
+  BOOST_CHECK_EQUAL(cr.getText(), "Nothing not found");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestControlResponse
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace mgmt
+} // namespace ndn
diff --git a/tests/unit/mgmt/dispatcher.t.cpp b/tests/unit/mgmt/dispatcher.t.cpp
new file mode 100644
index 0000000..c75af4d
--- /dev/null
+++ b/tests/unit/mgmt/dispatcher.t.cpp
@@ -0,0 +1,494 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/dispatcher.hpp"
+#include "mgmt/nfd/control-parameters.hpp"
+#include "util/dummy-client-face.hpp"
+
+#include "boost-test.hpp"
+#include "make-interest-data.hpp"
+#include "../identity-management-time-fixture.hpp"
+
+namespace ndn {
+namespace mgmt {
+namespace tests {
+
+using namespace ndn::tests;
+
+class DispatcherFixture : public IdentityManagementTimeFixture
+{
+public:
+  DispatcherFixture()
+    : face(io, m_keyChain, {true, true})
+    , dispatcher(face, m_keyChain, security::SigningInfo())
+    , storage(dispatcher.m_storage)
+  {
+  }
+
+public:
+  util::DummyClientFace face;
+  mgmt::Dispatcher dispatcher;
+  InMemoryStorageFifo& storage;
+};
+
+class VoidParameters : public mgmt::ControlParameters
+{
+public:
+  explicit
+  VoidParameters(const Block& wire)
+  {
+    wireDecode(wire);
+  }
+
+  Block
+  wireEncode() const final
+  {
+    return Block(128);
+  }
+
+  void
+  wireDecode(const Block& wire) final
+  {
+    if (wire.type() != 128)
+      BOOST_THROW_EXCEPTION(tlv::Error("Expecting TLV type 128"));
+  }
+};
+
+static Authorization
+makeTestAuthorization()
+{
+  return [] (const Name& prefix,
+             const Interest& interest,
+             const ControlParameters* params,
+             AcceptContinuation accept,
+             RejectContinuation reject) {
+    if (interest.getName()[-1] == name::Component("valid")) {
+      accept("");
+    }
+    else {
+      if (interest.getName()[-1] == name::Component("silent")) {
+        reject(RejectReply::SILENT);
+      }
+      else {
+        reject(RejectReply::STATUS403);
+      }
+    }
+  };
+}
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_FIXTURE_TEST_SUITE(TestDispatcher, DispatcherFixture)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  BOOST_CHECK_NO_THROW(dispatcher
+                         .addControlCommand<VoidParameters>("test/1", makeAcceptAllAuthorization(),
+                                                            bind([] { return true; }),
+                                                            bind([]{})));
+  BOOST_CHECK_NO_THROW(dispatcher
+                         .addControlCommand<VoidParameters>("test/2", makeAcceptAllAuthorization(),
+                                                            bind([] { return true; }),
+                                                            bind([]{})));
+
+  BOOST_CHECK_THROW(dispatcher
+                      .addControlCommand<VoidParameters>("test", makeAcceptAllAuthorization(),
+                                                         bind([] { return true; }),
+                                                         bind([]{})),
+                    std::out_of_range);
+
+  BOOST_CHECK_NO_THROW(dispatcher.addStatusDataset("status/1",
+                                                   makeAcceptAllAuthorization(), bind([]{})));
+  BOOST_CHECK_NO_THROW(dispatcher.addStatusDataset("status/2",
+                                                   makeAcceptAllAuthorization(), bind([]{})));
+  BOOST_CHECK_THROW(dispatcher.addStatusDataset("status",
+                                                makeAcceptAllAuthorization(), bind([]{})),
+                    std::out_of_range);
+
+  BOOST_CHECK_NO_THROW(dispatcher.addNotificationStream("stream/1"));
+  BOOST_CHECK_NO_THROW(dispatcher.addNotificationStream("stream/2"));
+  BOOST_CHECK_THROW(dispatcher.addNotificationStream("stream"), std::out_of_range);
+
+
+  BOOST_CHECK_NO_THROW(dispatcher.addTopPrefix("/root/1"));
+  BOOST_CHECK_NO_THROW(dispatcher.addTopPrefix("/root/2"));
+  BOOST_CHECK_THROW(dispatcher.addTopPrefix("/root"), std::out_of_range);
+
+  BOOST_CHECK_THROW(dispatcher
+                      .addControlCommand<VoidParameters>("test/3", makeAcceptAllAuthorization(),
+                                                         bind([] { return true; }),
+                                                         bind([]{})),
+                    std::domain_error);
+
+  BOOST_CHECK_THROW(dispatcher.addStatusDataset("status/3",
+                                                makeAcceptAllAuthorization(), bind([]{})),
+                    std::domain_error);
+
+  BOOST_CHECK_THROW(dispatcher.addNotificationStream("stream/3"), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(AddRemoveTopPrefix)
+{
+  std::map<std::string, size_t> nCallbackCalled;
+  dispatcher
+    .addControlCommand<VoidParameters>("test/1", makeAcceptAllAuthorization(),
+                                       bind([] { return true; }),
+                                       bind([&nCallbackCalled] { ++nCallbackCalled["test/1"]; }));
+
+  dispatcher
+    .addControlCommand<VoidParameters>("test/2", makeAcceptAllAuthorization(),
+                                       bind([] { return true; }),
+                                       bind([&nCallbackCalled] { ++nCallbackCalled["test/2"]; }));
+
+  face.receive(*makeInterest("/root/1/test/1/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 0);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/2"], 0);
+
+  dispatcher.addTopPrefix("/root/1");
+  advanceClocks(1_ms);
+
+  face.receive(*makeInterest("/root/1/test/1/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 1);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/2"], 0);
+
+  face.receive(*makeInterest("/root/1/test/2/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 1);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/2"], 1);
+
+  face.receive(*makeInterest("/root/2/test/1/%80%00"));
+  face.receive(*makeInterest("/root/2/test/2/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 1);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/2"], 1);
+
+  dispatcher.addTopPrefix("/root/2");
+  advanceClocks(1_ms);
+
+  face.receive(*makeInterest("/root/1/test/1/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 2);
+
+  face.receive(*makeInterest("/root/2/test/1/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 3);
+
+  dispatcher.removeTopPrefix("/root/1");
+  advanceClocks(1_ms);
+
+  face.receive(*makeInterest("/root/1/test/1/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 3);
+
+  face.receive(*makeInterest("/root/2/test/1/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 4);
+}
+
+BOOST_AUTO_TEST_CASE(ControlCommand)
+{
+  size_t nCallbackCalled = 0;
+  dispatcher
+    .addControlCommand<VoidParameters>("test",
+                                       makeTestAuthorization(),
+                                       bind([] { return true; }),
+                                       bind([&nCallbackCalled] { ++nCallbackCalled; }));
+
+  dispatcher.addTopPrefix("/root");
+  advanceClocks(1_ms);
+  face.sentData.clear();
+
+  face.receive(*makeInterest("/root/test/%80%00")); // returns 403
+  face.receive(*makeInterest("/root/test/%80%00/invalid")); // returns 403
+  face.receive(*makeInterest("/root/test/%80%00/silent")); // silently ignored
+  face.receive(*makeInterest("/root/test/.../invalid")); // silently ignored (wrong format)
+  face.receive(*makeInterest("/root/test/.../valid"));  // silently ignored (wrong format)
+  advanceClocks(1_ms, 20);
+  BOOST_CHECK_EQUAL(nCallbackCalled, 0);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 2);
+
+  BOOST_CHECK_EQUAL(face.sentData[0].getContentType(), tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(ControlResponse(face.sentData[0].getContent().blockFromValue()).getCode(), 403);
+  BOOST_CHECK_EQUAL(face.sentData[1].getContentType(), tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(ControlResponse(face.sentData[1].getContent().blockFromValue()).getCode(), 403);
+
+  face.receive(*makeInterest("/root/test/%80%00/valid"));
+  advanceClocks(1_ms, 10);
+  BOOST_CHECK_EQUAL(nCallbackCalled, 1);
+}
+
+class StatefulParameters : public mgmt::ControlParameters
+{
+public:
+  explicit
+  StatefulParameters(const Block& wire)
+  {
+    wireDecode(wire);
+  }
+
+  Block
+  wireEncode() const final
+  {
+    return Block();
+  }
+
+  void
+  wireDecode(const Block& wire) final
+  {
+    m_state = EXPECTED_STATE;
+  }
+
+  bool
+  check() const
+  {
+    return m_state == EXPECTED_STATE;
+  }
+
+private:
+  static constexpr int EXPECTED_STATE = 12602;
+  int m_state = 0;
+};
+
+BOOST_AUTO_TEST_CASE(ControlCommandAsyncAuthorization) // Bug 4059
+{
+  AcceptContinuation authorizationAccept;
+  auto authorization =
+    [&authorizationAccept] (const Name& prefix, const Interest& interest, const ControlParameters* params,
+        AcceptContinuation accept, RejectContinuation reject) {
+      authorizationAccept = accept;
+    };
+
+  auto validateParameters =
+    [] (const ControlParameters& params) {
+      return dynamic_cast<const StatefulParameters&>(params).check();
+    };
+
+  size_t nCallbackCalled = 0;
+  dispatcher
+    .addControlCommand<StatefulParameters>("test",
+                                           authorization,
+                                           validateParameters,
+                                           bind([&nCallbackCalled] { ++nCallbackCalled; }));
+
+  dispatcher.addTopPrefix("/root");
+  advanceClocks(1_ms);
+
+  face.receive(*makeInterest("/root/test/%80%00"));
+  BOOST_CHECK_EQUAL(nCallbackCalled, 0);
+  BOOST_REQUIRE(authorizationAccept != nullptr);
+
+  advanceClocks(1_ms);
+  authorizationAccept("");
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled, 1);
+}
+
+BOOST_AUTO_TEST_CASE(StatusDataset)
+{
+  const uint8_t smallBuf[] = {0x81, 0x01, 0x01};
+  const Block smallBlock(smallBuf, sizeof(smallBuf));
+  Block largeBlock;
+  {
+    EncodingBuffer encoder;
+    for (size_t i = 0; i < 2500; ++i) {
+      encoder.prependByte(1);
+    }
+    encoder.prependVarNumber(2500);
+    encoder.prependVarNumber(129);
+    largeBlock = encoder.block();
+  }
+
+  dispatcher.addStatusDataset("test/small",
+                              makeTestAuthorization(),
+                              [&smallBlock] (const Name& prefix, const Interest& interest,
+                                             StatusDatasetContext& context) {
+                                context.append(smallBlock);
+                                context.append(smallBlock);
+                                context.append(smallBlock);
+                                context.end();
+                              });
+
+  dispatcher.addStatusDataset("test/large",
+                              makeTestAuthorization(),
+                              [&largeBlock] (const Name& prefix, const Interest& interest,
+                                             StatusDatasetContext& context) {
+                                context.append(largeBlock);
+                                context.append(largeBlock);
+                                context.append(largeBlock);
+                                context.end();
+                              });
+
+  dispatcher.addStatusDataset("test/reject",
+                              makeTestAuthorization(),
+                              [] (const Name& prefix, const Interest& interest,
+                                  StatusDatasetContext& context) {
+                                context.reject();
+                              });
+
+  dispatcher.addTopPrefix("/root");
+  advanceClocks(1_ms);
+  face.sentData.clear();
+
+  face.receive(*makeInterest("/root/test/small/%80%00")); // returns 403
+  face.receive(*makeInterest("/root/test/small/%80%00/invalid")); // returns 403
+  face.receive(*makeInterest("/root/test/small/%80%00/silent")); // silently ignored
+  advanceClocks(1_ms, 20);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 2);
+
+  BOOST_CHECK_EQUAL(face.sentData[0].getContentType(), tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(ControlResponse(face.sentData[0].getContent().blockFromValue()).getCode(), 403);
+  BOOST_CHECK_EQUAL(face.sentData[1].getContentType(), tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(ControlResponse(face.sentData[1].getContent().blockFromValue()).getCode(), 403);
+
+  face.sentData.clear();
+
+  auto interestSmall = *makeInterest("/root/test/small/valid", true);
+  face.receive(interestSmall);
+  advanceClocks(1_ms, 10);
+
+  // one data packet is generated and sent to both places
+  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
+  BOOST_CHECK_EQUAL(storage.size(), 1);
+
+  auto fetchedData = storage.find(interestSmall);
+  BOOST_REQUIRE(fetchedData != nullptr);
+  BOOST_CHECK_EQUAL(face.sentData[0].wireEncode(), fetchedData->wireEncode());
+
+  face.receive(*makeInterest(Name("/root/test/small/valid").appendVersion(10))); // should be ignored
+  face.receive(*makeInterest(Name("/root/test/small/valid").appendSegment(20))); // should be ignored
+  advanceClocks(1_ms, 10);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
+  BOOST_CHECK_EQUAL(storage.size(), 1);
+
+  Block content = face.sentData[0].getContent();
+  BOOST_CHECK_NO_THROW(content.parse());
+
+  BOOST_REQUIRE_EQUAL(content.elements().size(), 3);
+  BOOST_CHECK_EQUAL(content.elements()[0], smallBlock);
+  BOOST_CHECK_EQUAL(content.elements()[1], smallBlock);
+  BOOST_CHECK_EQUAL(content.elements()[2], smallBlock);
+
+  storage.erase("/", true); // clear the storage
+  face.sentData.clear();
+  face.receive(*makeInterest("/root/test/large/valid"));
+  advanceClocks(1_ms, 10);
+
+  // two data packets are generated, the first one will be sent to both places
+  // while the second one will only be inserted into the in-memory storage
+  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
+  BOOST_CHECK_EQUAL(storage.size(), 2);
+
+  // segment0 should be sent through the face
+  const auto& component = face.sentData[0].getName().at(-1);
+  BOOST_CHECK(component.isSegment());
+  BOOST_CHECK_EQUAL(component.toSegment(), 0);
+
+  std::vector<Data> dataInStorage;
+  std::copy(storage.begin(), storage.end(), std::back_inserter(dataInStorage));
+
+  // the Data sent through the face should be the same as the first Data in the storage
+  BOOST_CHECK_EQUAL(face.sentData[0].getName(), dataInStorage[0].getName());
+  BOOST_CHECK_EQUAL(face.sentData[0].getContent(), dataInStorage[0].getContent());
+
+  content = [&dataInStorage] () -> Block {
+    EncodingBuffer encoder;
+    size_t valueLength = encoder.prependByteArray(dataInStorage[1].getContent().value(),
+                                                  dataInStorage[1].getContent().value_size());
+    valueLength += encoder.prependByteArray(dataInStorage[0].getContent().value(),
+                                            dataInStorage[0].getContent().value_size());
+    encoder.prependVarNumber(valueLength);
+    encoder.prependVarNumber(tlv::Content);
+    return encoder.block();
+  }();
+
+  BOOST_CHECK_NO_THROW(content.parse());
+  BOOST_REQUIRE_EQUAL(content.elements().size(), 3);
+  BOOST_CHECK_EQUAL(content.elements()[0], largeBlock);
+  BOOST_CHECK_EQUAL(content.elements()[1], largeBlock);
+  BOOST_CHECK_EQUAL(content.elements()[2], largeBlock);
+
+  storage.erase("/", true);// clear the storage
+  face.sentData.clear();
+  face.receive(*makeInterest("/root/test/reject/%80%00/valid")); // returns nack
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
+  BOOST_CHECK_EQUAL(face.sentData[0].getContentType(), tlv::ContentType_Nack);
+  BOOST_CHECK_EQUAL(ControlResponse(face.sentData[0].getContent().blockFromValue()).getCode(), 400);
+  BOOST_CHECK_EQUAL(storage.size(), 0); // the nack packet will not be inserted into the in-memory storage
+}
+
+BOOST_AUTO_TEST_CASE(NotificationStream)
+{
+  const uint8_t buf[] = {0x82, 0x01, 0x02};
+  const Block block(buf, sizeof(buf));
+  auto post = dispatcher.addNotificationStream("test");
+
+  post(block);
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 0);
+
+  dispatcher.addTopPrefix("/root");
+  advanceClocks(1_ms);
+  face.sentData.clear();
+
+  post(block);
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
+  BOOST_CHECK_EQUAL(storage.size(), 1);
+
+  post(block);
+  post(block);
+  post(block);
+  advanceClocks(1_ms, 10);
+
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 4);
+  BOOST_CHECK_EQUAL(face.sentData[0].getName(), "/root/test/%FE%00");
+  BOOST_CHECK_EQUAL(face.sentData[1].getName(), "/root/test/%FE%01");
+  BOOST_CHECK_EQUAL(face.sentData[2].getName(), "/root/test/%FE%02");
+  BOOST_CHECK_EQUAL(face.sentData[3].getName(), "/root/test/%FE%03");
+
+  BOOST_CHECK_EQUAL(face.sentData[0].getContent().blockFromValue(), block);
+  BOOST_CHECK_EQUAL(face.sentData[1].getContent().blockFromValue(), block);
+  BOOST_CHECK_EQUAL(face.sentData[2].getContent().blockFromValue(), block);
+  BOOST_CHECK_EQUAL(face.sentData[3].getContent().blockFromValue(), block);
+
+  // each version of notification will be sent to both places
+  std::vector<Data> dataInStorage;
+  std::copy(storage.begin(), storage.end(), std::back_inserter(dataInStorage));
+  BOOST_REQUIRE_EQUAL(dataInStorage.size(), 4);
+  BOOST_CHECK_EQUAL(dataInStorage[0].getName(), "/root/test/%FE%00");
+  BOOST_CHECK_EQUAL(dataInStorage[1].getName(), "/root/test/%FE%01");
+  BOOST_CHECK_EQUAL(dataInStorage[2].getName(), "/root/test/%FE%02");
+  BOOST_CHECK_EQUAL(dataInStorage[3].getName(), "/root/test/%FE%03");
+
+  BOOST_CHECK_EQUAL(dataInStorage[0].getContent().blockFromValue(), block);
+  BOOST_CHECK_EQUAL(dataInStorage[1].getContent().blockFromValue(), block);
+  BOOST_CHECK_EQUAL(dataInStorage[2].getContent().blockFromValue(), block);
+  BOOST_CHECK_EQUAL(dataInStorage[3].getContent().blockFromValue(), block);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestDispatcher
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace mgmt
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/channel-status.t.cpp b/tests/unit/mgmt/nfd/channel-status.t.cpp
new file mode 100644
index 0000000..b3afde8
--- /dev/null
+++ b/tests/unit/mgmt/nfd/channel-status.t.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/channel-status.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestChannelStatus)
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  ChannelStatus status1;
+  status1.setLocalUri("udp4://192.168.2.1");
+  Block wire = status1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x82, 0x14, 0x81, 0x12, 0x75, 0x64, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x31, 0x36, 0x38, 0x2e, 0x32, 0x2e, 0x31
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  ChannelStatus status2(wire);
+  BOOST_CHECK_EQUAL(status1, status2);
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  ChannelStatus status1, status2;
+
+  status1.setLocalUri("udp4://127.0.0.1:6363");
+  status2 = status1;
+  BOOST_CHECK_EQUAL(status1, status2);
+
+  status2.setLocalUri("dev://eth0");
+  BOOST_CHECK_NE(status1, status2);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  ChannelStatus status;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(status),
+                    "Channel(LocalUri: )");
+
+  status.setLocalUri("udp4://127.0.0.1:6363");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(status),
+                    "Channel(LocalUri: udp4://127.0.0.1:6363)");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestChannelStatus
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/command-options.t.cpp b/tests/unit/mgmt/nfd/command-options.t.cpp
new file mode 100644
index 0000000..e8fe76e
--- /dev/null
+++ b/tests/unit/mgmt/nfd/command-options.t.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/command-options.hpp"
+#include "security/signing-helpers.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestCommandOptions)
+
+BOOST_AUTO_TEST_CASE(Timeout)
+{
+  CommandOptions co;
+  BOOST_CHECK_EQUAL(co.getTimeout(), CommandOptions::DEFAULT_TIMEOUT);
+
+  co.setTimeout(7414_ms);
+  BOOST_CHECK_EQUAL(co.getTimeout(), 7414_ms);
+
+  BOOST_CHECK_THROW(co.setTimeout(time::milliseconds::zero()), std::out_of_range);
+  BOOST_CHECK_THROW(co.setTimeout(time::milliseconds(-1)), std::out_of_range);
+  BOOST_CHECK_EQUAL(co.getTimeout(), 7414_ms); // unchanged after throw
+
+  co.setTimeout(1_ms);
+  BOOST_CHECK_EQUAL(co.getTimeout(), 1_ms);
+}
+
+BOOST_AUTO_TEST_CASE(Prefix)
+{
+  CommandOptions co;
+  BOOST_CHECK_EQUAL(co.getPrefix(), CommandOptions::DEFAULT_PREFIX);
+
+  co.setPrefix(Name()); // empty Name is okay
+  BOOST_CHECK_EQUAL(co.getPrefix(), Name());
+
+  co.setPrefix("ndn:/localhop/net/example/nfd");
+  BOOST_CHECK_EQUAL(co.getPrefix(), Name("ndn:/localhop/net/example/nfd"));
+}
+
+BOOST_AUTO_TEST_CASE(SigningInfo)
+{
+  CommandOptions co;
+  BOOST_CHECK_EQUAL(co.getSigningInfo().getSignerType(), security::SigningInfo::SIGNER_TYPE_NULL);
+
+  co.setSigningInfo(signingByIdentity("ndn:/tmp/identity"));
+  BOOST_CHECK_EQUAL(co.getSigningInfo().getSignerType(), security::SigningInfo::SIGNER_TYPE_ID);
+  BOOST_CHECK_EQUAL(co.getSigningInfo().getSignerName(), "ndn:/tmp/identity");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCommandOptions
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/control-command.t.cpp b/tests/unit/mgmt/nfd/control-command.t.cpp
new file mode 100644
index 0000000..4db3752
--- /dev/null
+++ b/tests/unit/mgmt/nfd/control-command.t.cpp
@@ -0,0 +1,479 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/control-command.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestControlCommand)
+
+BOOST_AUTO_TEST_CASE(FaceCreateRequest)
+{
+  FaceCreateCommand command;
+
+  // good with required fields only
+  ControlParameters p1;
+  p1.setUri("tcp4://192.0.2.1:6363");
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK(Name("/PREFIX/faces/create").isPrefixOf(command.getRequestName("/PREFIX", p1)));
+
+  // good with optional fields
+  ControlParameters p2(p1);
+  p2.setLocalUri("tcp4://192.0.2.2:32114")
+    .setFacePersistency(FACE_PERSISTENCY_PERMANENT)
+    .setBaseCongestionMarkingInterval(100_ms)
+    .setDefaultCongestionThreshold(10000)
+    .setMtu(8192)
+    .setFlags(0x3)
+    .setMask(0x1);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+
+  // Uri is required
+  ControlParameters p3;
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+
+  // Name is forbidden
+  ControlParameters p4(p1);
+  p4.setName("/example");
+  BOOST_CHECK_THROW(command.validateRequest(p4), ControlCommand::ArgumentError);
+
+  // Flags and Mask must be specified together
+  ControlParameters p5(p1);
+  p5.setFlags(0x3);
+  BOOST_CHECK_THROW(command.validateRequest(p5), ControlCommand::ArgumentError);
+
+  ControlParameters p6(p1);
+  p6.setMask(0x1);
+  BOOST_CHECK_THROW(command.validateRequest(p6), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(FaceCreateResponse)
+{
+  FaceCreateCommand command;
+
+  // good
+  ControlParameters p1;
+  p1.setFaceId(3208)
+    .setUri("tcp4://192.0.2.1:6363")
+    .setLocalUri("tcp4://192.0.2.2:32114")
+    .setFacePersistency(FACE_PERSISTENCY_PERMANENT)
+    .setBaseCongestionMarkingInterval(500_ns)
+    .setDefaultCongestionThreshold(12345)
+    .setMtu(2048)
+    .setFlags(0x3);
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+
+  // Name is forbidden
+  ControlParameters p2(p1);
+  p2.setName("/example");
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  // Mask is forbidden
+  ControlParameters p3(p1);
+  p3.setMask(0x1);
+  BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
+
+  // FaceId must be valid
+  ControlParameters p4(p1);
+  p4.setFaceId(INVALID_FACE_ID);
+  BOOST_CHECK_THROW(command.validateResponse(p4), ControlCommand::ArgumentError);
+
+  // LocalUri is required
+  ControlParameters p5(p1);
+  p5.unsetLocalUri();
+  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(FaceUpdate)
+{
+  FaceUpdateCommand command;
+
+  ControlParameters p1;
+  p1.setFaceId(0);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  p1.setFaceId(INVALID_FACE_ID);
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+
+  p1.setFaceId(1);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+  command.applyDefaultsToRequest(p1);
+  BOOST_CHECK_EQUAL(p1.getFaceId(), 1);
+
+  ControlParameters p2;
+  p2.setFaceId(1)
+    .setFacePersistency(FACE_PERSISTENCY_PERSISTENT)
+    .setBaseCongestionMarkingInterval(765_ns)
+    .setDefaultCongestionThreshold(54321)
+    .setFlagBit(BIT_LOCAL_FIELDS_ENABLED, false);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError); // Mask forbidden but present
+
+  // Flags without Mask
+  p2.unsetMask();
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_NO_THROW(command.validateResponse(p2));
+
+  p2.setFlagBit(BIT_LOCAL_FIELDS_ENABLED, false);
+  p2.unsetFaceId();
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+
+  ControlParameters p3;
+  p3.setFaceId(1)
+    .setName("/ndn/name");
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
+
+  ControlParameters p4;
+  p4.setFaceId(1)
+    .setUri("tcp4://192.0.2.1");
+  BOOST_CHECK_THROW(command.validateRequest(p4), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p4), ControlCommand::ArgumentError);
+
+  ControlParameters p5;
+  BOOST_CHECK_NO_THROW(command.validateRequest(p5));
+  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+  BOOST_CHECK(!p5.hasFaceId());
+  command.applyDefaultsToRequest(p5);
+  BOOST_REQUIRE(p5.hasFaceId());
+  BOOST_CHECK_NO_THROW(command.validateRequest(p5));
+  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+  BOOST_CHECK_EQUAL(p5.getFaceId(), 0);
+
+  ControlParameters p6;
+  p6.setFaceId(1)
+    .setMtu(1024);
+  BOOST_CHECK_THROW(command.validateRequest(p6), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p6), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(FaceDestroy)
+{
+  FaceDestroyCommand command;
+
+  ControlParameters p1;
+  p1.setUri("tcp4://192.0.2.1")
+    .setFaceId(4);
+  BOOST_CHECK_THROW(command.validateRequest(p1), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+
+  ControlParameters p2;
+  p2.setFaceId(INVALID_FACE_ID);
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  ControlParameters p3;
+  p3.setFaceId(6);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p3));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p3));
+  Name n3;
+  BOOST_CHECK_NO_THROW(n3 = command.getRequestName("/PREFIX", p3));
+  BOOST_CHECK(Name("ndn:/PREFIX/faces/destroy").isPrefixOf(n3));
+}
+
+BOOST_AUTO_TEST_CASE(FibAddNextHop)
+{
+  FibAddNextHopCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/")
+    .setFaceId(22);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/fib/add-nexthop").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setFaceId(0)
+    .setCost(6);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  p2.setFaceId(INVALID_FACE_ID);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  command.applyDefaultsToRequest(p1);
+  BOOST_REQUIRE(p1.hasCost());
+  BOOST_CHECK_EQUAL(p1.getCost(), 0);
+
+  p1.unsetFaceId();
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  command.applyDefaultsToRequest(p1);
+  BOOST_REQUIRE(p1.hasFaceId());
+  BOOST_CHECK_EQUAL(p1.getFaceId(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FibRemoveNextHop)
+{
+  FibRemoveNextHopCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/")
+    .setFaceId(22);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/fib/remove-nexthop").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setFaceId(0);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  p2.setFaceId(INVALID_FACE_ID);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  p1.unsetFaceId();
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  command.applyDefaultsToRequest(p1);
+  BOOST_REQUIRE(p1.hasFaceId());
+  BOOST_CHECK_EQUAL(p1.getFaceId(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(CsConfigRequest)
+{
+  CsConfigCommand command;
+
+  // good empty request
+  ControlParameters p1;
+  command.validateRequest(p1);
+  BOOST_CHECK(Name("/PREFIX/cs/config").isPrefixOf(command.getRequestName("/PREFIX", p1)));
+
+  // good full request
+  ControlParameters p2;
+  p2.setCapacity(1574);
+  p2.setFlagBit(BIT_CS_ENABLE_ADMIT, true);
+  p2.setFlagBit(BIT_CS_ENABLE_SERVE, true);
+  command.validateRequest(p2);
+
+  // bad request: Flags but no Mask
+  ControlParameters p3(p2);
+  p3.unsetMask();
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+
+  // bad request: Mask but no Flags
+  ControlParameters p4(p2);
+  p4.unsetFlags();
+  BOOST_CHECK_THROW(command.validateRequest(p4), ControlCommand::ArgumentError);
+
+  // bad request: forbidden field
+  ControlParameters p5(p2);
+  p5.setName("/example");
+  BOOST_CHECK_THROW(command.validateRequest(p5), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(CsConfigResponse)
+{
+  CsConfigCommand command;
+
+  // bad empty response
+  ControlParameters p1;
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+
+  // bad response: Mask not allowed
+  ControlParameters p2;
+  p2.setCapacity(1574);
+  p2.setFlagBit(BIT_CS_ENABLE_ADMIT, true);
+  p2.setFlagBit(BIT_CS_ENABLE_SERVE, true);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  // good response
+  ControlParameters p3(p2);
+  p3.unsetMask();
+  command.validateResponse(p3);
+}
+
+BOOST_AUTO_TEST_CASE(CsEraseRequest)
+{
+  CsEraseCommand command;
+
+  // good no-limit request
+  ControlParameters p1;
+  p1.setName("/u4LYPNU8Q");
+  command.validateRequest(p1);
+  BOOST_CHECK(Name("/PREFIX/cs/erase").isPrefixOf(command.getRequestName("/PREFIX", p1)));
+
+  // good limited request
+  ControlParameters p2;
+  p2.setName("/IMw1RaLF");
+  p2.setCount(177);
+  command.validateRequest(p2);
+
+  // bad request: zero entry
+  ControlParameters p3;
+  p3.setName("/ahMID1jcib");
+  p3.setCount(0);
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+
+  // bad request: forbidden field
+  ControlParameters p4(p2);
+  p4.setCapacity(278);
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(CsEraseResponse)
+{
+  CsEraseCommand command;
+
+  // good normal response
+  ControlParameters p1;
+  p1.setName("/TwiIwCdR");
+  p1.setCount(1);
+  command.validateResponse(p1);
+
+  // good limit exceeded request
+  ControlParameters p2;
+  p2.setName("/NMsiy44pr");
+  p2.setCapacity(360);
+  p2.setCount(360);
+  command.validateResponse(p2);
+
+  // good zero-entry response
+  ControlParameters p3;
+  p3.setName("/5f1LRPh1L");
+  p3.setCount(0);
+  command.validateResponse(p3);
+
+  // bad request: missing Count
+  ControlParameters p4(p1);
+  p4.unsetCount();
+  BOOST_CHECK_THROW(command.validateResponse(p4), ControlCommand::ArgumentError);
+
+  // bad request: zero capacity
+  ControlParameters p5(p1);
+  p5.setCapacity(0);
+  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(StrategyChoiceSet)
+{
+  StrategyChoiceSetCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/")
+    .setStrategy("ndn:/strategy/P");
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/strategy-choice/set").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example");
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(StrategyChoiceUnset)
+{
+  StrategyChoiceUnsetCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/example");
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/strategy-choice/unset").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setStrategy("ndn:/strategy/P");
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  ControlParameters p3;
+  p3.setName("ndn:/");
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(RibRegister)
+{
+  RibRegisterCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/");
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/rib/register").isPrefixOf(n1));
+
+  command.applyDefaultsToRequest(p1);
+  BOOST_REQUIRE(p1.hasOrigin());
+  BOOST_CHECK_EQUAL(p1.getOrigin(), ROUTE_ORIGIN_APP);
+  BOOST_REQUIRE(p1.hasCost());
+  BOOST_CHECK_EQUAL(p1.getCost(), 0);
+  BOOST_REQUIRE(p1.hasFlags());
+  BOOST_CHECK_EQUAL(p1.getFlags(), static_cast<uint64_t>(ROUTE_FLAG_CHILD_INHERIT));
+  BOOST_CHECK_EQUAL(p1.hasExpirationPeriod(), false);
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setFaceId(2)
+    .setCost(6);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  command.applyDefaultsToRequest(p2);
+  BOOST_CHECK_EQUAL(p2.hasExpirationPeriod(), false);
+  BOOST_CHECK_NO_THROW(command.validateResponse(p2));
+}
+
+BOOST_AUTO_TEST_CASE(RibUnregister)
+{
+  RibUnregisterCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/")
+    .setFaceId(22)
+    .setOrigin(ROUTE_ORIGIN_STATIC);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/rib/unregister").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setFaceId(0)
+    .setOrigin(ROUTE_ORIGIN_APP);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  p2.setFaceId(INVALID_FACE_ID);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  p2.unsetFaceId();
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestControlCommand
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/control-parameters.t.cpp b/tests/unit/mgmt/nfd/control-parameters.t.cpp
new file mode 100644
index 0000000..6030060
--- /dev/null
+++ b/tests/unit/mgmt/nfd/control-parameters.t.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/control-parameters.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestControlParameters)
+
+BOOST_AUTO_TEST_CASE(Fields)
+{
+  ControlParameters input;
+
+  ControlParameters decoded(input.wireEncode());
+  BOOST_CHECK_EQUAL(decoded.hasName(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFaceId(), false);
+  BOOST_CHECK_EQUAL(decoded.hasUri(), false);
+  BOOST_CHECK_EQUAL(decoded.hasLocalUri(), false);
+  BOOST_CHECK_EQUAL(decoded.hasOrigin(), false);
+  BOOST_CHECK_EQUAL(decoded.hasCost(), false);
+  BOOST_CHECK_EQUAL(decoded.hasCapacity(), false);
+  BOOST_CHECK_EQUAL(decoded.hasCount(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFlags(), false);
+  BOOST_CHECK_EQUAL(decoded.hasMask(), false);
+  BOOST_CHECK_EQUAL(decoded.hasStrategy(), false);
+  BOOST_CHECK_EQUAL(decoded.hasExpirationPeriod(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFacePersistency(), false);
+
+  input.setName("/name");
+  input.setFaceId(2634);
+  input.setUri("udp4://192.0.2.1:6363");
+  input.setLocalUri("udp4://192.0.2.2:6363");
+  input.setOrigin(ROUTE_ORIGIN_NLSR);
+  input.setCost(1388);
+  input.setCapacity(2632);
+  input.setCount(3100);
+  input.setFlags(0xAFC4);
+  input.setMask(0xF7A1);
+  input.setStrategy("/strategy-name");
+  input.setExpirationPeriod(1800000_ms);
+  input.setFacePersistency(FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+
+  decoded.wireDecode(input.wireEncode());
+  BOOST_CHECK_EQUAL(decoded.hasName(), true);
+  BOOST_CHECK_EQUAL(decoded.hasFaceId(), true);
+  BOOST_CHECK_EQUAL(decoded.hasUri(), true);
+  BOOST_CHECK_EQUAL(decoded.hasLocalUri(), true);
+  BOOST_CHECK_EQUAL(decoded.hasOrigin(), true);
+  BOOST_CHECK_EQUAL(decoded.hasCost(), true);
+  BOOST_CHECK_EQUAL(decoded.hasCapacity(), true);
+  BOOST_CHECK_EQUAL(decoded.hasCount(), true);
+  BOOST_CHECK_EQUAL(decoded.hasFlags(), true);
+  BOOST_CHECK_EQUAL(decoded.hasMask(), true);
+  BOOST_CHECK_EQUAL(decoded.hasStrategy(), true);
+  BOOST_CHECK_EQUAL(decoded.hasExpirationPeriod(), true);
+  BOOST_CHECK_EQUAL(decoded.hasFacePersistency(), true);
+
+  BOOST_CHECK_EQUAL(decoded.getName(), "/name");
+  BOOST_CHECK_EQUAL(decoded.getFaceId(), 2634);
+  BOOST_CHECK_EQUAL(decoded.getUri(), "udp4://192.0.2.1:6363");
+  BOOST_CHECK_EQUAL(decoded.getLocalUri(), "udp4://192.0.2.2:6363");
+  BOOST_CHECK_EQUAL(decoded.getOrigin(), ROUTE_ORIGIN_NLSR);
+  BOOST_CHECK_EQUAL(decoded.getCost(), 1388);
+  BOOST_CHECK_EQUAL(decoded.getCapacity(), 2632);
+  BOOST_CHECK_EQUAL(decoded.getCount(), 3100);
+  BOOST_CHECK_EQUAL(decoded.getFlags(), 0xAFC4);
+  BOOST_CHECK_EQUAL(decoded.getMask(), 0xF7A1);
+  BOOST_CHECK_EQUAL(decoded.getStrategy(), "/strategy-name");
+  BOOST_CHECK_EQUAL(decoded.getExpirationPeriod(), 1800000_ms);
+  BOOST_CHECK_EQUAL(decoded.getFacePersistency(), FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+
+  input.unsetName();
+  input.unsetFaceId();
+  input.unsetUri();
+  input.unsetLocalUri();
+  input.unsetOrigin();
+  input.unsetCost();
+  input.unsetCapacity();
+  input.unsetCount();
+  input.unsetFlags();
+  input.unsetMask();
+  input.unsetStrategy();
+  input.unsetExpirationPeriod();
+  input.unsetFacePersistency();
+  BOOST_CHECK_EQUAL(input.hasName(), false);
+  BOOST_CHECK_EQUAL(input.hasFaceId(), false);
+  BOOST_CHECK_EQUAL(input.hasUri(), false);
+  BOOST_CHECK_EQUAL(input.hasLocalUri(), false);
+  BOOST_CHECK_EQUAL(input.hasOrigin(), false);
+  BOOST_CHECK_EQUAL(input.hasCost(), false);
+  BOOST_CHECK_EQUAL(input.hasCapacity(), false);
+  BOOST_CHECK_EQUAL(input.hasCount(), false);
+  BOOST_CHECK_EQUAL(input.hasFlags(), false);
+  BOOST_CHECK_EQUAL(input.hasMask(), false);
+  BOOST_CHECK_EQUAL(input.hasStrategy(), false);
+  BOOST_CHECK_EQUAL(input.hasExpirationPeriod(), false);
+  BOOST_CHECK_EQUAL(input.hasFacePersistency(), false);
+}
+
+BOOST_AUTO_TEST_CASE(FlagsAndMask)
+{
+  ControlParameters p;
+
+  BOOST_CHECK(!p.hasFlags());
+  BOOST_CHECK(!p.hasMask());
+  BOOST_CHECK(!p.hasFlagBit(0));
+  BOOST_CHECK(!p.getFlagBit(0));
+
+  // Set bit 2 to true (change Mask)
+  p.setFlagBit(2, true);
+  // 2^2 = 4
+  BOOST_CHECK_EQUAL(p.getFlags(), 4);
+  BOOST_CHECK_EQUAL(p.getMask(), 4);
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(p.getFlagBit(2));
+  BOOST_CHECK(!p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+
+  // Set bit 3 to true (no change to Mask)
+  p.setFlagBit(3, true, false);
+  // 2^3 + 2^2 = 12
+  BOOST_CHECK_EQUAL(p.getFlags(), 12);
+  // 2^2 = 4
+  BOOST_CHECK_EQUAL(p.getMask(), 4);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(p.getFlagBit(2));
+
+  // Set bit 1 to false (change Mask)
+  p.setFlagBit(1, false);
+  // 2^3 + 2^2 = 12
+  BOOST_CHECK_EQUAL(p.getFlags(), 12);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(p.getFlagBit(2));
+  BOOST_CHECK(p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+
+  // Set bit 2 to false (change Mask)
+  p.setFlagBit(2, false);
+  // 2^3 = 8
+  BOOST_CHECK_EQUAL(p.getFlags(), 8);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(!p.getFlagBit(2));
+  BOOST_CHECK(p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+
+  // Set bit 0 to true (change Mask)
+  p.setFlagBit(0, true);
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^2 + 2^1 + 2^0 = 7
+  BOOST_CHECK_EQUAL(p.getMask(), 7);
+  BOOST_CHECK(p.hasFlagBit(0));
+  BOOST_CHECK(p.getFlagBit(0));
+
+  // Unset bit 0
+  p.unsetFlagBit(0);
+  BOOST_REQUIRE(p.hasFlags());
+  BOOST_REQUIRE(p.hasMask());
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(p.hasFlagBit(1));
+  BOOST_CHECK(!p.hasFlagBit(0));
+  BOOST_CHECK(p.getFlagBit(0));
+
+  // Unset bit 3 (already unset in Mask, so no change)
+  p.unsetFlagBit(3);
+  BOOST_REQUIRE(p.hasFlags());
+  BOOST_REQUIRE(p.hasMask());
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+
+  // Unset bit 2
+  p.unsetFlagBit(2);
+  BOOST_REQUIRE(p.hasFlags());
+  BOOST_REQUIRE(p.hasMask());
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^1 = 2
+  BOOST_CHECK_EQUAL(p.getMask(), 2);
+  BOOST_CHECK(!p.hasFlagBit(2));
+  BOOST_CHECK(!p.getFlagBit(2));
+
+  // Unset bit 1
+  // Flags and Mask fields will be deleted as Mask is now 0
+  p.unsetFlagBit(1);
+  BOOST_CHECK(!p.hasFlags());
+  BOOST_CHECK(!p.hasMask());
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(!p.getFlagBit(3));
+  BOOST_CHECK(!p.hasFlagBit(2));
+  BOOST_CHECK(!p.getFlagBit(2));
+  BOOST_CHECK(!p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+  BOOST_CHECK(!p.hasFlagBit(0));
+  BOOST_CHECK(!p.getFlagBit(0));
+
+  BOOST_CHECK_THROW(p.setFlagBit(64, true), std::out_of_range);
+  BOOST_CHECK_THROW(p.unsetFlagBit(64), std::out_of_range);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestControlParameters
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/controller-fixture.hpp b/tests/unit/mgmt/nfd/controller-fixture.hpp
new file mode 100644
index 0000000..0d14296
--- /dev/null
+++ b/tests/unit/mgmt/nfd/controller-fixture.hpp
@@ -0,0 +1,90 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TESTS_MGMT_NFD_CONTROLLER_FIXTURE_HPP
+#define NDN_TESTS_MGMT_NFD_CONTROLLER_FIXTURE_HPP
+
+#include "mgmt/nfd/controller.hpp"
+#include "util/dummy-client-face.hpp"
+#include "security/v2/certificate-fetcher-offline.hpp"
+
+#include "boost-test.hpp"
+#include "dummy-validator.hpp"
+#include "../../identity-management-time-fixture.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+using namespace ndn::tests;
+
+class ControllerFixture : public IdentityManagementTimeFixture
+{
+protected:
+  ControllerFixture()
+    : face(io, m_keyChain)
+    , m_validator(true)
+    , controller(face, m_keyChain, m_validator)
+    , commandFailCallback(bind(&ControllerFixture::recordCommandFail, this, _1))
+    , datasetFailCallback(bind(&ControllerFixture::recordDatasetFail, this, _1, _2))
+  {
+    Name identityName("/localhost/ControllerFixture");
+    m_keyChain.setDefaultIdentity(this->addIdentity(identityName));
+  }
+
+  /** \brief controls whether Controller's validator should accept or reject validation requests
+   *
+   *  Initially, the validator accepts all requests.
+   *  Setting \p false causes validator to reject all requests.
+   */
+  void
+  setValidationResult(bool shouldAccept)
+  {
+    m_validator.getPolicy().setResult(shouldAccept);
+  }
+
+private:
+  void
+  recordCommandFail(const ControlResponse& response)
+  {
+    failCodes.push_back(response.getCode());
+  }
+
+  void
+  recordDatasetFail(uint32_t code, const std::string& reason)
+  {
+    failCodes.push_back(code);
+  }
+
+protected:
+  ndn::util::DummyClientFace face;
+  DummyValidator m_validator;
+  Controller controller;
+  Controller::CommandFailCallback commandFailCallback;
+  Controller::DatasetFailCallback datasetFailCallback;
+  std::vector<uint32_t> failCodes;
+};
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
+
+#endif // NDN_TESTS_MGMT_NFD_CONTROLLER_FIXTURE_HPP
diff --git a/tests/unit/mgmt/nfd/controller.t.cpp b/tests/unit/mgmt/nfd/controller.t.cpp
new file mode 100644
index 0000000..e9a3c4f
--- /dev/null
+++ b/tests/unit/mgmt/nfd/controller.t.cpp
@@ -0,0 +1,273 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/controller.hpp"
+#include "mgmt/nfd/control-response.hpp"
+
+#include "controller-fixture.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+
+class CommandFixture : public ControllerFixture
+{
+protected:
+  CommandFixture()
+    : succeedCallback(bind(&CommandFixture::succeed, this, _1))
+  {
+  }
+
+  void
+  respond(const ControlResponse& responsePayload)
+  {
+    auto responseData = makeData(face.sentInterests.at(0).getName());
+    responseData->setContent(responsePayload.wireEncode());
+    face.receive(*responseData);
+    this->advanceClocks(1_ms);
+  }
+
+private:
+  void
+  succeed(const ControlParameters& parameters)
+  {
+    succeeds.push_back(parameters);
+  }
+
+protected:
+  Controller::CommandSucceedCallback succeedCallback;
+  std::vector<ControlParameters> succeeds;
+};
+
+// This test suite focuses on ControlCommand functionality of Controller.
+// Individual commands are tested in nfd-control-command.t.cpp
+// StatusDataset functionality is tested in nfd-status-dataset.t.cpp
+BOOST_FIXTURE_TEST_SUITE(TestController, CommandFixture)
+
+static ControlParameters
+makeFaceCreateResponse()
+{
+  ControlParameters resp;
+  resp.setFaceId(22)
+      .setUri("tcp4://192.0.2.1:6363")
+      .setLocalUri("tcp4://192.0.2.2:10847")
+      .setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT)
+      .setFlags(0x7);
+  return resp;
+}
+
+BOOST_AUTO_TEST_CASE(Success)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, commandFailCallback);
+  this->advanceClocks(1_ms);
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  const Interest& requestInterest = face.sentInterests[0];
+
+  FaceCreateCommand command;
+  BOOST_CHECK(Name("/localhost/nfd/faces/create").isPrefixOf(requestInterest.getName()));
+  // 9 components: ndn:/localhost/nfd/faces/create/<parameters>/<signed Interest x4>
+  BOOST_REQUIRE_EQUAL(requestInterest.getName().size(), 9);
+  ControlParameters request;
+  // 4th component: <parameters>
+  BOOST_REQUIRE_NO_THROW(request.wireDecode(requestInterest.getName().at(4).blockFromValue()));
+  BOOST_CHECK_NO_THROW(command.validateRequest(request));
+  BOOST_CHECK_EQUAL(request.getUri(), parameters.getUri());
+  BOOST_CHECK_EQUAL(requestInterest.getInterestLifetime(), CommandOptions::DEFAULT_TIMEOUT);
+
+  ControlParameters responseBody = makeFaceCreateResponse();
+  ControlResponse responsePayload(201, "created");
+  responsePayload.setBody(responseBody.wireEncode());
+  this->respond(responsePayload);
+
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+  BOOST_REQUIRE_EQUAL(succeeds.size(), 1);
+  BOOST_CHECK_EQUAL(succeeds.back().getUri(), responseBody.getUri());
+  BOOST_CHECK_EQUAL(succeeds.back().getFaceId(), responseBody.getFaceId());
+}
+
+BOOST_AUTO_TEST_CASE(SuccessNoCallback)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  controller.start<FaceCreateCommand>(parameters, nullptr, commandFailCallback);
+  this->advanceClocks(1_ms);
+
+  ControlParameters responseBody = makeFaceCreateResponse();
+  ControlResponse responsePayload(201, "created");
+  responsePayload.setBody(responseBody.wireEncode());
+  this->respond(responsePayload);
+
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(OptionsPrefix)
+{
+  ControlParameters parameters;
+  parameters.setName("/ndn/com/example");
+  parameters.setFaceId(400);
+
+  CommandOptions options;
+  options.setPrefix("/localhop/net/example/router1/nfd");
+
+  controller.start<RibRegisterCommand>(parameters, succeedCallback, commandFailCallback, options);
+  this->advanceClocks(1_ms);
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  const Interest& requestInterest = face.sentInterests[0];
+
+  FaceCreateCommand command;
+  BOOST_CHECK(Name("/localhop/net/example/router1/nfd/rib/register").isPrefixOf(requestInterest.getName()));
+}
+
+BOOST_AUTO_TEST_CASE(InvalidRequest)
+{
+  ControlParameters parameters;
+  parameters.setName("ndn:/should-not-have-this-field");
+  // Uri is missing
+
+  BOOST_CHECK_THROW(controller.start<FaceCreateCommand>(
+                      parameters, succeedCallback, commandFailCallback),
+                    ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(ValidationFailure)
+{
+  this->setValidationResult(false);
+
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, commandFailCallback);
+  this->advanceClocks(1_ms);
+
+  ControlParameters responseBody = makeFaceCreateResponse();
+  ControlResponse responsePayload(201, "created");
+  responsePayload.setBody(responseBody.wireEncode());
+  this->respond(responsePayload);
+
+  BOOST_CHECK_EQUAL(succeeds.size(), 0);
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_VALIDATION);
+}
+
+BOOST_AUTO_TEST_CASE(ErrorCode)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, commandFailCallback);
+  this->advanceClocks(1_ms);
+
+  ControlResponse responsePayload(401, "Not Authenticated");
+  this->respond(responsePayload);
+
+  BOOST_CHECK_EQUAL(succeeds.size(), 0);
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), 401);
+}
+
+BOOST_AUTO_TEST_CASE(InvalidResponse)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, commandFailCallback);
+  this->advanceClocks(1_ms);
+
+  ControlParameters responseBody = makeFaceCreateResponse();
+  responseBody.unsetFaceId() // FaceId is missing
+              .setName("ndn:/should-not-have-this-field");
+  ControlResponse responsePayload(201, "created");
+  responsePayload.setBody(responseBody.wireEncode());
+  this->respond(responsePayload);
+
+  BOOST_CHECK_EQUAL(succeeds.size(), 0);
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(Nack)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, commandFailCallback);
+  this->advanceClocks(1_ms);
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  const Interest& requestInterest = face.sentInterests[0];
+
+  auto responseNack = makeNack(requestInterest, lp::NackReason::NO_ROUTE);
+  face.receive(responseNack);
+  this->advanceClocks(1_ms);
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_NACK);
+}
+
+BOOST_AUTO_TEST_CASE(Timeout)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  CommandOptions options;
+  options.setTimeout(50_ms);
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, commandFailCallback, options);
+  this->advanceClocks(1_ms); // express Interest
+  this->advanceClocks(51_ms); // timeout
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_TIMEOUT);
+}
+
+BOOST_AUTO_TEST_CASE(FailureNoCallback)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  CommandOptions options;
+  options.setTimeout(50_ms);
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, nullptr, options);
+  this->advanceClocks(1_ms); // express Interest
+  this->advanceClocks(51_ms); // timeout
+
+  BOOST_CHECK_EQUAL(succeeds.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestController
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/cs-info.t.cpp b/tests/unit/mgmt/nfd/cs-info.t.cpp
new file mode 100644
index 0000000..46a0ed3
--- /dev/null
+++ b/tests/unit/mgmt/nfd/cs-info.t.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/cs-info.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestCsInfo)
+
+static CsInfo
+makeCsInfo()
+{
+  return CsInfo()
+    .setCapacity(20177)
+    .setEnableAdmit(false)
+    .setEnableServe(true)
+    .setNEntries(5509)
+    .setNHits(12951)
+    .setNMisses(28179);
+}
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  CsInfo csi1 = makeCsInfo();
+  Block wire = csi1.wireEncode();
+
+  static const uint8_t EXPECTED[] = {
+    0x80, 0x13, // CsInfo
+          0x83, 0x02, 0x4E, 0xD1, // Capacity
+          0x6C, 0x01, 0x02,       // Flags
+          0x87, 0x02, 0x15, 0x85, // NCsEntries
+          0x81, 0x02, 0x32, 0x97, // NHits
+          0x82, 0x02, 0x6E, 0x13, // NMisses
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(wire.begin(), wire.end(), EXPECTED, EXPECTED + sizeof(EXPECTED));
+
+  CsInfo csi2(wire);
+  BOOST_CHECK_EQUAL(csi2.getCapacity(), 20177);
+  BOOST_CHECK_EQUAL(csi2.getEnableAdmit(), false);
+  BOOST_CHECK_EQUAL(csi2.getEnableServe(), true);
+  BOOST_CHECK_EQUAL(csi2.getNEntries(), 5509);
+  BOOST_CHECK_EQUAL(csi2.getNHits(), 12951);
+  BOOST_CHECK_EQUAL(csi2.getNMisses(), 28179);
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  CsInfo csi1, csi2;
+  BOOST_CHECK_EQUAL(csi1, csi2);
+
+  csi1 = makeCsInfo();
+  BOOST_CHECK_NE(csi1, csi2);
+  csi2 = csi1;
+  BOOST_CHECK_EQUAL(csi1, csi2);
+
+  csi2.setCapacity(csi2.getCapacity() + 1);
+  BOOST_CHECK_NE(csi1, csi2);
+  csi2 = csi1;
+
+  csi2.setEnableAdmit(!csi2.getEnableAdmit());
+  BOOST_CHECK_NE(csi1, csi2);
+  csi2 = csi1;
+
+  csi2.setEnableServe(!csi2.getEnableServe());
+  BOOST_CHECK_NE(csi1, csi2);
+  csi2 = csi1;
+
+  csi2.setNEntries(csi2.getNEntries() + 1);
+  BOOST_CHECK_NE(csi1, csi2);
+  csi2 = csi1;
+
+  csi2.setNHits(csi2.getNHits() + 1);
+  BOOST_CHECK_NE(csi1, csi2);
+  csi2 = csi1;
+
+  csi2.setNMisses(csi2.getNMisses() + 1);
+  BOOST_CHECK_NE(csi1, csi2);
+  csi2 = csi1;
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  CsInfo csi;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(csi),
+    "CS: 0 entries, 0 max, admit disabled, serve disabled, 0 hits, 0 misses");
+
+  csi = makeCsInfo();
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(csi),
+    "CS: 5509 entries, 20177 max, admit disabled, serve enabled, 12951 hits, 28179 misses");
+
+  csi.setEnableAdmit(true).setNHits(1).setNMisses(1);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(csi),
+    "CS: 5509 entries, 20177 max, admit enabled, serve enabled, 1 hit, 1 miss");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCsInfo
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/face-event-notification.t.cpp b/tests/unit/mgmt/nfd/face-event-notification.t.cpp
new file mode 100644
index 0000000..4da762b
--- /dev/null
+++ b/tests/unit/mgmt/nfd/face-event-notification.t.cpp
@@ -0,0 +1,269 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/face-event-notification.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestFaceEventNotification)
+
+BOOST_AUTO_TEST_CASE(Traits)
+{
+  FaceEventNotification notification;
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_NON_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_POINT_TO_POINT);
+
+  notification.setFaceScope(FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_POINT_TO_POINT);
+
+  notification.setFacePersistency(FACE_PERSISTENCY_ON_DEMAND);
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_ON_DEMAND);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_POINT_TO_POINT);
+
+  notification.setFacePersistency(FACE_PERSISTENCY_PERMANENT);
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_PERMANENT);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_POINT_TO_POINT);
+
+  notification.setLinkType(LINK_TYPE_MULTI_ACCESS);
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_PERMANENT);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_MULTI_ACCESS);
+}
+
+BOOST_AUTO_TEST_CASE(Created)
+{
+  FaceEventNotification notification1;
+  notification1.setKind(FACE_EVENT_CREATED)
+               .setFaceId(20)
+               .setRemoteUri("tcp4://192.0.2.1:55555")
+               .setLocalUri("tcp4://192.0.2.2:6363")
+               .setFaceScope(FACE_SCOPE_LOCAL)
+               .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+               .setLinkType(LINK_TYPE_MULTI_ACCESS)
+               .setFlags(0x3);
+  Block wire = notification1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0xc0, 0x41, 0xc1, 0x01, 0x01, 0x69, 0x01, 0x14, 0x72, 0x16,
+    0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x31, 0x3a, 0x35, 0x35, 0x35,
+    0x35, 0x35, 0x81, 0x15, 0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f,
+    0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x32,
+    0x3a, 0x36, 0x33, 0x36, 0x33, 0x84, 0x01, 0x01, 0x85, 0x01,
+    0x01, 0x86, 0x01, 0x01, 0x6c, 0x01, 0x03,
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FaceEventNotification notification2(wire);
+  BOOST_CHECK_EQUAL(notification1, notification2);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(notification2),
+                    "FaceEvent(Kind: created,\n"
+                    "          FaceId: 20,\n"
+                    "          RemoteUri: tcp4://192.0.2.1:55555,\n"
+                    "          LocalUri: tcp4://192.0.2.2:6363,\n"
+                    "          FaceScope: local,\n"
+                    "          FacePersistency: on-demand,\n"
+                    "          LinkType: multi-access,\n"
+                    "          Flags: 0x3\n"
+                    "          )");
+}
+
+BOOST_AUTO_TEST_CASE(Destroyed)
+{
+  FaceEventNotification notification1;
+  notification1.setKind(FACE_EVENT_DESTROYED)
+               .setFaceId(20)
+               .setRemoteUri("tcp4://192.0.2.1:55555")
+               .setLocalUri("tcp4://192.0.2.2:6363")
+               .setFaceScope(FACE_SCOPE_LOCAL)
+               .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+               .setLinkType(LINK_TYPE_MULTI_ACCESS)
+               .setFlags(0x4);
+  Block wire = notification1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0xc0, 0x41, 0xc1, 0x01, 0x02, 0x69, 0x01, 0x14, 0x72, 0x16,
+    0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x31, 0x3a, 0x35, 0x35, 0x35,
+    0x35, 0x35, 0x81, 0x15, 0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f,
+    0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x32,
+    0x3a, 0x36, 0x33, 0x36, 0x33, 0x84, 0x01, 0x01, 0x85, 0x01,
+    0x01, 0x86, 0x01, 0x01, 0x6c, 0x01, 0x04,
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FaceEventNotification notification2(wire);
+  BOOST_CHECK_EQUAL(notification1, notification2);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(notification2),
+                    "FaceEvent(Kind: destroyed,\n"
+                    "          FaceId: 20,\n"
+                    "          RemoteUri: tcp4://192.0.2.1:55555,\n"
+                    "          LocalUri: tcp4://192.0.2.2:6363,\n"
+                    "          FaceScope: local,\n"
+                    "          FacePersistency: on-demand,\n"
+                    "          LinkType: multi-access,\n"
+                    "          Flags: 0x4\n"
+                    "          )");
+}
+
+BOOST_AUTO_TEST_CASE(Up)
+{
+  FaceEventNotification notification1;
+  notification1.setKind(FACE_EVENT_UP)
+               .setFaceId(20)
+               .setRemoteUri("tcp4://192.0.2.1:55555")
+               .setLocalUri("tcp4://192.0.2.2:6363")
+               .setFaceScope(FACE_SCOPE_LOCAL)
+               .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+               .setLinkType(LINK_TYPE_MULTI_ACCESS)
+               .setFlags(0x05);
+  Block wire = notification1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0xc0, 0x41, 0xc1, 0x01, 0x03, 0x69, 0x01, 0x14, 0x72, 0x16,
+    0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x31, 0x3a, 0x35, 0x35, 0x35,
+    0x35, 0x35, 0x81, 0x15, 0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f,
+    0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x32,
+    0x3a, 0x36, 0x33, 0x36, 0x33, 0x84, 0x01, 0x01, 0x85, 0x01,
+    0x01, 0x86, 0x01, 0x01, 0x6c, 0x01, 0x05,
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FaceEventNotification notification2(wire);
+  BOOST_CHECK_EQUAL(notification1, notification2);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(notification2),
+                    "FaceEvent(Kind: up,\n"
+                    "          FaceId: 20,\n"
+                    "          RemoteUri: tcp4://192.0.2.1:55555,\n"
+                    "          LocalUri: tcp4://192.0.2.2:6363,\n"
+                    "          FaceScope: local,\n"
+                    "          FacePersistency: on-demand,\n"
+                    "          LinkType: multi-access,\n"
+                    "          Flags: 0x5\n"
+                    "          )");
+}
+
+BOOST_AUTO_TEST_CASE(Down)
+{
+  FaceEventNotification notification1;
+  notification1.setKind(FACE_EVENT_DOWN)
+               .setFaceId(20)
+               .setRemoteUri("tcp4://192.0.2.1:55555")
+               .setLocalUri("tcp4://192.0.2.2:6363")
+               .setFaceScope(FACE_SCOPE_LOCAL)
+               .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+               .setLinkType(LINK_TYPE_MULTI_ACCESS)
+               .setFlags(0x06);
+  Block wire = notification1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0xc0, 0x41, 0xc1, 0x01, 0x04, 0x69, 0x01, 0x14, 0x72, 0x16,
+    0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x31, 0x3a, 0x35, 0x35, 0x35,
+    0x35, 0x35, 0x81, 0x15, 0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f,
+    0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x32,
+    0x3a, 0x36, 0x33, 0x36, 0x33, 0x84, 0x01, 0x01, 0x85, 0x01,
+    0x01, 0x86, 0x01, 0x01, 0x6c, 0x01, 0x06,
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FaceEventNotification notification2(wire);
+  BOOST_CHECK_EQUAL(notification1, notification2);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(notification2),
+                    "FaceEvent(Kind: down,\n"
+                    "          FaceId: 20,\n"
+                    "          RemoteUri: tcp4://192.0.2.1:55555,\n"
+                    "          LocalUri: tcp4://192.0.2.2:6363,\n"
+                    "          FaceScope: local,\n"
+                    "          FacePersistency: on-demand,\n"
+                    "          LinkType: multi-access,\n"
+                    "          Flags: 0x6\n"
+                    "          )");
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  FaceEventNotification notification1, notification2;
+  BOOST_CHECK_EQUAL(notification1, notification2);
+
+  notification1.setKind(FACE_EVENT_CREATED)
+               .setFaceId(123)
+               .setRemoteUri("tcp4://192.0.2.1:55555")
+               .setLocalUri("tcp4://192.0.2.2:6363");
+  notification2 = notification1;
+  BOOST_CHECK_EQUAL(notification1, notification2);
+
+  notification2.setFaceId(42);
+  BOOST_CHECK_NE(notification1, notification2);
+
+  notification2 = notification1;
+  notification2.setKind(FACE_EVENT_DESTROYED);
+  BOOST_CHECK_NE(notification1, notification2);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFaceEventNotification
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/face-query-filter.t.cpp b/tests/unit/mgmt/nfd/face-query-filter.t.cpp
new file mode 100644
index 0000000..3b9b2a2
--- /dev/null
+++ b/tests/unit/mgmt/nfd/face-query-filter.t.cpp
@@ -0,0 +1,143 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/face-query-filter.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestFaceQueryFilter)
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  FaceQueryFilter filter1;
+  BOOST_CHECK_EQUAL(filter1.hasFaceId(), false);
+  BOOST_CHECK_EQUAL(filter1.hasUriScheme(), false);
+  BOOST_CHECK_EQUAL(filter1.hasRemoteUri(), false);
+  BOOST_CHECK_EQUAL(filter1.hasLocalUri(), false);
+  BOOST_CHECK_EQUAL(filter1.hasFaceScope(), false);
+  BOOST_CHECK_EQUAL(filter1.hasFacePersistency(), false);
+  BOOST_CHECK_EQUAL(filter1.hasLinkType(), false);
+
+  filter1.setFaceId(100)
+         .setUriScheme("tcp4")
+         .setRemoteUri("tcp4://192.0.2.1:6363")
+         .setLocalUri("tcp4://192.0.2.2:55555")
+         .setFaceScope(FACE_SCOPE_LOCAL)
+         .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+         .setLinkType(LINK_TYPE_MULTI_ACCESS);
+
+  Block wire = filter1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //  printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x96, 0x41, 0x69, 0x01, 0x64, 0x83, 0x04, 0x74, 0x63, 0x70,
+    0x34, 0x72, 0x15, 0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f,
+    0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x31, 0x3a,
+    0x36, 0x33, 0x36, 0x33, 0x81, 0x16, 0x74, 0x63, 0x70, 0x34,
+    0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32,
+    0x2e, 0x32, 0x3a, 0x35, 0x35, 0x35, 0x35, 0x35, 0x84, 0x01,
+    0x01, 0x85, 0x01, 0x01, 0x86, 0x01, 0x01,
+  };
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FaceQueryFilter filter2(wire);
+  BOOST_CHECK_EQUAL(filter1.getFaceId(), filter2.getFaceId());
+  BOOST_CHECK_EQUAL(filter1.getUriScheme(), filter2.getUriScheme());
+  BOOST_CHECK_EQUAL(filter1.getRemoteUri(), filter2.getRemoteUri());
+  BOOST_CHECK_EQUAL(filter1.getLocalUri(), filter2.getLocalUri());
+  BOOST_CHECK_EQUAL(filter1.getFaceScope(), filter2.getFaceScope());
+  BOOST_CHECK_EQUAL(filter1.getFacePersistency(), filter2.getFacePersistency());
+  BOOST_CHECK_EQUAL(filter1.getLinkType(), filter2.getLinkType());
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  FaceQueryFilter filter1, filter2;
+  BOOST_CHECK_EQUAL(filter1.empty(), true);
+  BOOST_CHECK_EQUAL(filter1, filter2);
+
+  filter1.setFaceId(100)
+         .setUriScheme("tcp4")
+         .setRemoteUri("tcp4://192.0.2.1:6363")
+         .setLocalUri("tcp4://192.0.2.2:55555")
+         .setFaceScope(FACE_SCOPE_LOCAL)
+         .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+         .setLinkType(LINK_TYPE_MULTI_ACCESS);
+  BOOST_CHECK_EQUAL(filter1.empty(), false);
+  BOOST_CHECK_NE(filter1, filter2);
+
+  filter2 = filter1;
+  BOOST_CHECK_EQUAL(filter1, filter2);
+
+  filter2.setFaceScope(FACE_SCOPE_NON_LOCAL);
+  BOOST_CHECK_NE(filter1, filter2);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  FaceQueryFilter filter;
+  filter.setFaceId(100)
+        .setUriScheme("tcp4")
+        .setRemoteUri("tcp4://192.0.2.1:6363")
+        .setLocalUri("tcp4://192.0.2.2:55555")
+        .setFaceScope(FACE_SCOPE_LOCAL)
+        .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+        .setLinkType(LINK_TYPE_MULTI_ACCESS);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(filter),
+                    "FaceQueryFilter(FaceID: 100,\n"
+                    "UriScheme: tcp4,\n"
+                    "RemoteUri: tcp4://192.0.2.1:6363,\n"
+                    "LocalUri: tcp4://192.0.2.2:55555,\n"
+                    "FaceScope: local,\n"
+                    "FacePersistency: on-demand,\n"
+                    "LinkType: multi-access,\n"
+                    ")");
+
+  filter.unsetFaceId()
+        .unsetUriScheme()
+        .unsetRemoteUri()
+        .unsetLocalUri()
+        .unsetFaceScope()
+        .unsetFacePersistency()
+        .unsetLinkType();
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(filter), "FaceQueryFilter()");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFaceQueryFilter
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/face-status.t.cpp b/tests/unit/mgmt/nfd/face-status.t.cpp
new file mode 100644
index 0000000..0ed7aca
--- /dev/null
+++ b/tests/unit/mgmt/nfd/face-status.t.cpp
@@ -0,0 +1,179 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/face-status.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestFaceStatus)
+
+static FaceStatus
+makeFaceStatus()
+{
+  return FaceStatus()
+      .setFaceId(100)
+      .setRemoteUri("tcp4://192.0.2.1:6363")
+      .setLocalUri("tcp4://192.0.2.2:55555")
+      .setFaceScope(FACE_SCOPE_LOCAL)
+      .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+      .setLinkType(LINK_TYPE_MULTI_ACCESS)
+      .setExpirationPeriod(10_s)
+      .setBaseCongestionMarkingInterval(5_ns)
+      .setDefaultCongestionThreshold(7)
+      .setMtu(9)
+      .setNInInterests(10)
+      .setNInData(200)
+      .setNInNacks(1)
+      .setNOutInterests(3000)
+      .setNOutData(4)
+      .setNOutNacks(2)
+      .setNInBytes(1329719163)
+      .setNOutBytes(999110448)
+      .setFlags(0x7);
+}
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  FaceStatus status1 = makeFaceStatus();
+  Block wire = status1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x80, 0x6a, 0x69, 0x01, 0x64, 0x72, 0x15, 0x74, 0x63, 0x70,
+    0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e,
+    0x32, 0x2e, 0x31, 0x3a, 0x36, 0x33, 0x36, 0x33, 0x81, 0x16,
+    0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x32, 0x3a, 0x35, 0x35, 0x35,
+    0x35, 0x35, 0x6d, 0x02, 0x27, 0x10, 0x84, 0x01, 0x01, 0x85,
+    0x01, 0x01, 0x86, 0x01, 0x01, 0x87, 0x01, 0x05, 0x88, 0x01,
+    0x07, 0x89, 0x01, 0x09, 0x90, 0x01, 0x0a, 0x91, 0x01, 0xc8,
+    0x97, 0x01, 0x01, 0x92, 0x02, 0x0b, 0xb8, 0x93, 0x01, 0x04,
+    0x98, 0x01, 0x02, 0x94, 0x04, 0x4f, 0x41, 0xe7, 0x7b, 0x95,
+    0x04, 0x3b, 0x8d, 0x37, 0x30, 0x6c, 0x01, 0x07,
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FaceStatus status2(wire);
+  BOOST_CHECK_EQUAL(status1, status2);
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  FaceStatus status1, status2;
+
+  status1 = makeFaceStatus();
+  status2 = status1;
+  BOOST_CHECK_EQUAL(status1, status2);
+
+  status2.setFaceId(42);
+  BOOST_CHECK_NE(status1, status2);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  FaceStatus status;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(status),
+                    "Face(FaceId: 0,\n"
+                    "     RemoteUri: ,\n"
+                    "     LocalUri: ,\n"
+                    "     ExpirationPeriod: infinite,\n"
+                    "     FaceScope: non-local,\n"
+                    "     FacePersistency: persistent,\n"
+                    "     LinkType: point-to-point,\n"
+                    "     Flags: 0x0,\n"
+                    "     Counters: {Interests: {in: 0, out: 0},\n"
+                    "                Data: {in: 0, out: 0},\n"
+                    "                Nacks: {in: 0, out: 0},\n"
+                    "                bytes: {in: 0, out: 0}}\n"
+                    "     )");
+
+  status = makeFaceStatus();
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(status),
+                    "Face(FaceId: 100,\n"
+                    "     RemoteUri: tcp4://192.0.2.1:6363,\n"
+                    "     LocalUri: tcp4://192.0.2.2:55555,\n"
+                    "     ExpirationPeriod: 10000 milliseconds,\n"
+                    "     FaceScope: local,\n"
+                    "     FacePersistency: on-demand,\n"
+                    "     LinkType: multi-access,\n"
+                    "     BaseCongestionMarkingInterval: 5 nanoseconds,\n"
+                    "     DefaultCongestionThreshold: 7 bytes,\n"
+                    "     Mtu: 9 bytes,\n"
+                    "     Flags: 0x7,\n"
+                    "     Counters: {Interests: {in: 10, out: 3000},\n"
+                    "                Data: {in: 200, out: 4},\n"
+                    "                Nacks: {in: 1, out: 2},\n"
+                    "                bytes: {in: 1329719163, out: 999110448}}\n"
+                    "     )");
+}
+
+BOOST_AUTO_TEST_CASE(ExpirationPeriod)
+{
+  FaceStatus status;
+  BOOST_CHECK_EQUAL(status.hasExpirationPeriod(), false);
+
+  status.setExpirationPeriod(1_min);
+  BOOST_REQUIRE_EQUAL(status.hasExpirationPeriod(), true);
+  BOOST_CHECK_EQUAL(status.getExpirationPeriod(), 1_min);
+
+  status.unsetExpirationPeriod();
+  BOOST_CHECK_EQUAL(status.hasExpirationPeriod(), false);
+}
+
+BOOST_AUTO_TEST_CASE(FlagBit)
+{
+  FaceStatus status;
+  status.setFlags(0x7);
+  BOOST_CHECK_EQUAL(status.getFlags(), 0x7);
+
+  BOOST_CHECK(status.getFlagBit(0));
+  BOOST_CHECK(status.getFlagBit(1));
+  BOOST_CHECK(status.getFlagBit(2));
+  BOOST_CHECK(!status.getFlagBit(3));
+
+  status.setFlagBit(3, true);
+  BOOST_CHECK_EQUAL(status.getFlags(), 0xf);
+  BOOST_CHECK(status.getFlagBit(3));
+
+  status.setFlagBit(1, false);
+  BOOST_CHECK_EQUAL(status.getFlags(), 0xd);
+  BOOST_CHECK(!status.getFlagBit(1));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFaceStatus
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/fib-entry.t.cpp b/tests/unit/mgmt/nfd/fib-entry.t.cpp
new file mode 100644
index 0000000..fa7ec69
--- /dev/null
+++ b/tests/unit/mgmt/nfd/fib-entry.t.cpp
@@ -0,0 +1,196 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/fib-entry.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestFibEntry)
+
+static FibEntry
+makeFibEntry()
+{
+  std::vector<NextHopRecord> nexthops;
+  for (size_t i = 1; i < 4; i++) {
+    nexthops.push_back(NextHopRecord()
+                       .setFaceId(i * 10)
+                       .setCost(i * 100 + 100));
+  }
+
+  return FibEntry()
+      .setPrefix("/this/is/a/test")
+      .setNextHopRecords(nexthops.begin(), nexthops.end());
+}
+
+BOOST_AUTO_TEST_CASE(NextHopRecordEncode)
+{
+  NextHopRecord record1;
+  record1.setFaceId(10)
+      .setCost(200);
+  const Block& wire = record1.wireEncode();
+
+  static const uint8_t expected[] = {
+    0x81, 0x06, 0x69, 0x01, 0x0a, 0x6a, 0x01, 0xc8
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  NextHopRecord record2(wire);
+  BOOST_CHECK_EQUAL(record1, record2);
+}
+
+BOOST_AUTO_TEST_CASE(NextHopRecordEquality)
+{
+  NextHopRecord record1, record2;
+
+  record1.setFaceId(10)
+      .setCost(200);
+  record2 = record1;
+  BOOST_CHECK_EQUAL(record1, record2);
+
+  record2.setFaceId(42);
+  BOOST_CHECK_NE(record1, record2);
+
+  record2 = record1;
+  record2.setCost(42);
+  BOOST_CHECK_NE(record1, record2);
+}
+
+BOOST_AUTO_TEST_CASE(FibEntryNoNextHopsEncode)
+{
+  FibEntry entry1;
+  entry1.setPrefix("/this/is/a/test");
+  BOOST_REQUIRE(entry1.getNextHopRecords().empty());
+  const Block& wire = entry1.wireEncode();
+
+  static const uint8_t expected[] = {
+    0x80, 0x15, 0x07, 0x13, 0x08, 0x04, 0x74, 0x68, 0x69, 0x73,
+    0x08, 0x02, 0x69, 0x73, 0x08, 0x01, 0x61, 0x08, 0x04, 0x74,
+    0x65, 0x73, 0x74
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FibEntry entry2(wire);
+  BOOST_CHECK_EQUAL(entry1, entry2);
+}
+
+BOOST_AUTO_TEST_CASE(FibEntryEncode)
+{
+  FibEntry entry1 = makeFibEntry();
+  NextHopRecord oneMore;
+  oneMore.setFaceId(40);
+  oneMore.setCost(500);
+  entry1.addNextHopRecord(oneMore);
+  const Block& wire = entry1.wireEncode();
+
+  static const uint8_t expected[] = {
+    0x80, 0x38, 0x07, 0x13, 0x08, 0x04, 0x74, 0x68, 0x69, 0x73, 0x08, 0x02, 0x69, 0x73, 0x08, 0x01,
+    0x61, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x81, 0x06, 0x69, 0x01, 0x0a, 0x6a, 0x01, 0xc8, 0x81,
+    0x07, 0x69, 0x01, 0x14, 0x6a, 0x02, 0x01, 0x2c, 0x81, 0x07, 0x69, 0x01, 0x1e, 0x6a, 0x02, 0x01,
+    0x90, 0x81, 0x07, 0x69, 0x01, 0x28, 0x6a, 0x02, 0x01, 0xf4
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FibEntry entry2(wire);
+  BOOST_CHECK_EQUAL(entry1, entry2);
+}
+
+BOOST_AUTO_TEST_CASE(FibEntryEquality)
+{
+  FibEntry entry1, entry2;
+  BOOST_CHECK_EQUAL(entry1, entry2);
+
+  entry1 = entry2 = makeFibEntry();
+  BOOST_CHECK_EQUAL(entry1, entry2);
+  BOOST_CHECK_EQUAL(entry2, entry1);
+
+  entry2.setPrefix("/another/prefix");
+  BOOST_CHECK_NE(entry1, entry2);
+
+  entry2 = entry1;
+  std::vector<NextHopRecord> empty;
+  entry2.setNextHopRecords(empty.begin(), empty.end());
+  BOOST_CHECK_NE(entry1, entry2);
+  BOOST_CHECK_NE(entry2, entry1);
+
+  entry2 = entry1;
+  auto nh1 = NextHopRecord()
+             .setFaceId(1)
+             .setCost(1000);
+  entry1.addNextHopRecord(nh1);
+  BOOST_CHECK_NE(entry1, entry2);
+  BOOST_CHECK_NE(entry2, entry1);
+
+  auto nh42 = NextHopRecord()
+              .setFaceId(42)
+              .setCost(42);
+  entry1.addNextHopRecord(nh42);
+  entry2.addNextHopRecord(nh42)
+      .addNextHopRecord(nh1);
+  BOOST_CHECK_EQUAL(entry1, entry2); // order of NextHopRecords is irrelevant
+  BOOST_CHECK_EQUAL(entry2, entry1);
+
+  entry1 = entry2 = makeFibEntry();
+  entry1.addNextHopRecord(nh1)
+      .addNextHopRecord(nh42);
+  entry2.addNextHopRecord(nh42)
+      .addNextHopRecord(nh42);
+  BOOST_CHECK_NE(entry1, entry2); // match each NextHopRecord at most once
+  BOOST_CHECK_NE(entry2, entry1);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  NextHopRecord record;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(record),
+                    "NextHopRecord(FaceId: 0, Cost: 0)");
+
+  FibEntry entry;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(entry),
+                    "FibEntry(Prefix: /,\n"
+                    "         NextHops: []\n"
+                    "         )");
+
+  entry = makeFibEntry();
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(entry),
+                    "FibEntry(Prefix: /this/is/a/test,\n"
+                    "         NextHops: [NextHopRecord(FaceId: 10, Cost: 200),\n"
+                    "                    NextHopRecord(FaceId: 20, Cost: 300),\n"
+                    "                    NextHopRecord(FaceId: 30, Cost: 400)]\n"
+                    "         )");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFibEntry
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/forwarder-status.t.cpp b/tests/unit/mgmt/nfd/forwarder-status.t.cpp
new file mode 100644
index 0000000..b2c3d48
--- /dev/null
+++ b/tests/unit/mgmt/nfd/forwarder-status.t.cpp
@@ -0,0 +1,141 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/forwarder-status.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestForwarderStatus)
+
+static ForwarderStatus
+makeForwarderStatus()
+{
+  return ForwarderStatus()
+      .setNfdVersion("0.5.1-14-g05dd444")
+      .setStartTimestamp(time::fromUnixTimestamp(time::milliseconds(375193249325LL)))
+      .setCurrentTimestamp(time::fromUnixTimestamp(time::milliseconds(886109034272LL)))
+      .setNNameTreeEntries(1849943160)
+      .setNFibEntries(621739748)
+      .setNPitEntries(482129741)
+      .setNMeasurementsEntries(1771725298)
+      .setNCsEntries(1264968688)
+      .setNInInterests(612811615)
+      .setNInData(1843576050)
+      .setNInNacks(1234)
+      .setNOutInterests(952144445)
+      .setNOutData(138198826)
+      .setNOutNacks(4321)
+      .setNSatisfiedInterests(961020)
+      .setNUnsatisfiedInterests(941024);
+}
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  ForwarderStatus status1 = makeForwarderStatus();
+  Block wire = status1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x15, 0x71, 0x80, 0x11, 0x30, 0x2e, 0x35, 0x2e, 0x31, 0x2d, 0x31, 0x34,
+    0x2d, 0x67, 0x30, 0x35, 0x64, 0x64, 0x34, 0x34, 0x34, 0x81, 0x08, 0x00,
+    0x00, 0x00, 0x57, 0x5b, 0x42, 0xa6, 0x2d, 0x82, 0x08, 0x00, 0x00, 0x00,
+    0xce, 0x50, 0x36, 0xd7, 0x20, 0x83, 0x04, 0x6e, 0x43, 0xe4, 0x78, 0x84,
+    0x04, 0x25, 0x0e, 0xfe, 0xe4, 0x85, 0x04, 0x1c, 0xbc, 0xb7, 0x4d, 0x86,
+    0x04, 0x69, 0x9a, 0x61, 0xf2, 0x87, 0x04, 0x4b, 0x65, 0xe3, 0xf0, 0x90,
+    0x04, 0x24, 0x86, 0xc3, 0x5f, 0x91, 0x04, 0x6d, 0xe2, 0xbc, 0xf2, 0x97,
+    0x02, 0x04, 0xd2, 0x92, 0x04, 0x38, 0xc0, 0x92, 0x3d, 0x93, 0x04, 0x08,
+    0x3c, 0xbf, 0x2a, 0x98, 0x02, 0x10, 0xe1, 0x99, 0x04, 0x00, 0x0e, 0xa9,
+    0xfc, 0x9a, 0x04, 0x00, 0x0e, 0x5b, 0xe0,
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  ForwarderStatus status2(wire);
+  BOOST_CHECK_EQUAL(status1, status2);
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  ForwarderStatus status1, status2;
+
+  status1 = makeForwarderStatus();
+  status2 = status1;
+  BOOST_CHECK_EQUAL(status1, status2);
+
+  status2.setNPitEntries(42);
+  BOOST_CHECK_NE(status1, status2);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  ForwarderStatus status;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(status),
+                    "GeneralStatus(NfdVersion: ,\n"
+                    "              StartTimestamp: 0 nanoseconds since Jan 1, 1970,\n"
+                    "              CurrentTimestamp: 0 nanoseconds since Jan 1, 1970,\n"
+                    "              Counters: {NameTreeEntries: 0,\n"
+                    "                         FibEntries: 0,\n"
+                    "                         PitEntries: 0,\n"
+                    "                         MeasurementsEntries: 0,\n"
+                    "                         CsEntries: 0,\n"
+                    "                         Interests: {in: 0, out: 0},\n"
+                    "                         Data: {in: 0, out: 0},\n"
+                    "                         Nacks: {in: 0, out: 0},\n"
+                    "                         SatisfiedInterests: 0,\n"
+                    "                         UnsatisfiedInterests: 0}\n"
+                    "              )");
+
+  status = makeForwarderStatus();
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(status),
+                    "GeneralStatus(NfdVersion: 0.5.1-14-g05dd444,\n"
+                    "              StartTimestamp: 375193249325000000 nanoseconds since Jan 1, 1970,\n"
+                    "              CurrentTimestamp: 886109034272000000 nanoseconds since Jan 1, 1970,\n"
+                    "              Counters: {NameTreeEntries: 1849943160,\n"
+                    "                         FibEntries: 621739748,\n"
+                    "                         PitEntries: 482129741,\n"
+                    "                         MeasurementsEntries: 1771725298,\n"
+                    "                         CsEntries: 1264968688,\n"
+                    "                         Interests: {in: 612811615, out: 952144445},\n"
+                    "                         Data: {in: 1843576050, out: 138198826},\n"
+                    "                         Nacks: {in: 1234, out: 4321},\n"
+                    "                         SatisfiedInterests: 961020,\n"
+                    "                         UnsatisfiedInterests: 941024}\n"
+                    "              )");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestForwarderStatus
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/rib-entry.t.cpp b/tests/unit/mgmt/nfd/rib-entry.t.cpp
new file mode 100644
index 0000000..f2ab51a
--- /dev/null
+++ b/tests/unit/mgmt/nfd/rib-entry.t.cpp
@@ -0,0 +1,240 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/rib-entry.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestRibEntry)
+
+static Route
+makeRoute()
+{
+  return Route()
+      .setFaceId(1)
+      .setOrigin(ROUTE_ORIGIN_NLSR)
+      .setCost(100)
+      .setFlags(ROUTE_FLAG_CAPTURE);
+}
+
+static RibEntry
+makeRibEntry()
+{
+  return RibEntry()
+      .setName("/hello/world")
+      .addRoute(makeRoute()
+                .setExpirationPeriod(10_s));
+}
+
+BOOST_AUTO_TEST_CASE(RouteEncode)
+{
+  Route route1 = makeRoute();
+  route1.setExpirationPeriod(10_s);
+  const Block& wire = route1.wireEncode();
+
+  static const uint8_t expected[] = {
+    0x81, 0x10, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02,
+    0x6d, 0x02, 0x27, 0x10
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  Route route2(wire);
+  BOOST_CHECK_EQUAL(route1, route2);
+}
+
+BOOST_AUTO_TEST_CASE(RouteNoExpirationPeriodEncode)
+{
+  Route route1 = makeRoute();
+  const Block& wire = route1.wireEncode();
+
+  static const uint8_t expected[] = {
+    0x81, 0x0C, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  Route route2(wire);
+  BOOST_CHECK_EQUAL(route1, route2);
+}
+
+BOOST_AUTO_TEST_CASE(RouteExpirationPeriod)
+{
+  Route route;
+  BOOST_CHECK_EQUAL(route.hasExpirationPeriod(), false);
+  BOOST_CHECK_EQUAL(route.getExpirationPeriod(), time::milliseconds::max());
+
+  route.setExpirationPeriod(1_min);
+  BOOST_CHECK_EQUAL(route.hasExpirationPeriod(), true);
+  BOOST_CHECK_EQUAL(route.getExpirationPeriod(), 1_min);
+
+  route.setExpirationPeriod(time::milliseconds::max());
+  BOOST_CHECK_EQUAL(route.hasExpirationPeriod(), false);
+  BOOST_CHECK_EQUAL(route.getExpirationPeriod(), time::milliseconds::max());
+
+  route.setExpirationPeriod(1_min);
+  BOOST_CHECK_EQUAL(route.hasExpirationPeriod(), true);
+
+  route.unsetExpirationPeriod();
+  BOOST_CHECK_EQUAL(route.hasExpirationPeriod(), false);
+}
+
+BOOST_AUTO_TEST_CASE(RouteEquality)
+{
+  Route route1, route2;
+
+  route1 = makeRoute();
+  route2 = route1;
+  BOOST_CHECK_EQUAL(route1, route2);
+
+  route2.setFaceId(42);
+  BOOST_CHECK_NE(route1, route2);
+
+  route2 = route1;
+  route2.setExpirationPeriod(1_min);
+  BOOST_CHECK_NE(route1, route2);
+}
+
+BOOST_AUTO_TEST_CASE(RibEntryEncode)
+{
+  RibEntry entry1 = makeRibEntry();
+  entry1.addRoute(Route()
+                  .setFaceId(2)
+                  .setOrigin(ROUTE_ORIGIN_APP)
+                  .setCost(32)
+                  .setFlags(ROUTE_FLAG_CHILD_INHERIT)
+                  .setExpirationPeriod(5_s));
+  const Block& wire = entry1.wireEncode();
+
+  static const uint8_t expected[] = {
+    0x80, 0x34, 0x07, 0x0e, 0x08, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x08, 0x05, 0x77,
+    0x6f, 0x72, 0x6c, 0x64, 0x81, 0x10, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01,
+    0x64, 0x6c, 0x01, 0x02, 0x6d, 0x02, 0x27, 0x10, 0x81, 0x10, 0x69, 0x01, 0x02, 0x6f,
+    0x01, 0x00, 0x6a, 0x01, 0x20, 0x6c, 0x01, 0x01, 0x6d, 0x02, 0x13, 0x88
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  RibEntry entry2(wire);
+  BOOST_CHECK_EQUAL(entry1, entry2);
+}
+
+BOOST_AUTO_TEST_CASE(RibEntryClearRoutes)
+{
+  RibEntry entry;
+  entry.setName("/hello/world");
+  BOOST_CHECK_EQUAL(entry.getRoutes().size(), 0);
+
+  Route route1;
+  route1.setFaceId(42);
+  entry.addRoute(route1);
+  BOOST_REQUIRE_EQUAL(entry.getRoutes().size(), 1);
+  BOOST_CHECK_EQUAL(entry.getRoutes().front(), route1);
+
+  entry.clearRoutes();
+  BOOST_CHECK_EQUAL(entry.getRoutes().size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(RibEntryEquality)
+{
+  RibEntry entry1, entry2;
+  BOOST_CHECK_EQUAL(entry1, entry2);
+
+  entry1 = entry2 = makeRibEntry();
+  BOOST_CHECK_EQUAL(entry1, entry2);
+  BOOST_CHECK_EQUAL(entry2, entry1);
+
+  entry2.setName("/different/name");
+  BOOST_CHECK_NE(entry1, entry2);
+
+  entry2 = entry1;
+  std::vector<Route> empty;
+  entry2.setRoutes(empty.begin(), empty.end());
+  BOOST_CHECK_NE(entry1, entry2);
+  BOOST_CHECK_NE(entry2, entry1);
+
+  entry2 = entry1;
+  auto r1 = Route()
+            .setFaceId(1)
+            .setCost(1000);
+  entry1.addRoute(r1);
+  BOOST_CHECK_NE(entry1, entry2);
+  BOOST_CHECK_NE(entry2, entry1);
+
+  auto r42 = Route()
+             .setFaceId(42)
+             .setCost(42);
+  entry1.addRoute(r42);
+  entry2.addRoute(r42)
+      .addRoute(r1);
+  BOOST_CHECK_EQUAL(entry1, entry2); // order of Routes is irrelevant
+  BOOST_CHECK_EQUAL(entry2, entry1);
+
+  entry1 = entry2 = makeRibEntry();
+  entry1.addRoute(r1)
+      .addRoute(r42);
+  entry2.addRoute(r42)
+      .addRoute(r42);
+  BOOST_CHECK_NE(entry1, entry2); // match each Route at most once
+  BOOST_CHECK_NE(entry2, entry1);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  Route route;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(route),
+                    "Route(FaceId: 0, Origin: app, Cost: 0, Flags: 0x1, ExpirationPeriod: infinite)");
+
+  RibEntry entry;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(entry),
+                    "RibEntry(Prefix: /,\n"
+                    "         Routes: []\n"
+                    "         )");
+
+  entry = makeRibEntry();
+  entry.addRoute(Route()
+                 .setFaceId(2)
+                 .setOrigin(ROUTE_ORIGIN_STATIC)
+                 .setCost(32)
+                 .setFlags(ROUTE_FLAG_CHILD_INHERIT));
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(entry),
+                    "RibEntry(Prefix: /hello/world,\n"
+                    "         Routes: [Route(FaceId: 1, Origin: nlsr, Cost: 100, Flags: 0x2, "
+                    "ExpirationPeriod: 10000 milliseconds),\n"
+                    "                  Route(FaceId: 2, Origin: static, Cost: 32, Flags: 0x1, "
+                    "ExpirationPeriod: infinite)]\n"
+                    "         )");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestRibEntry
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/status-dataset.t.cpp b/tests/unit/mgmt/nfd/status-dataset.t.cpp
new file mode 100644
index 0000000..a436de0
--- /dev/null
+++ b/tests/unit/mgmt/nfd/status-dataset.t.cpp
@@ -0,0 +1,486 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/status-dataset.hpp"
+#include "mgmt/nfd/controller.hpp"
+
+#include "controller-fixture.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+
+class ControllerStatusDatasetFixture : public ControllerFixture
+{
+protected:
+  /** \brief send one WireEncodable as Data reply
+   *  \param prefix dataset prefix without version and segment
+   *  \param payload payload block
+   *  \note payload must fit in one Data
+   */
+  template<typename T>
+  void
+  sendDataset(const Name& prefix, const T& payload)
+  {
+    BOOST_CONCEPT_ASSERT((WireEncodable<T>));
+
+    auto data = this->prepareDatasetReply(prefix);
+    data->setContent(payload.wireEncode());
+    face.receive(*signData(data));
+  }
+
+  /** \brief send two WireEncodables as Data reply
+   *  \param prefix dataset prefix without version and segment
+   *  \param payload1 first vector item
+   *  \param payload2 second vector item
+   *  \note all payloads must fit in one Data
+   */
+  template<typename T1, typename T2>
+  void
+  sendDataset(const Name& prefix, const T1& payload1, const T2& payload2)
+  {
+    // The test suite allows up to two items, and put them in the same Data packet,
+    // because this test suite focuses on Controller::fetch<StatusDataset>,
+    // and is not intended to cover SegmentFetcher behavior.
+
+    BOOST_CONCEPT_ASSERT((WireEncodable<T1>));
+    BOOST_CONCEPT_ASSERT((WireEncodable<T2>));
+
+    ndn::encoding::EncodingBuffer buffer;
+    payload2.wireEncode(buffer);
+    payload1.wireEncode(buffer);
+
+    auto data = this->prepareDatasetReply(prefix);
+    data->setContent(buffer.buf(), buffer.size());
+    face.receive(*signData(data));
+  }
+
+private:
+  shared_ptr<Data>
+  prepareDatasetReply(const Name& prefix)
+  {
+    Name name = prefix;
+    name.appendVersion().appendSegment(0);
+
+    // These warnings assist in debugging a `hasResult` check failure.
+    // They usually indicate a misspelled prefix or incorrect timing in the test case.
+    if (face.sentInterests.size() < 1) {
+      BOOST_WARN_MESSAGE(false, "no Interest expressed");
+    }
+    else {
+      BOOST_WARN_MESSAGE(face.sentInterests.back().getName().isPrefixOf(name),
+                         "last Interest " << face.sentInterests.back().getName() <<
+                         " cannot be satisfied by this Data " << name);
+    }
+
+    auto data = make_shared<Data>(name);
+    data->setFinalBlock(name[-1]);
+    return data;
+  }
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestStatusDataset, ControllerStatusDatasetFixture)
+
+BOOST_AUTO_TEST_SUITE(Failures)
+
+BOOST_AUTO_TEST_CASE(Timeout)
+{
+  CommandOptions options;
+  options.setTimeout(3000_ms);
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback,
+    options);
+  this->advanceClocks(500_ms);
+  BOOST_CHECK_EQUAL(controller.m_fetchers.size(), 1);
+
+  this->advanceClocks(500_ms, 6);
+  BOOST_CHECK_EQUAL(controller.m_fetchers.size(), 0);
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_TIMEOUT);
+}
+
+BOOST_AUTO_TEST_CASE(DataHasNoSegment)
+{
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  face.receive(*makeData("/localhost/nfd/faces/list/%FD%00"));
+  this->advanceClocks(500_ms);
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_SERVER);
+}
+
+BOOST_AUTO_TEST_CASE(ValidationFailure)
+{
+  this->setValidationResult(false);
+
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  FaceStatus payload;
+  payload.setFaceId(5744);
+  this->sendDataset("/localhost/nfd/faces/list", payload);
+  this->advanceClocks(500_ms);
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_VALIDATION);
+}
+
+BOOST_AUTO_TEST_CASE(Nack)
+{
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  face.receive(lp::Nack(face.sentInterests.back()));
+  this->advanceClocks(500_ms);
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_NACK);
+}
+
+BOOST_AUTO_TEST_CASE(ParseError1)
+{
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  Name payload; // Name is not valid FaceStatus
+  this->sendDataset("/localhost/nfd/faces/list", payload);
+  this->advanceClocks(500_ms);
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_SERVER);
+}
+
+BOOST_AUTO_TEST_CASE(ParseError2)
+{
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  FaceStatus payload1;
+  payload1.setFaceId(10930);
+  Name payload2; // Name is not valid FaceStatus
+  this->sendDataset("/localhost/nfd/faces/list", payload1, payload2);
+  this->advanceClocks(500_ms);
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_SERVER);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Failures
+
+BOOST_AUTO_TEST_SUITE(NoCallback)
+
+BOOST_AUTO_TEST_CASE(Success)
+{
+  controller.fetch<FaceDataset>(
+    nullptr,
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+  BOOST_CHECK_EQUAL(controller.m_fetchers.size(), 1);
+
+  FaceStatus payload;
+  payload.setFaceId(2577);
+  this->sendDataset("/localhost/nfd/faces/list", payload);
+  BOOST_CHECK_NO_THROW(this->advanceClocks(500_ms));
+
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+  BOOST_CHECK_EQUAL(controller.m_fetchers.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(Failure)
+{
+  CommandOptions options;
+  options.setTimeout(3000_ms);
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    nullptr,
+    options);
+  BOOST_CHECK_NO_THROW(this->advanceClocks(500_ms, 7));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // NoCallback
+
+BOOST_AUTO_TEST_SUITE(Datasets)
+
+BOOST_AUTO_TEST_CASE(StatusGeneral)
+{
+  bool hasResult = false;
+  controller.fetch<ForwarderGeneralStatusDataset>(
+    [&hasResult] (const ForwarderStatus& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.getNfdVersion(), "0.4.2");
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  ForwarderStatus payload;
+  payload.setNfdVersion("0.4.2");
+  this->sendDataset("/localhost/nfd/status/general", payload);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceList)
+{
+  bool hasResult = false;
+  controller.fetch<FaceDataset>(
+    [&hasResult] (const std::vector<FaceStatus>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getFaceId(), 24485);
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  FaceStatus payload1;
+  payload1.setFaceId(24485);
+  FaceStatus payload2;
+  payload2.setFaceId(12987);
+  this->sendDataset("/localhost/nfd/faces/list", payload1, payload2);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceQuery)
+{
+  FaceQueryFilter filter;
+  filter.setUriScheme("udp4");
+  bool hasResult = false;
+  controller.fetch<FaceQueryDataset>(
+    filter,
+    [&hasResult] (const std::vector<FaceStatus>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 1);
+      BOOST_CHECK_EQUAL(result.front().getFaceId(), 8795);
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  Name prefix("/localhost/nfd/faces/query");
+  prefix.append(filter.wireEncode());
+  FaceStatus payload;
+  payload.setFaceId(8795);
+  this->sendDataset(prefix, payload);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceQueryWithOptions)
+{
+  FaceQueryFilter filter;
+  filter.setUriScheme("udp4");
+  CommandOptions options;
+  options.setTimeout(3000_ms);
+  bool hasResult = false;
+  controller.fetch<FaceQueryDataset>(
+    filter,
+    [&hasResult] (const std::vector<FaceStatus>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 1);
+      BOOST_CHECK_EQUAL(result.front().getFaceId(), 14022);
+    },
+    datasetFailCallback,
+    options);
+  this->advanceClocks(500_ms);
+
+  Name prefix("/localhost/nfd/faces/query");
+  prefix.append(filter.wireEncode());
+  FaceStatus payload;
+  payload.setFaceId(14022);
+  this->sendDataset(prefix, payload);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceChannels)
+{
+  bool hasResult = false;
+  controller.fetch<ChannelDataset>(
+    [&hasResult] (const std::vector<ChannelStatus>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getLocalUri(), "tcp4://192.0.2.1:6363");
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  ChannelStatus payload1;
+  payload1.setLocalUri("tcp4://192.0.2.1:6363");
+  ChannelStatus payload2;
+  payload2.setLocalUri("udp4://192.0.2.1:6363");
+  this->sendDataset("/localhost/nfd/faces/channels", payload1, payload2);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FibList)
+{
+  bool hasResult = false;
+  controller.fetch<FibDataset>(
+    [&hasResult] (const std::vector<FibEntry>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getPrefix(), "/wYs7fzYcfG");
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  FibEntry payload1;
+  payload1.setPrefix("/wYs7fzYcfG");
+  FibEntry payload2;
+  payload2.setPrefix("/LKvmnzY5S");
+  this->sendDataset("/localhost/nfd/fib/list", payload1, payload2);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(CsInfo)
+{
+  using ndn::nfd::CsInfo;
+
+  bool hasResult = false;
+  controller.fetch<CsInfoDataset>(
+    [&hasResult] (const CsInfo& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.getNHits(), 4539);
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  CsInfo payload;
+  payload.setNHits(4539);
+  this->sendDataset("/localhost/nfd/cs/info", payload);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(StrategyChoiceList)
+{
+  bool hasResult = false;
+  controller.fetch<StrategyChoiceDataset>(
+    [&hasResult] (const std::vector<StrategyChoice>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getName(), "/8MLz6N3B");
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  StrategyChoice payload1;
+  payload1.setName("/8MLz6N3B");
+  StrategyChoice payload2;
+  payload2.setName("/svqcBu0YwU");
+  this->sendDataset("/localhost/nfd/strategy-choice/list", payload1, payload2);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(RibList)
+{
+  bool hasResult = false;
+  controller.fetch<RibDataset>(
+    [&hasResult] (const std::vector<RibEntry>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getName(), "/zXxBth97ee");
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  RibEntry payload1;
+  payload1.setName("/zXxBth97ee");
+  RibEntry payload2;
+  payload2.setName("/rJ8CvUpr4G");
+  this->sendDataset("/localhost/nfd/rib/list", payload1, payload2);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(RibListWithOptions)
+{
+  CommandOptions options;
+  options.setPrefix("/localhop/nfd");
+  bool hasResult = false;
+  controller.fetch<RibDataset>(
+    [&hasResult] (const std::vector<RibEntry>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 1);
+      BOOST_CHECK_EQUAL(result.front().getName(), "/e6L5K4ascd");
+    },
+    datasetFailCallback,
+    options);
+  this->advanceClocks(500_ms);
+
+  RibEntry payload;
+  payload.setName("/e6L5K4ascd");
+  this->sendDataset("/localhop/nfd/rib/list", payload);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Datasets
+
+BOOST_AUTO_TEST_SUITE_END() // TestStatusDataset
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/strategy-choice.t.cpp b/tests/unit/mgmt/nfd/strategy-choice.t.cpp
new file mode 100644
index 0000000..9996541
--- /dev/null
+++ b/tests/unit/mgmt/nfd/strategy-choice.t.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/strategy-choice.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestStrategyChoice)
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  StrategyChoice sc1;
+  sc1.setName("/hello/world")
+     .setStrategy("/some/non/existing/strategy/name");
+  Block wire = sc1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x80, 0x39, 0x07, 0x0e, 0x08, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x08, 0x05, 0x77,
+    0x6f, 0x72, 0x6c, 0x64, 0x6b, 0x27, 0x07, 0x25, 0x08, 0x04, 0x73, 0x6f, 0x6d, 0x65,
+    0x08, 0x03, 0x6e, 0x6f, 0x6e, 0x08, 0x08, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e,
+    0x67, 0x08, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x08, 0x04, 0x6e,
+    0x61, 0x6d, 0x65
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  StrategyChoice sc2(wire);
+  BOOST_CHECK_EQUAL(sc1, sc2);
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  StrategyChoice sc1, sc2;
+
+  sc1.setName("/A")
+     .setStrategy("/strategyP");
+  sc2 = sc1;
+  BOOST_CHECK_EQUAL(sc1, sc2);
+
+  sc2.setName("/B");
+  BOOST_CHECK_NE(sc1, sc2);
+
+  sc2 = sc1;
+  sc2.setStrategy("/strategyQ");
+  BOOST_CHECK_NE(sc1, sc2);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  StrategyChoice sc;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(sc),
+                    "StrategyChoice(Name: /, Strategy: /)");
+
+  sc.setName("/A")
+    .setStrategy("/strategyP");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(sc),
+                    "StrategyChoice(Name: /A, Strategy: /strategyP)");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestStrategyChoice
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/status-dataset-context.t.cpp b/tests/unit/mgmt/status-dataset-context.t.cpp
new file mode 100644
index 0000000..34e6fc1
--- /dev/null
+++ b/tests/unit/mgmt/status-dataset-context.t.cpp
@@ -0,0 +1,309 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/status-dataset-context.hpp"
+
+#include "boost-test.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace mgmt {
+namespace tests {
+
+using namespace ndn::tests;
+
+class StatusDatasetContextFixture
+{
+private:
+  struct SendDataArgs
+  {
+    Name dataName;
+    Block content;
+    time::milliseconds imsFresh;
+    bool isFinalBlock;
+  };
+
+protected:
+  StatusDatasetContextFixture()
+    : interest(makeInterest("/test/context/interest"))
+    , contentBlock(makeStringBlock(tlv::Content, "/test/data/content"))
+    , context(*interest,
+              [this] (const Name& dataName, const Block& content,
+                      time::milliseconds imsFresh, bool isFinalBlock) {
+                SendDataArgs args{dataName, content, imsFresh, isFinalBlock};
+                sendDataHistory.push_back(args);
+              },
+              [this] (const ControlResponse& resp) {
+                sendNackHistory.push_back(resp);
+              })
+    , defaultImsFresh(1000_ms)
+  {
+  }
+
+  Name
+  makeSegmentName(size_t segmentNo) const
+  {
+    auto name = context.getPrefix();
+    return name.appendSegment(segmentNo);
+  }
+
+  Block
+  concatenateDataContent() const
+  {
+    EncodingBuffer encoder;
+    size_t valueLength = 0;
+    for (const auto& args : sendDataHistory) {
+      const auto& content = args.content;
+      valueLength += encoder.appendByteArray(content.value(), content.value_size());
+    }
+    encoder.prependVarNumber(valueLength);
+    encoder.prependVarNumber(tlv::Content);
+    return encoder.block();
+  }
+
+protected:
+  std::vector<SendDataArgs> sendDataHistory;
+  std::vector<ControlResponse> sendNackHistory;
+  shared_ptr<Interest> interest;
+  Block contentBlock;
+  mgmt::StatusDatasetContext context;
+  time::milliseconds defaultImsFresh;
+};
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_FIXTURE_TEST_SUITE(TestStatusDatasetContext, StatusDatasetContextFixture)
+
+BOOST_AUTO_TEST_SUITE(Prefix)
+
+BOOST_AUTO_TEST_CASE(Get)
+{
+  Name dataName = context.getPrefix();
+  BOOST_CHECK(dataName[-1].isVersion());
+  BOOST_CHECK_EQUAL(dataName.getPrefix(-1), interest->getName());
+}
+
+BOOST_AUTO_TEST_CASE(SetValid)
+{
+  Name validPrefix = Name(interest->getName()).append("/valid");
+  BOOST_CHECK_NO_THROW(context.setPrefix(validPrefix));
+  BOOST_CHECK(context.getPrefix()[-1].isVersion());
+  BOOST_CHECK_EQUAL(context.getPrefix().getPrefix(-1), validPrefix);
+}
+
+BOOST_AUTO_TEST_CASE(SetInvalid)
+{
+  Name invalidPrefix = Name(interest->getName()).getPrefix(-1).append("/invalid");
+  BOOST_CHECK_THROW(context.setPrefix(invalidPrefix), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(SetValidWithAppendCalled)
+{
+  Name validPrefix = Name(interest->getName()).append("/valid");
+  context.append(contentBlock);
+  BOOST_CHECK_THROW(context.setPrefix(validPrefix), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(SetValidWithEndCalled)
+{
+  Name validPrefix = Name(interest->getName()).append("/valid");
+  context.end();
+  BOOST_CHECK_THROW(context.setPrefix(validPrefix), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(SetValidWithRejectCalled)
+{
+  Name validPrefix = Name(interest->getName()).append("/valid");
+  context.reject();
+  BOOST_CHECK_THROW(context.setPrefix(validPrefix), std::domain_error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Prefix
+
+BOOST_AUTO_TEST_SUITE(Expiry)
+
+BOOST_AUTO_TEST_CASE(GetAndSet)
+{
+  auto period = 9527_ms;
+  BOOST_CHECK_EQUAL(context.getExpiry(), 1000_ms);
+  BOOST_CHECK_EQUAL(context.setExpiry(period).getExpiry(), period);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Expiry
+
+BOOST_AUTO_TEST_SUITE(Respond)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  BOOST_CHECK_NO_THROW(context.append(contentBlock));
+  BOOST_CHECK(sendDataHistory.empty()); // does not call end yet
+
+  BOOST_CHECK_NO_THROW(context.end());
+
+  BOOST_REQUIRE_EQUAL(sendDataHistory.size(), 1);
+  const auto& args = sendDataHistory[0];
+
+  BOOST_CHECK_EQUAL(args.dataName, makeSegmentName(0));
+  BOOST_CHECK_EQUAL(args.content.blockFromValue(), contentBlock);
+  BOOST_CHECK_EQUAL(args.imsFresh, defaultImsFresh);
+  BOOST_CHECK_EQUAL(args.isFinalBlock, true);
+}
+
+BOOST_AUTO_TEST_CASE(Large)
+{
+  static Block largeBlock = [] () -> Block {
+    EncodingBuffer encoder;
+    size_t maxBlockSize = MAX_NDN_PACKET_SIZE >> 1;
+    for (size_t i = 0; i < maxBlockSize; ++i) {
+      encoder.prependByte(1);
+    }
+    encoder.prependVarNumber(maxBlockSize);
+    encoder.prependVarNumber(tlv::Content);
+    return encoder.block();
+  }();
+
+  BOOST_CHECK_NO_THROW(context.append(largeBlock));
+  BOOST_CHECK_NO_THROW(context.end());
+
+  // two segments are generated
+  BOOST_REQUIRE_EQUAL(sendDataHistory.size(), 2);
+
+  // check segment0
+  BOOST_CHECK_EQUAL(sendDataHistory[0].dataName, makeSegmentName(0));
+  BOOST_CHECK_EQUAL(sendDataHistory[0].imsFresh, defaultImsFresh);
+  BOOST_CHECK_EQUAL(sendDataHistory[0].isFinalBlock, false);
+
+  // check segment1
+  BOOST_CHECK_EQUAL(sendDataHistory[1].dataName, makeSegmentName(1));
+  BOOST_CHECK_EQUAL(sendDataHistory[1].imsFresh, defaultImsFresh);
+  BOOST_CHECK_EQUAL(sendDataHistory[1].isFinalBlock, true);
+
+  // check data content
+  auto contentLargeBlock = concatenateDataContent();
+  BOOST_CHECK_NO_THROW(contentLargeBlock.parse());
+  BOOST_REQUIRE_EQUAL(contentLargeBlock.elements().size(), 1);
+  BOOST_CHECK_EQUAL(contentLargeBlock.elements()[0], largeBlock);
+}
+
+BOOST_AUTO_TEST_CASE(MultipleSmall)
+{
+  size_t nBlocks = 100;
+  for (size_t i = 0 ; i < nBlocks ; i ++) {
+    BOOST_CHECK_NO_THROW(context.append(contentBlock));
+  }
+  BOOST_CHECK_NO_THROW(context.end());
+
+  // check data to in-memory storage
+  BOOST_REQUIRE_EQUAL(sendDataHistory.size(), 1);
+  BOOST_CHECK_EQUAL(sendDataHistory[0].dataName, makeSegmentName(0));
+  BOOST_CHECK_EQUAL(sendDataHistory[0].imsFresh, defaultImsFresh);
+  BOOST_CHECK_EQUAL(sendDataHistory[0].isFinalBlock, true);
+
+  auto contentMultiBlocks = concatenateDataContent();
+  BOOST_CHECK_NO_THROW(contentMultiBlocks.parse());
+  BOOST_CHECK_EQUAL(contentMultiBlocks.elements().size(), nBlocks);
+  for (auto&& element : contentMultiBlocks.elements()) {
+    BOOST_CHECK_EQUAL(element, contentBlock);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Respond
+
+BOOST_AUTO_TEST_SUITE(Reject)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  BOOST_CHECK_NO_THROW(context.reject());
+  BOOST_REQUIRE_EQUAL(sendNackHistory.size(), 1);
+  BOOST_CHECK_EQUAL(sendNackHistory[0].getCode(), 400);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Reject
+
+class AbnormalStateTestFixture
+{
+protected:
+  AbnormalStateTestFixture()
+    : context(Interest("/abnormal-state"), bind([]{}), bind([]{}))
+  {
+  }
+
+protected:
+  mgmt::StatusDatasetContext context;
+};
+
+BOOST_FIXTURE_TEST_SUITE(AbnormalState, AbnormalStateTestFixture)
+
+BOOST_AUTO_TEST_CASE(AppendReject)
+{
+  const uint8_t buf[] = {0x82, 0x01, 0x02};
+  BOOST_CHECK_NO_THROW(context.append(Block(buf, sizeof(buf))));
+  BOOST_CHECK_THROW(context.reject(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(AppendEndReject)
+{
+  const uint8_t buf[] = {0x82, 0x01, 0x02};
+  BOOST_CHECK_NO_THROW(context.append(Block(buf, sizeof(buf))));
+  BOOST_CHECK_NO_THROW(context.end());
+  BOOST_CHECK_THROW(context.reject(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(EndAppend)
+{
+  BOOST_CHECK_NO_THROW(context.end());
+  // end, append -> error
+  const uint8_t buf[] = {0x82, 0x01, 0x02};
+  BOOST_CHECK_THROW(context.append(Block(buf, sizeof(buf))), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(EndEnd)
+{
+  BOOST_CHECK_NO_THROW(context.end());
+  BOOST_CHECK_THROW(context.end(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(EndReject)
+{
+  BOOST_CHECK_NO_THROW(context.end());
+  BOOST_CHECK_THROW(context.reject(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(RejectAppend)
+{
+  BOOST_CHECK_NO_THROW(context.reject());
+  const uint8_t buf[] = {0x82, 0x01, 0x02};
+  BOOST_CHECK_THROW(context.append(Block(buf, sizeof(buf))), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(RejectEnd)
+{
+  BOOST_CHECK_NO_THROW(context.reject());
+  BOOST_CHECK_THROW(context.end(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // AbnormalState
+
+BOOST_AUTO_TEST_SUITE_END() // TestStatusDatasetContext
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace mgmt
+} // namespace ndn