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

Change-Id: I78ea29938259fac288781bed12fb2399ac7eba26
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