tools: nfdc strategy set/unset commands

nfdc set-strategy and nfdc unset-strategy are deprecated.

refs #3865

Change-Id: I934cbfcd567ac7ee33381ae4baf00b668977a0aa
diff --git a/docs/manpages/nfdc-strategy.rst b/docs/manpages/nfdc-strategy.rst
index 7d32d0e..6a1e592 100644
--- a/docs/manpages/nfdc-strategy.rst
+++ b/docs/manpages/nfdc-strategy.rst
@@ -5,8 +5,8 @@
 --------
 | nfdc strategy [list]
 | nfdc strategy show [prefix] <PREFIX>
-| nfdc set-strategy <PREFIX> <STRATEGY>
-| nfdc unset-strategy <PREFIX>
+| nfdc strategy set [prefix] <PREFIX> [strategy] <STRATEGY>
+| nfdc strategy unset [prefix] <PREFIX>
 
 DESCRIPTION
 -----------
@@ -22,21 +22,21 @@
 
 The **nfdc strategy show** command shows the effective strategy choice for a specific name.
 
-The **nfdc set-strategy** command sets the strategy for a name prefix.
+The **nfdc strategy set** command sets the strategy for a name prefix.
+The strategy for ``/'' prefix is the default strategy.
 
-The **nfdc unset-strategy** command clears the strategy choice at a name prefix,
+The **nfdc strategy unset** command clears the strategy choice at a name prefix,
 so that a strategy choice at a shorter prefix or the default strategy will be used.
-It undoes a prior **nfdc set-strategy** command on the same name prefix.
+It undoes a prior **nfdc strategy set** command on the same name prefix.
+It is prohibited to unset the strategy choice for ``/'' prefix because there must always be a
+default strategy.
 
 OPTIONS
 -------
 <PREFIX>
     The name prefix of a strategy choice.
     The strategy choice is effective for all Interests under the name prefix,
-    unless overridden by another strategy choice.
-    Specifying ``ndn:/`` as the prefix in **nfdc set-strategy** changes the default strategy.
-    Specifying ``ndn:/`` as the prefix in **nfdc unset-strategy** is disallowed,
-    because NFD must always have a default strategy.
+    unless overridden by another strategy choice at a longer prefix.
 
 <STRATEGY>
     A name that identifies the forwarding strategy.
@@ -51,6 +51,8 @@
 
 2: Malformed command line
 
+7: Strategy not found (**nfdc strategy set** only)
+
 SEE ALSO
 --------
 nfd(1), nfdc(1)
diff --git a/tests/tools/nfdc/strategy-choice-module.t.cpp b/tests/tools/nfdc/strategy-choice-module.t.cpp
index ff6605a..c14a446 100644
--- a/tests/tools/nfdc/strategy-choice-module.t.cpp
+++ b/tests/tools/nfdc/strategy-choice-module.t.cpp
@@ -126,6 +126,100 @@
 
 BOOST_AUTO_TEST_SUITE_END() // ShowCommand
 
