Add move command for moving (leaf)list instances
Change-Id: I0bff25209f74601a450c12a810200b3c124d65f2
diff --git a/tests/command_completion.cpp b/tests/command_completion.cpp
index 8fe463f..45eb998 100644
--- a/tests/command_completion.cpp
+++ b/tests/command_completion.cpp
@@ -21,7 +21,7 @@
int expectedContextLength;
SECTION("no prefix")
{
- expectedCompletions = {"cd", "copy", "create", "delete", "set", "commit", "get", "ls", "discard", "help", "describe"};
+ expectedCompletions = {"cd", "copy", "create", "delete", "set", "commit", "get", "ls", "discard", "help", "describe", "move"};
expectedContextLength = 0;
SECTION("no space") {
input = "";
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index 07320c9..b9870f3 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -432,6 +432,189 @@
REQUIRE(datastore.getItems("/example-schema:leafInt16") == DatastoreAccess::Tree{});
}
+ SECTION("moving leaflist instances")
+ {
+ DatastoreAccess::Tree expected;
+ {
+ // sysrepo does this twice for some reason, it's possibly a bug
+ REQUIRE_CALL(mock, write("/example-schema:protocols", std::nullopt, "http"s)).TIMES(2);
+ REQUIRE_CALL(mock, write("/example-schema:protocols", std::nullopt, "ftp"s));
+ REQUIRE_CALL(mock, write("/example-schema:protocols", std::nullopt, "pop3"s));
+ REQUIRE_CALL(mock, write("/example-schema:protocols", "http"s, "ftp"s));
+ REQUIRE_CALL(mock, write("/example-schema:protocols", "ftp"s, "pop3"s));
+ datastore.createLeafListInstance("/example-schema:protocols[.='http']");
+ datastore.createLeafListInstance("/example-schema:protocols[.='ftp']");
+ datastore.createLeafListInstance("/example-schema:protocols[.='pop3']");
+ datastore.commitChanges();
+ expected = {
+ {"/example-schema:protocols", special_{SpecialValue::LeafList}},
+ {"/example-schema:protocols[.='http']", "http"s},
+ {"/example-schema:protocols[.='ftp']", "ftp"s},
+ {"/example-schema:protocols[.='pop3']", "pop3"s},
+ };
+ REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
+ }
+
+ std::string sourcePath;
+ SECTION("begin")
+ {
+ REQUIRE_CALL(mock, write("/example-schema:protocols", std::nullopt, "pop3"s));
+ sourcePath = "/example-schema:protocols[.='pop3']";
+ datastore.moveItem(sourcePath, yang::move::Absolute::Begin);
+ datastore.commitChanges();
+ expected = {
+ {"/example-schema:protocols", special_{SpecialValue::LeafList}},
+ {"/example-schema:protocols[.='pop3']", "pop3"s},
+ {"/example-schema:protocols[.='http']", "http"s},
+ {"/example-schema:protocols[.='ftp']", "ftp"s},
+ };
+ REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
+ }
+
+ SECTION("end")
+ {
+ sourcePath = "/example-schema:protocols[.='http']";
+ REQUIRE_CALL(mock, write("/example-schema:protocols", "pop3"s, "http"s));
+ datastore.moveItem(sourcePath, yang::move::Absolute::End);
+ datastore.commitChanges();
+ expected = {
+ {"/example-schema:protocols", special_{SpecialValue::LeafList}},
+ {"/example-schema:protocols[.='ftp']", "ftp"s},
+ {"/example-schema:protocols[.='pop3']", "pop3"s},
+ {"/example-schema:protocols[.='http']", "http"s},
+ };
+ REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
+ }
+
+ SECTION("after")
+ {
+ sourcePath = "/example-schema:protocols[.='http']";
+ REQUIRE_CALL(mock, write("/example-schema:protocols", "ftp"s, "http"s));
+ datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::After, {{".", "ftp"s}}});
+ datastore.commitChanges();
+ expected = {
+ {"/example-schema:protocols", special_{SpecialValue::LeafList}},
+ {"/example-schema:protocols[.='ftp']", "ftp"s},
+ {"/example-schema:protocols[.='http']", "http"s},
+ {"/example-schema:protocols[.='pop3']", "pop3"s},
+ };
+ REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
+ }
+
+ SECTION("before")
+ {
+ sourcePath = "/example-schema:protocols[.='http']";
+ REQUIRE_CALL(mock, write("/example-schema:protocols", "ftp"s, "http"s));
+ datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::Before, {{".", "pop3"s}}});
+ datastore.commitChanges();
+ expected = {
+ {"/example-schema:protocols", special_{SpecialValue::LeafList}},
+ {"/example-schema:protocols[.='ftp']", "ftp"s},
+ {"/example-schema:protocols[.='http']", "http"s},
+ {"/example-schema:protocols[.='pop3']", "pop3"s},
+ };
+ REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
+ }
+ }
+
+ SECTION("moving list instances")
+ {
+ DatastoreAccess::Tree expected;
+ {
+ // sysrepo does this twice for some reason, it's possibly a bug
+ REQUIRE_CALL(mock, write("/example-schema:players[name='John']", std::nullopt, ""s)).TIMES(2);
+ REQUIRE_CALL(mock, write("/example-schema:players[name='John']/name", std::nullopt, "John"s));
+ REQUIRE_CALL(mock, write("/example-schema:players[name='Eve']", std::nullopt, ""s));
+ REQUIRE_CALL(mock, write("/example-schema:players[name='Eve']", ""s, ""s));
+ REQUIRE_CALL(mock, write("/example-schema:players[name='Eve']/name", std::nullopt, "Eve"s));
+ REQUIRE_CALL(mock, write("/example-schema:players[name='Adam']", std::nullopt, ""s));
+ REQUIRE_CALL(mock, write("/example-schema:players[name='Adam']/name", std::nullopt, "Adam"s));
+ REQUIRE_CALL(mock, write("/example-schema:players[name='Adam']", ""s, ""s));
+ datastore.createListInstance("/example-schema:players[name='John']");
+ datastore.createListInstance("/example-schema:players[name='Eve']");
+ datastore.createListInstance("/example-schema:players[name='Adam']");
+ datastore.commitChanges();
+ expected = {
+ {"/example-schema:players[name='John']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='John']/name", "John"s},
+ {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='Eve']/name", "Eve"s},
+ {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='Adam']/name", "Adam"s},
+ };
+ REQUIRE(datastore.getItems("/example-schema:players") == expected);
+ }
+
+ std::string sourcePath;
+ SECTION("begin")
+ {
+ sourcePath = "/example-schema:players[name='Adam']";
+ REQUIRE_CALL(mock, write("/example-schema:players[name='Adam']", std::nullopt, ""s));
+ datastore.moveItem(sourcePath, yang::move::Absolute::Begin);
+ datastore.commitChanges();
+ expected = {
+ {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='Adam']/name", "Adam"s},
+ {"/example-schema:players[name='John']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='John']/name", "John"s},
+ {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='Eve']/name", "Eve"s},
+ };
+ REQUIRE(datastore.getItems("/example-schema:players") == expected);
+ }
+
+ SECTION("end")
+ {
+ sourcePath = "/example-schema:players[name='John']";
+ REQUIRE_CALL(mock, write("/example-schema:players[name='John']", ""s, ""s));
+ datastore.moveItem(sourcePath, yang::move::Absolute::End);
+ datastore.commitChanges();
+ expected = {
+ {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='Eve']/name", "Eve"s},
+ {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='Adam']/name", "Adam"s},
+ {"/example-schema:players[name='John']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='John']/name", "John"s},
+ };
+ REQUIRE(datastore.getItems("/example-schema:players") == expected);
+ }
+
+ SECTION("after")
+ {
+ sourcePath = "/example-schema:players[name='John']";
+ REQUIRE_CALL(mock, write("/example-schema:players[name='John']", ""s, ""s));
+ datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::After, {{"name", "Eve"s}}});
+ datastore.commitChanges();
+ expected = {
+ {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='Eve']/name", "Eve"s},
+ {"/example-schema:players[name='John']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='John']/name", "John"s},
+ {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='Adam']/name", "Adam"s},
+ };
+ REQUIRE(datastore.getItems("/example-schema:players") == expected);
+ }
+
+ SECTION("before")
+ {
+ sourcePath = "/example-schema:players[name='John']";
+ REQUIRE_CALL(mock, write("/example-schema:players[name='John']", ""s, ""s));
+ datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::Before, {{"name", "Adam"s}}});
+ datastore.commitChanges();
+ expected = {
+ {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='Eve']/name", "Eve"s},
+ {"/example-schema:players[name='John']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='John']/name", "John"s},
+ {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
+ {"/example-schema:players[name='Adam']/name", "Adam"s},
+ };
+ REQUIRE(datastore.getItems("/example-schema:players") == expected);
+ }
+ }
+
waitForCompletionAndBitMore(seq1);
}
diff --git a/tests/datastoreaccess_mock.hpp b/tests/datastoreaccess_mock.hpp
index ac75875..354dd44 100644
--- a/tests/datastoreaccess_mock.hpp
+++ b/tests/datastoreaccess_mock.hpp
@@ -27,6 +27,7 @@
IMPLEMENT_MOCK1(deleteLeafListInstance);
IMPLEMENT_MOCK1(createListInstance);
IMPLEMENT_MOCK1(deleteListInstance);
+ IMPLEMENT_MOCK2(moveItem);
IMPLEMENT_MOCK2(executeRpc);
// Can't use IMPLEMENT_MOCK for private methods - IMPLEMENT_MOCK needs full visibility of the method
diff --git a/tests/example-schema.yang b/tests/example-schema.yang
index 3f32a87..fdd8daf 100644
--- a/tests/example-schema.yang
+++ b/tests/example-schema.yang
@@ -251,4 +251,17 @@
leaf-list addresses {
type string;
}
+
+ leaf-list protocols {
+ type string;
+ ordered-by user;
+ }
+
+ list players {
+ key "name";
+ ordered-by user;
+ leaf name {
+ type string;
+ }
+ }
}
diff --git a/tests/list_manipulation.cpp b/tests/list_manipulation.cpp
index 35cd019..c646e13 100644
--- a/tests/list_manipulation.cpp
+++ b/tests/list_manipulation.cpp
@@ -26,6 +26,7 @@
schema->addLeaf("/mod:company", "mod:department", schema->validIdentities("other", "deptypes"));
schema->addList("/mod:company", "mod:inventory", {"id"});
schema->addLeaf("/mod:company/mod:inventory", "mod:id", yang::Int32{});
+ schema->addContainer("/", "mod:cont");
Parser parser(schema);
std::string input;
std::ostringstream errorStream;
@@ -83,4 +84,89 @@
REQUIRE(commandGet.type() == typeid(get_));
REQUIRE(boost::get<get_>(commandGet) == expectedGet);
}
+
+ SECTION("moving (leaf)list instances")
+ {
+ move_ expected;
+ SECTION("begin")
+ {
+ SECTION("cwd: /")
+ {
+ input = "move mod:addresses['1.2.3.4'] begin";
+ expected.m_source.m_nodes.push_back(dataNode_{module_{"mod"}, leafListElement_{"addresses", "1.2.3.4"s}});
+ }
+
+ SECTION("cwd: /mod:cont")
+ {
+ parser.changeNode(dataPath_{Scope::Absolute, {dataNode_{module_{"mod"}, container_{"cont"}}}});
+ SECTION("relative")
+ {
+ input = "move ../mod:addresses['1.2.3.4'] begin";
+ expected.m_source.m_nodes.push_back(dataNode_{nodeup_{}});
+ expected.m_source.m_nodes.push_back(dataNode_{module_{"mod"}, leafListElement_{"addresses", "1.2.3.4"s}});
+ }
+
+ SECTION("absolute")
+ {
+ input = "move /mod:addresses['1.2.3.4'] begin";
+ expected.m_source.m_scope = Scope::Absolute;
+ expected.m_source.m_nodes.push_back(dataNode_{module_{"mod"}, leafListElement_{"addresses", "1.2.3.4"s}});
+ }
+ }
+
+ expected.m_destination = yang::move::Absolute::Begin;
+ }
+
+ SECTION("end")
+ {
+ input = "move mod:addresses['1.2.3.4'] end";
+ expected.m_source.m_nodes.push_back(dataNode_{module_{"mod"}, leafListElement_{"addresses", "1.2.3.4"s}});
+ expected.m_destination = yang::move::Absolute::End;
+ }
+
+ SECTION("after")
+ {
+ input = "move mod:addresses['1.2.3.4'] after '0.0.0.0'";
+ expected.m_source.m_nodes.push_back(dataNode_{module_{"mod"}, leafListElement_{"addresses", "1.2.3.4"s}});
+ expected.m_destination = yang::move::Relative {
+ yang::move::Relative::Position::After,
+ {{".", "0.0.0.0"s}}
+ };
+ }
+
+ SECTION("before")
+ {
+ input = "move mod:addresses['1.2.3.4'] before '0.0.0.0'";
+ expected.m_source.m_nodes.push_back(dataNode_{module_{"mod"}, leafListElement_{"addresses", "1.2.3.4"s}});
+ expected.m_destination = yang::move::Relative {
+ yang::move::Relative::Position::Before,
+ {{".", "0.0.0.0"s}}
+ };
+ }
+
+ SECTION("list instance with destination")
+ {
+ input = "move mod:list[number=12] before [number=15]";
+ auto keys = std::map<std::string, leaf_data_>{
+ {"number", int32_t{12}}};
+ expected.m_source.m_nodes.push_back(dataNode_{module_{"mod"}, listElement_("list", keys)});
+ expected.m_destination = yang::move::Relative {
+ yang::move::Relative::Position::Before,
+ ListInstance{{"number", int32_t{15}}}
+ };
+ }
+
+ SECTION("list instance without destination")
+ {
+ input = "move mod:list[number=3] begin";
+ auto keys = std::map<std::string, leaf_data_>{
+ {"number", int32_t{3}}};
+ expected.m_source.m_nodes.push_back(dataNode_{module_{"mod"}, listElement_("list", keys)});
+ expected.m_destination = yang::move::Absolute::Begin;
+ }
+
+ command_ commandMove = parser.parseCommand(input, errorStream);
+ REQUIRE(commandMove.type() == typeid(move_));
+ REQUIRE(boost::get<move_>(commandMove) == expected);
+ }
}
diff --git a/tests/pretty_printers.hpp b/tests/pretty_printers.hpp
index 429a0e9..69c06f5 100644
--- a/tests/pretty_printers.hpp
+++ b/tests/pretty_printers.hpp
@@ -26,7 +26,7 @@
return s;
}
-std::ostream& operator<<(std::ostream& s, const DatastoreAccess::Tree& map)
+std::ostream& operator<<(std::ostream& s, const ListInstance& map)
{
s << std::endl
<< "{";
@@ -37,6 +37,16 @@
return s;
}
+std::ostream& operator<<(std::ostream& s, const DatastoreAccess::Tree& tree)
+{
+ s << "DatastoreAccess::Tree {\n";
+ for (const auto& [xpath, value] : tree) {
+ s << " {" << xpath << ", " << leafDataToString(value) << "}\n";
+ }
+ s << "}\n";
+ return s;
+}
+
std::ostream& operator<<(std::ostream& s, const yang::LeafDataType& type)
{
s << std::endl
@@ -133,3 +143,30 @@
s << "\nls_ {\n " << ls.m_path << "}\n";
return s;
}
+
+std::ostream& operator<<(std::ostream& s, const move_& move)
+{
+ s << "\nmove_ {\n";
+ s << " path: " << move.m_source;
+ s << " mode: ";
+ if (std::holds_alternative<yang::move::Absolute>(move.m_destination)) {
+ if (std::get<yang::move::Absolute>(move.m_destination) == yang::move::Absolute::Begin) {
+ s << "Absolute::Begin";
+ } else {
+ s << "Absolute::End";
+ }
+ } else {
+ const yang::move::Relative& relative = std::get<yang::move::Relative>(move.m_destination);
+ s << "Relative {\n";
+ s << " position: ";
+ if (relative.m_position == yang::move::Relative::Position::After) {
+ s << "Position::After\n";
+ } else {
+ s << "Position::Before\n";
+ }
+ s << " path: ";
+ s << relative.m_path;
+ }
+ s << "\n}\n";
+ return s;
+}