+BOOST_FIXTURE_TEST_SUITE(SetCommand, ExecuteCommandFixture)
+
+BOOST_AUTO_TEST_CASE(Normal)
+{
+  this->processInterest = [this] (const Interest& interest) {
+    ControlParameters req = MOCK_NFD_MGMT_REQUIRE_COMMAND_IS("/localhost/nfd/strategy-choice/set");
+    BOOST_REQUIRE(req.hasName());
+    BOOST_CHECK_EQUAL(req.getName(), "/VBXSJg3m/XYs81ARNhx");
+    BOOST_REQUIRE(req.hasStrategy());
+    BOOST_CHECK_EQUAL(req.getStrategy(), "/strategyP");
+
+    ControlParameters resp;
+    resp.setName("/VBXSJg3m/XYs81ARNhx");
+    resp.setStrategy("/strategyP/%FD%05");
+    this->succeedCommand(interest, resp);
+  };
+
+  this->execute("strategy set /VBXSJg3m/XYs81ARNhx /strategyP");
+  BOOST_CHECK_EQUAL(exitCode, 0);
+  BOOST_CHECK(out.is_equal("strategy-set prefix=/VBXSJg3m/XYs81ARNhx strategy=/strategyP/%FD%05\n"));
+  BOOST_CHECK(err.is_empty());
+}
+
+BOOST_AUTO_TEST_CASE(UnknownStrategy)
+{
+  this->processInterest = [this] (const Interest& interest) {
+    ControlParameters req = MOCK_NFD_MGMT_REQUIRE_COMMAND_IS("/localhost/nfd/strategy-choice/set");
+
+    ControlParameters resp;
+    this->failCommand(interest, 404, "strategy not found");
+  };
+
+  this->execute("strategy set /zezRSP1I /strategyQ");
+  BOOST_CHECK_EQUAL(exitCode, 7);
+  BOOST_CHECK(out.is_empty());
+  BOOST_CHECK(err.is_equal("Unknown strategy: /strategyQ\n"));
+}
+
+BOOST_AUTO_TEST_CASE(ErrorCommand)
+{
+  this->processInterest = nullptr; // no response to command
+
+  this->execute("strategy set /LJdNFfQJ8 /strategyP");
+  BOOST_CHECK_EQUAL(exitCode, 1);
+  BOOST_CHECK(out.is_empty());
+  BOOST_CHECK(err.is_equal("Error 10060 when setting strategy: request timed out\n"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // SetCommand
+
+BOOST_FIXTURE_TEST_SUITE(UnsetCommand, ExecuteCommandFixture)
+
+BOOST_AUTO_TEST_CASE(Normal)
+{
+  this->processInterest = [this] (const Interest& interest) {
+    ControlParameters req = MOCK_NFD_MGMT_REQUIRE_COMMAND_IS("/localhost/nfd/strategy-choice/unset");
+    BOOST_REQUIRE(req.hasName());
+    BOOST_CHECK_EQUAL(req.getName(), "/CFVecryP");
+
+    ControlParameters resp;
+    resp.setName("/CFVecryP");
+    this->succeedCommand(interest, resp);
+  };
+
+  this->execute("strategy unset /CFVecryP");
+  BOOST_CHECK_EQUAL(exitCode, 0);
+  BOOST_CHECK(out.is_equal("strategy-unset prefix=/CFVecryP\n"));
+  BOOST_CHECK(err.is_empty());
+}
+
+BOOST_AUTO_TEST_CASE(CannotUnsetDefault)
+{
+  this->processInterest = [this] (const Interest& interest) {
+    BOOST_ERROR("unexpected command");
+  };
+
+  this->execute("strategy unset /");
+  BOOST_CHECK_EQUAL(exitCode, 2);
+  BOOST_CHECK(out.is_empty());
+  BOOST_CHECK(err.is_equal("Unsetting default strategy is prohibited\n"));
+}
+
+BOOST_AUTO_TEST_CASE(ErrorCommand)
+{
+  this->processInterest = nullptr; // no response to command
+
+  this->execute("strategy unset /GQcEsG96");
+  BOOST_CHECK_EQUAL(exitCode, 1);
+  BOOST_CHECK(out.is_empty());
+  BOOST_CHECK(err.is_equal("Error 10060 when unsetting strategy: request timed out\n"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // UnsetCommand
+
 const std::string STATUS_XML = stripXmlSpaces(R"XML(
   <strategyChoices>
     <strategyChoice>
diff --git a/tools/nfdc/available-commands.cpp b/tools/nfdc/available-commands.cpp
index a1d22cb..0d4847f 100644
--- a/tools/nfdc/available-commands.cpp
+++ b/tools/nfdc/available-commands.cpp
@@ -58,8 +58,8 @@
     {"unregister", "unregister a prefix", "route remove"},
     {"create", "create a face", "face create"},
     {"destroy", "destroy a face", "face destroy"},
-    {"set-strategy", "set strategy choice on namespace", ""},
-    {"unset-strategy", "unset strategy choice on namespace", ""},
+    {"set-strategy", "set strategy choice on namespace", "strategy set"},
+    {"unset-strategy", "unset strategy choice on namespace", "strategy unset"},
     {"add-nexthop", "add FIB nexthop", "route add"},
     {"remove-nexthop", "remove FIB nexthop", "route remove"}
   };
diff --git a/tools/nfdc/strategy-choice-module.cpp b/tools/nfdc/strategy-choice-module.cpp
index e5fe584..9e0031f 100644
--- a/tools/nfdc/strategy-choice-module.cpp
+++ b/tools/nfdc/strategy-choice-module.cpp
@@ -43,6 +43,19 @@
     .setTitle("show strategy choice of an entry")
     .addArg("prefix", ArgValueType::NAME, Required::YES, Positional::YES);
   parser.addCommand(defStrategyShow, &StrategyChoiceModule::show);
+
+  CommandDefinition defStrategySet("strategy", "set");
+  defStrategySet
+    .setTitle("set strategy choice for a name prefix")
+    .addArg("prefix", ArgValueType::NAME, Required::YES, Positional::YES)
+    .addArg("strategy", ArgValueType::NAME, Required::YES, Positional::YES);
+  parser.addCommand(defStrategySet, &StrategyChoiceModule::set);
+
+  CommandDefinition defStrategyUnset("strategy", "unset");
+  defStrategyUnset
+    .setTitle("clear strategy choice at a name prefix")
+    .addArg("prefix", ArgValueType::NAME, Required::YES, Positional::YES);
+  parser.addCommand(defStrategyUnset, &StrategyChoiceModule::unset);
 }
 
 void
@@ -84,6 +97,58 @@
 }
 
 void
+StrategyChoiceModule::set(ExecuteContext& ctx)
+{
+  auto prefix = ctx.args.get<Name>("prefix");
+  auto strategy = ctx.args.get<Name>("strategy");
+
+  ctx.controller.start<ndn::nfd::StrategyChoiceSetCommand>(
+    ControlParameters().setName(prefix).setStrategy(strategy),
+    [&] (const ControlParameters& resp) {
+      ctx.out << "strategy-set ";
+      text::ItemAttributes ia;
+      ctx.out << ia("prefix") << resp.getName()
+              << ia("strategy") << resp.getStrategy() << '\n';
+    },
+    [&] (const ControlResponse& resp) {
+      if (resp.getCode() == 404) {
+        ctx.exitCode = 7;
+        ctx.err << "Unknown strategy: " << strategy << '\n';
+        ///\todo #3887 list available strategies
+        return;
+      }
+      ctx.makeCommandFailureHandler("setting strategy")(resp); // invoke general error handler
+    },
+    ctx.makeCommandOptions());
+
+  ctx.face.processEvents();
+}
+
+void
+StrategyChoiceModule::unset(ExecuteContext& ctx)
+{
+  auto prefix = ctx.args.get<Name>("prefix");
+
+  if (prefix.empty()) {
+    ctx.exitCode = 2;
+    ctx.err << "Unsetting default strategy is prohibited\n";
+    return;
+  }
+
+  ctx.controller.start<ndn::nfd::StrategyChoiceUnsetCommand>(
+    ControlParameters().setName(prefix),
+    [&] (const ControlParameters& resp) {
+      ctx.out << "strategy-unset ";
+      text::ItemAttributes ia;
+      ctx.out << ia("prefix") << resp.getName() << '\n';
+    },
+    ctx.makeCommandFailureHandler("unsetting strategy"),
+    ctx.makeCommandOptions());
+
+  ctx.face.processEvents();
+}
+
+void
 StrategyChoiceModule::fetchStatus(Controller& controller,
                                   const function<void()>& onSuccess,
                                   const Controller::DatasetFailCallback& onFailure,
diff --git a/tools/nfdc/strategy-choice-module.hpp b/tools/nfdc/strategy-choice-module.hpp
index aabc3db..71fa9a2 100644
--- a/tools/nfdc/strategy-choice-module.hpp
+++ b/tools/nfdc/strategy-choice-module.hpp
@@ -56,6 +56,16 @@
   static void
   show(ExecuteContext& ctx);
 
+  /** \brief the 'strategy set' command
+   */
+  static void
+  set(ExecuteContext& ctx);
+
+  /** \brief the 'strategy unset' command
+   */
+  static void
+  unset(ExecuteContext& ctx);
+
   void
   fetchStatus(Controller& controller,
               const function<void()>& onSuccess,