blob: 022b115342808697d1912758b02acbf6767f72e1 [file] [log] [blame]
Václav Kubernát73109382018-09-14 19:52:03 +02001/*
2 * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
3 * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
4 *
5 * Written by Václav Kubernát <kubervac@fit.cvut.cz>
6 *
7*/
8
Václav Kubernát26b56082020-02-03 18:28:56 +01009#include "trompeloeil_doctest.hpp"
Jan Kundrát6ee84792020-01-24 01:43:36 +010010#include <sysrepo-cpp/Session.hpp>
Václav Kubernáte7248b22020-06-26 15:38:59 +020011#include "yang_schema.hpp"
12#include "proxy_datastore.hpp"
Václav Kubernát73109382018-09-14 19:52:03 +020013
Václav Kubernátc31bd602019-03-07 11:44:48 +010014#ifdef sysrepo_BACKEND
Václav Kubernát73109382018-09-14 19:52:03 +020015#include "sysrepo_access.hpp"
Jan Kundrát7ec214d2020-06-19 17:05:07 +020016using OnInvalidSchemaPathCreate = DatastoreException;
17using OnInvalidSchemaPathDelete = void;
18using OnInvalidSchemaPathMove = sysrepo::sysrepo_exception;
Václav Kubernát74487df2020-06-04 01:29:28 +020019using OnInvalidRpcPath = sysrepo::sysrepo_exception;
Jan Kundrát7ec214d2020-06-19 17:05:07 +020020using OnKeyNotFound = void;
Václav Kubernáta8789602020-07-20 15:18:19 +020021using OnExec = void;
Václav Kubernátc31bd602019-03-07 11:44:48 +010022#elif defined(netconf_BACKEND)
Jan Kundrát7ec214d2020-06-19 17:05:07 +020023using OnInvalidSchemaPathCreate = std::runtime_error;
24using OnInvalidSchemaPathDelete = std::runtime_error;
25using OnInvalidSchemaPathMove = std::runtime_error;
Václav Kubernát74487df2020-06-04 01:29:28 +020026using OnInvalidRpcPath = std::runtime_error;
Jan Kundrát7ec214d2020-06-19 17:05:07 +020027using OnKeyNotFound = std::runtime_error;
Václav Kubernáta8789602020-07-20 15:18:19 +020028using OnExec = void;
Václav Kubernátc31bd602019-03-07 11:44:48 +010029#include "netconf_access.hpp"
30#include "netopeer_vars.hpp"
Václav Kubernát74487df2020-06-04 01:29:28 +020031#elif defined(yang_BACKEND)
32#include <fstream>
33#include "yang_access.hpp"
34#include "yang_access_test_vars.hpp"
35using OnInvalidSchemaPathCreate = DatastoreException;
36using OnInvalidSchemaPathDelete = DatastoreException;
37using OnInvalidSchemaPathMove = DatastoreException;
38using OnInvalidRpcPath = DatastoreException;
39using OnKeyNotFound = DatastoreException;
Václav Kubernáta8789602020-07-20 15:18:19 +020040using OnExec = std::logic_error;
Václav Kubernátc31bd602019-03-07 11:44:48 +010041#else
42#error "Unknown backend"
43#endif
Jan Kundrátbb525b42020-02-04 11:56:59 +010044#include "pretty_printers.hpp"
Václav Kubernát73109382018-09-14 19:52:03 +020045#include "sysrepo_subscription.hpp"
Václav Kubernát8e121ff2019-10-15 15:47:45 +020046#include "utils.hpp"
Václav Kubernát73109382018-09-14 19:52:03 +020047
Jan Kundrát6ee84792020-01-24 01:43:36 +010048using namespace std::literals::string_literals;
49
Václav Kubernát69aabe92020-01-24 16:53:12 +010050class MockRecorder : public trompeloeil::mock_interface<Recorder> {
Václav Kubernát73109382018-09-14 19:52:03 +020051public:
Václav Kubernát69aabe92020-01-24 16:53:12 +010052 IMPLEMENT_MOCK3(write);
Václav Kubernát73109382018-09-14 19:52:03 +020053};
54
Jan Kundrátbb525b42020-02-04 11:56:59 +010055class MockDataSupplier : public trompeloeil::mock_interface<DataSupplier> {
56public:
57 IMPLEMENT_CONST_MOCK1(get_data);
58};
59
Jan Kundrát7ec214d2020-06-19 17:05:07 +020060namespace {
61template <class ...> constexpr std::false_type always_false [[maybe_unused]] {};
62template <class Exception, typename Callable> void catching(const Callable& what) {
63
64 if constexpr (std::is_same_v<Exception, void>) {
Jan Kundrát3867c9e2020-06-18 20:26:45 +020065 what();
Jan Kundrát7ec214d2020-06-19 17:05:07 +020066 } else if constexpr (std::is_same<Exception, std::runtime_error>()) {
67 // cannot use REQUIRE_THROWS_AS(..., Exception) directly because that one
68 // needs an extra `typename` deep in the bowels of doctest
69 REQUIRE_THROWS_AS(what(), std::runtime_error);
Václav Kubernát74487df2020-06-04 01:29:28 +020070 } else if constexpr (std::is_same<Exception, std::logic_error>()) {
71 REQUIRE_THROWS_AS(what(), std::logic_error);
Jan Kundrát7ec214d2020-06-19 17:05:07 +020072 } else if constexpr (std::is_same<Exception, DatastoreException>()) {
73 REQUIRE_THROWS_AS(what(), DatastoreException);
74 } else if constexpr (std::is_same<Exception, sysrepo::sysrepo_exception>()) {
75 REQUIRE_THROWS_AS(what(), sysrepo::sysrepo_exception);
76 } else {
77 static_assert(always_false<Exception>); // https://stackoverflow.com/a/53945549/2245623
Jan Kundrát3867c9e2020-06-18 20:26:45 +020078 }
79}
Jan Kundrát7ec214d2020-06-19 17:05:07 +020080}
Jan Kundrát3867c9e2020-06-18 20:26:45 +020081
Václav Kubernát74487df2020-06-04 01:29:28 +020082#if defined(yang_BACKEND)
83class TestYangAccess : public YangAccess {
84public:
85 void commitChanges() override
86 {
87 YangAccess::commitChanges();
88 dumpToSysrepo();
89 }
90
91 void copyConfig(const Datastore source, const Datastore destination) override
92 {
93 YangAccess::copyConfig(source, destination);
94 dumpToSysrepo();
95 }
96
97private:
98 void dumpToSysrepo()
99 {
100 {
101 std::ofstream of(testConfigFile);
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200102 of << dump(DataFormat::Xml);
Václav Kubernát74487df2020-06-04 01:29:28 +0200103 }
104 auto command = std::string(sysrepocfgExecutable) + " --import=" + testConfigFile + " --format=xml --datastore=running example-schema";
105 REQUIRE(std::system(command.c_str()) == 0);
106 }
107};
108#endif
109
Václav Kubernát8e121ff2019-10-15 15:47:45 +0200110TEST_CASE("setting/getting values")
Václav Kubernát73109382018-09-14 19:52:03 +0200111{
Václav Kubernát73109382018-09-14 19:52:03 +0200112 trompeloeil::sequence seq1;
113 MockRecorder mock;
Václav Kubernátab612e92019-11-26 19:51:31 +0100114 SysrepoSubscription subscription("example-schema", &mock);
Václav Kubernátc31bd602019-03-07 11:44:48 +0100115
116#ifdef sysrepo_BACKEND
Václav Kubernát715c85c2020-04-14 01:46:08 +0200117 SysrepoAccess datastore("netconf-cli-test", Datastore::Running);
Václav Kubernátc31bd602019-03-07 11:44:48 +0100118#elif defined(netconf_BACKEND)
119 NetconfAccess datastore(NETOPEER_SOCKET_PATH);
Václav Kubernát74487df2020-06-04 01:29:28 +0200120#elif defined(yang_BACKEND)
121 TestYangAccess datastore;
122 datastore.addSchemaDir(schemaDir);
123 datastore.addSchemaFile(exampleSchemaFile);
Václav Kubernátc31bd602019-03-07 11:44:48 +0100124#else
125#error "Unknown backend"
126#endif
Václav Kubernát73109382018-09-14 19:52:03 +0200127
Václav Kubernát69aabe92020-01-24 16:53:12 +0100128
Václav Kubernát134d78f2019-09-03 16:42:29 +0200129 SECTION("set leafInt8 to -128")
Václav Kubernát73109382018-09-14 19:52:03 +0200130 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100131 REQUIRE_CALL(mock, write("/example-schema:leafInt8", std::nullopt, "-128"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +0200132 datastore.setLeaf("/example-schema:leafInt8", int8_t{-128});
133 datastore.commitChanges();
134 }
135
136 SECTION("set leafInt16 to -32768")
137 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100138 REQUIRE_CALL(mock, write("/example-schema:leafInt16", std::nullopt, "-32768"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +0200139 datastore.setLeaf("/example-schema:leafInt16", int16_t{-32768});
140 datastore.commitChanges();
141 }
142
143 SECTION("set leafInt32 to -2147483648")
144 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100145 REQUIRE_CALL(mock, write("/example-schema:leafInt32", std::nullopt, "-2147483648"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +0200146 datastore.setLeaf("/example-schema:leafInt32", int32_t{-2147483648});
147 datastore.commitChanges();
148 }
149
150 SECTION("set leafInt64 to -50000000000")
151 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100152 REQUIRE_CALL(mock, write("/example-schema:leafInt64", std::nullopt, "-50000000000"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +0200153 datastore.setLeaf("/example-schema:leafInt64", int64_t{-50000000000});
154 datastore.commitChanges();
155 }
156
157 SECTION("set leafUInt8 to 255")
158 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100159 REQUIRE_CALL(mock, write("/example-schema:leafUInt8", std::nullopt, "255"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +0200160 datastore.setLeaf("/example-schema:leafUInt8", uint8_t{255});
161 datastore.commitChanges();
162 }
163
164 SECTION("set leafUInt16 to 65535")
165 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100166 REQUIRE_CALL(mock, write("/example-schema:leafUInt16", std::nullopt, "65535"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +0200167 datastore.setLeaf("/example-schema:leafUInt16", uint16_t{65535});
168 datastore.commitChanges();
169 }
170
171 SECTION("set leafUInt32 to 4294967295")
172 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100173 REQUIRE_CALL(mock, write("/example-schema:leafUInt32", std::nullopt, "4294967295"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +0200174 datastore.setLeaf("/example-schema:leafUInt32", uint32_t{4294967295});
175 datastore.commitChanges();
176 }
177
178 SECTION("set leafUInt64 to 50000000000")
179 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100180 REQUIRE_CALL(mock, write("/example-schema:leafUInt64", std::nullopt, "50000000000"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +0200181 datastore.setLeaf("/example-schema:leafUInt64", uint64_t{50000000000});
Václav Kubernát73109382018-09-14 19:52:03 +0200182 datastore.commitChanges();
183 }
184
185 SECTION("set leafEnum to coze")
186 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100187 REQUIRE_CALL(mock, write("/example-schema:leafEnum", std::nullopt, "coze"s));
Václav Kubernát73109382018-09-14 19:52:03 +0200188 datastore.setLeaf("/example-schema:leafEnum", enum_{"coze"});
189 datastore.commitChanges();
190 }
191
192 SECTION("set leafDecimal to 123.544")
193 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100194 REQUIRE_CALL(mock, write("/example-schema:leafDecimal", std::nullopt, "123.544"s));
Václav Kubernát73109382018-09-14 19:52:03 +0200195 datastore.setLeaf("/example-schema:leafDecimal", 123.544);
196 datastore.commitChanges();
197 }
198
Jan Kundrátd2872862020-06-18 21:02:00 +0200199 SECTION("set a string, then delete it")
200 {
201 REQUIRE_CALL(mock, write("/example-schema:leafString", std::nullopt, "blah"s));
202 datastore.setLeaf("/example-schema:leafString", "blah"s);
203 datastore.commitChanges();
204 DatastoreAccess::Tree expected{{"/example-schema:leafString", "blah"s}};
205 REQUIRE(datastore.getItems("/example-schema:leafString") == expected);
206
207 REQUIRE_CALL(mock, write("/example-schema:leafString", "blah"s, std::nullopt));
208 datastore.deleteItem("/example-schema:leafString");
209 datastore.commitChanges();
210 expected.clear();
211 REQUIRE(datastore.getItems("/example-schema:leafString") == expected);
212 }
213
Jan Kundrát7ec214d2020-06-19 17:05:07 +0200214 SECTION("set a non-existing leaf")
215 {
216 catching<OnInvalidSchemaPathCreate>([&]{
217 datastore.setLeaf("/example-schema:non-existing", "what"s);
218 });
219 }
220
Václav Kubernát73109382018-09-14 19:52:03 +0200221 SECTION("create presence container")
222 {
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200223 REQUIRE(datastore.dump(DataFormat::Json).find("example-schema:pContainer") == std::string::npos);
Václav Kubernát69aabe92020-01-24 16:53:12 +0100224 REQUIRE_CALL(mock, write("/example-schema:pContainer", std::nullopt, ""s));
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200225 datastore.createItem("/example-schema:pContainer");
Václav Kubernát73109382018-09-14 19:52:03 +0200226 datastore.commitChanges();
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200227 REQUIRE(datastore.dump(DataFormat::Json).find("example-schema:pContainer") != std::string::npos);
Václav Kubernát73109382018-09-14 19:52:03 +0200228 }
229
Václav Kubernátcc7a93f2020-02-04 11:48:15 +0100230 SECTION("create/delete a list instance")
Václav Kubernát45f4a822019-05-29 21:10:50 +0200231 {
Václav Kubernátcc7a93f2020-02-04 11:48:15 +0100232 {
233 REQUIRE_CALL(mock, write("/example-schema:person[name='Nguyen']", std::nullopt, ""s));
234 REQUIRE_CALL(mock, write("/example-schema:person[name='Nguyen']/name", std::nullopt, "Nguyen"s));
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200235 datastore.createItem("/example-schema:person[name='Nguyen']");
Václav Kubernátcc7a93f2020-02-04 11:48:15 +0100236 datastore.commitChanges();
237 }
238 {
239 REQUIRE_CALL(mock, write("/example-schema:person[name='Nguyen']", ""s, std::nullopt));
240 REQUIRE_CALL(mock, write("/example-schema:person[name='Nguyen']/name", "Nguyen"s, std::nullopt));
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200241 datastore.deleteItem("/example-schema:person[name='Nguyen']");
Václav Kubernátcc7a93f2020-02-04 11:48:15 +0100242 datastore.commitChanges();
243 }
Václav Kubernát45f4a822019-05-29 21:10:50 +0200244 }
245
Jan Kundrát3867c9e2020-06-18 20:26:45 +0200246 SECTION("deleting non-existing list keys")
247 {
Jan Kundrát7ec214d2020-06-19 17:05:07 +0200248 catching<OnKeyNotFound>([&]{
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200249 datastore.deleteItem("/example-schema:person[name='non existing']");
Jan Kundrát3867c9e2020-06-18 20:26:45 +0200250 datastore.commitChanges();
251 });
252 }
253
Jan Kundrát7ec214d2020-06-19 17:05:07 +0200254 SECTION("accessing non-existing schema nodes as a list")
Jan Kundrát3867c9e2020-06-18 20:26:45 +0200255 {
Jan Kundrát7ec214d2020-06-19 17:05:07 +0200256 catching<OnInvalidSchemaPathCreate>([&]{
257 datastore.createItem("/example-schema:non-existing-list[xxx='blah']");
258 datastore.commitChanges();
259 });
260 catching<OnInvalidSchemaPathDelete>([&]{
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200261 datastore.deleteItem("/example-schema:non-existing-list[xxx='non existing']");
Jan Kundrát3867c9e2020-06-18 20:26:45 +0200262 datastore.commitChanges();
263 });
264 }
265
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200266 SECTION("leafref pointing to a key of a list")
267 {
268 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100269 REQUIRE_CALL(mock, write("/example-schema:person[name='Dan']", std::nullopt, ""s));
270 REQUIRE_CALL(mock, write("/example-schema:person[name='Dan']/name", std::nullopt, "Dan"s));
271 REQUIRE_CALL(mock, write("/example-schema:person[name='Elfi']", std::nullopt, ""s));
272 REQUIRE_CALL(mock, write("/example-schema:person[name='Elfi']/name", std::nullopt, "Elfi"s));
273 REQUIRE_CALL(mock, write("/example-schema:person[name='Kolafa']", std::nullopt, ""s));
274 REQUIRE_CALL(mock, write("/example-schema:person[name='Kolafa']/name", std::nullopt, "Kolafa"s));
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200275 datastore.createItem("/example-schema:person[name='Dan']");
276 datastore.createItem("/example-schema:person[name='Elfi']");
277 datastore.createItem("/example-schema:person[name='Kolafa']");
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200278 datastore.commitChanges();
279 }
280
Václav Kubernát7b191ce2020-06-30 16:22:53 +0200281 std::string value;
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200282 SECTION("Dan")
283 {
Václav Kubernát7b191ce2020-06-30 16:22:53 +0200284 value = "Dan";
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200285 }
286
287 SECTION("Elfi")
288 {
Václav Kubernát7b191ce2020-06-30 16:22:53 +0200289 value = "Elfi";
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200290 }
291
292 SECTION("Kolafa")
293 {
Václav Kubernát7b191ce2020-06-30 16:22:53 +0200294 value = "Kolafa";
295 }
296
297 datastore.setLeaf("/example-schema:bossPerson", value);
298 {
299 REQUIRE_CALL(mock, write("/example-schema:bossPerson", std::nullopt, value));
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200300 datastore.commitChanges();
301 }
Václav Kubernát7b191ce2020-06-30 16:22:53 +0200302 REQUIRE(datastore.getItems("/example-schema:bossPerson") == DatastoreAccess::Tree{{"/example-schema:bossPerson", value}});
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200303 }
Václav Kubernát8e121ff2019-10-15 15:47:45 +0200304 SECTION("bool values get correctly represented as bools")
305 {
306 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100307 REQUIRE_CALL(mock, write("/example-schema:down", std::nullopt, "true"s));
Václav Kubernát8e121ff2019-10-15 15:47:45 +0200308 datastore.setLeaf("/example-schema:down", bool{true});
309 datastore.commitChanges();
310 }
311
Jan Kundrátb331b552020-01-23 15:25:29 +0100312 DatastoreAccess::Tree expected{{"/example-schema:down", bool{true}}};
Václav Kubernát8e121ff2019-10-15 15:47:45 +0200313 REQUIRE(datastore.getItems("/example-schema:down") == expected);
314 }
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200315
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200316 SECTION("getting items from the whole module")
317 {
318 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100319 REQUIRE_CALL(mock, write("/example-schema:up", std::nullopt, "true"s));
320 REQUIRE_CALL(mock, write("/example-schema:down", std::nullopt, "false"s));
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200321 datastore.setLeaf("/example-schema:up", bool{true});
322 datastore.setLeaf("/example-schema:down", bool{false});
323 datastore.commitChanges();
324 }
325
Václav Kubernátcf9224f2020-06-02 09:55:29 +0200326 DatastoreAccess::Tree expected{
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200327 // Sysrepo always returns containers when getting values, but
328 // libnetconf does not. This is fine by the YANG standard:
329 // https://tools.ietf.org/html/rfc7950#section-7.5.7 Furthermore,
330 // NetconfAccess implementation actually only iterates over leafs,
331 // so even if libnetconf did include containers, they wouldn't get
332 // shown here anyway. With sysrepo2, this won't be necessary,
333 // because it'll use the same data structure as libnetconf, so the
334 // results will be consistent.
335#ifdef sysrepo_BACKEND
Václav Kubernátda8e4b92020-02-04 11:56:30 +0100336 {"/example-schema:inventory", special_{SpecialValue::Container}},
Václav Kubernátcf9224f2020-06-02 09:55:29 +0200337 {"/example-schema:lol", special_{SpecialValue::Container}},
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200338#endif
Václav Kubernátcf9224f2020-06-02 09:55:29 +0200339 {"/example-schema:up", bool{true}},
340 {"/example-schema:down", bool{false}}};
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200341 REQUIRE(datastore.getItems("/example-schema:*") == expected);
342 }
343
Václav Kubernát152ce222019-12-19 12:23:32 +0100344 SECTION("getItems returns correct datatypes")
345 {
346 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100347 REQUIRE_CALL(mock, write("/example-schema:leafEnum", std::nullopt, "lol"s));
Václav Kubernát152ce222019-12-19 12:23:32 +0100348 datastore.setLeaf("/example-schema:leafEnum", enum_{"lol"});
349 datastore.commitChanges();
350 }
Jan Kundrátb331b552020-01-23 15:25:29 +0100351 DatastoreAccess::Tree expected{{"/example-schema:leafEnum", enum_{"lol"}}};
Václav Kubernát152ce222019-12-19 12:23:32 +0100352
353 REQUIRE(datastore.getItems("/example-schema:leafEnum") == expected);
354 }
355
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100356 SECTION("getItems on a list")
357 {
358 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100359 REQUIRE_CALL(mock, write("/example-schema:person[name='Jan']", std::nullopt, ""s));
360 REQUIRE_CALL(mock, write("/example-schema:person[name='Jan']/name", std::nullopt, "Jan"s));
361 REQUIRE_CALL(mock, write("/example-schema:person[name='Michal']", std::nullopt, ""s));
362 REQUIRE_CALL(mock, write("/example-schema:person[name='Michal']/name", std::nullopt, "Michal"s));
363 REQUIRE_CALL(mock, write("/example-schema:person[name='Petr']", std::nullopt, ""s));
364 REQUIRE_CALL(mock, write("/example-schema:person[name='Petr']/name", std::nullopt, "Petr"s));
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200365 datastore.createItem("/example-schema:person[name='Jan']");
366 datastore.createItem("/example-schema:person[name='Michal']");
367 datastore.createItem("/example-schema:person[name='Petr']");
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100368 datastore.commitChanges();
369 }
Jan Kundrátb331b552020-01-23 15:25:29 +0100370 DatastoreAccess::Tree expected{
Václav Kubernát144729d2020-01-08 15:20:35 +0100371 {"/example-schema:person[name='Jan']", special_{SpecialValue::List}},
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100372 {"/example-schema:person[name='Jan']/name", std::string{"Jan"}},
Václav Kubernát144729d2020-01-08 15:20:35 +0100373 {"/example-schema:person[name='Michal']", special_{SpecialValue::List}},
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100374 {"/example-schema:person[name='Michal']/name", std::string{"Michal"}},
Václav Kubernát144729d2020-01-08 15:20:35 +0100375 {"/example-schema:person[name='Petr']", special_{SpecialValue::List}},
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100376 {"/example-schema:person[name='Petr']/name", std::string{"Petr"}}
377 };
378
379 REQUIRE(datastore.getItems("/example-schema:person") == expected);
380 }
381
Václav Kubernát69aabe92020-01-24 16:53:12 +0100382 SECTION("presence containers")
383 {
384 DatastoreAccess::Tree expected;
385 // Make sure it's not there before we create it
386 REQUIRE(datastore.getItems("/example-schema:pContainer") == expected);
387
388 {
389 REQUIRE_CALL(mock, write("/example-schema:pContainer", std::nullopt, ""s));
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200390 datastore.createItem("/example-schema:pContainer");
Václav Kubernát69aabe92020-01-24 16:53:12 +0100391 datastore.commitChanges();
392 }
393 expected = {
394 {"/example-schema:pContainer", special_{SpecialValue::PresenceContainer}}
395 };
396 REQUIRE(datastore.getItems("/example-schema:pContainer") == expected);
397
398 // Make sure it's not there after we delete it
399 {
400 REQUIRE_CALL(mock, write("/example-schema:pContainer", ""s, std::nullopt));
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200401 datastore.deleteItem("/example-schema:pContainer");
Václav Kubernát69aabe92020-01-24 16:53:12 +0100402 datastore.commitChanges();
403 }
404 expected = {};
405 REQUIRE(datastore.getItems("/example-schema:pContainer") == expected);
Václav Kubernátda8e4b92020-02-04 11:56:30 +0100406 }
Václav Kubernát69aabe92020-01-24 16:53:12 +0100407
Jan Kundrát7ec214d2020-06-19 17:05:07 +0200408 SECTION("creating a non-existing schema node as a container")
409 {
410 catching<OnInvalidSchemaPathCreate>([&]{
411 datastore.createItem("/example-schema:non-existing-presence-container");
412 datastore.commitChanges();
413 });
414 }
415
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200416 SECTION("deleting a non-existing schema node as a container or leaf")
Jan Kundrát3867c9e2020-06-18 20:26:45 +0200417 {
Jan Kundrát7ec214d2020-06-19 17:05:07 +0200418 catching<OnInvalidSchemaPathDelete>([&]{
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200419 datastore.deleteItem("/example-schema:non-existing-presence-container");
Jan Kundrát3867c9e2020-06-18 20:26:45 +0200420 datastore.commitChanges();
421 });
422 }
423
Václav Kubernátda8e4b92020-02-04 11:56:30 +0100424 SECTION("nested presence container")
425 {
426 DatastoreAccess::Tree expected;
427 // Make sure it's not there before we create it
428 REQUIRE(datastore.getItems("/example-schema:inventory/stuff") == expected);
429 {
430 REQUIRE_CALL(mock, write("/example-schema:inventory", std::nullopt, ""s));
431 REQUIRE_CALL(mock, write("/example-schema:inventory/stuff", std::nullopt, ""s));
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200432 datastore.createItem("/example-schema:inventory/stuff");
Václav Kubernátda8e4b92020-02-04 11:56:30 +0100433 datastore.commitChanges();
434 }
435 expected = {
436 {"/example-schema:inventory/stuff", special_{SpecialValue::PresenceContainer}}
437 };
438 REQUIRE(datastore.getItems("/example-schema:inventory/stuff") == expected);
439 {
440 REQUIRE_CALL(mock, write("/example-schema:inventory", ""s, std::nullopt));
441 REQUIRE_CALL(mock, write("/example-schema:inventory/stuff", ""s, std::nullopt));
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200442 datastore.deleteItem("/example-schema:inventory/stuff");
Václav Kubernátda8e4b92020-02-04 11:56:30 +0100443 datastore.commitChanges();
444 }
445 expected = {};
446 REQUIRE(datastore.getItems("/example-schema:inventory/stuff") == expected);
Václav Kubernát69aabe92020-01-24 16:53:12 +0100447 }
448
Jan Kundrátbd3169c2020-02-03 19:31:34 +0100449 SECTION("floats")
450 {
451 datastore.setLeaf("/example-schema:leafDecimal", 123.4);
452 REQUIRE_CALL(mock, write("/example-schema:leafDecimal", std::nullopt, "123.4"s));
453 datastore.commitChanges();
454 DatastoreAccess::Tree expected {
455 {"/example-schema:leafDecimal", 123.4},
456 };
457 REQUIRE(datastore.getItems("/example-schema:leafDecimal") == expected);
458 }
459
Jan Kundrát3ff50122020-05-07 00:37:50 +0200460 SECTION("unions")
461 {
462 datastore.setLeaf("/example-schema:unionIntString", int32_t{10});
463 REQUIRE_CALL(mock, write("/example-schema:unionIntString", std::nullopt, "10"s));
464 datastore.commitChanges();
465 DatastoreAccess::Tree expected {
466 {"/example-schema:unionIntString", int32_t{10}},
467 };
468 REQUIRE(datastore.getItems("/example-schema:unionIntString") == expected);
469 }
470
Jan Kundrát0d8abd12020-05-07 02:00:14 +0200471 SECTION("identityref") {
472 datastore.setLeaf("/example-schema:beast", identityRef_{"example-schema", "Mammal"});
473 REQUIRE_CALL(mock, write("/example-schema:beast", std::nullopt, "example-schema:Mammal"s));
474 datastore.commitChanges();
475 DatastoreAccess::Tree expected {
476 {"/example-schema:beast", identityRef_{"example-schema", "Mammal"}},
477 };
478 REQUIRE(datastore.getItems("/example-schema:beast") == expected);
479
480 datastore.setLeaf("/example-schema:beast", identityRef_{"Whale"});
481 REQUIRE_CALL(mock, write("/example-schema:beast", "example-schema:Mammal", "example-schema:Whale"s));
482 datastore.commitChanges();
483 expected = {
484 {"/example-schema:beast", identityRef_{"example-schema", "Whale"}},
485 };
486 REQUIRE(datastore.getItems("/example-schema:beast") == expected);
487 }
488
Jan Kundrát68985442020-05-07 02:15:34 +0200489 SECTION("binary")
490 {
491 datastore.setLeaf("/example-schema:blob", binary_{"cHduegByIQ=="s});
492 REQUIRE_CALL(mock, write("/example-schema:blob", std::nullopt, "cHduegByIQ=="s));
493 datastore.commitChanges();
494 DatastoreAccess::Tree expected {
495 {"/example-schema:blob", binary_{"cHduegByIQ=="s}},
496 };
497 REQUIRE(datastore.getItems("/example-schema:blob") == expected);
498 }
499
Jan Kundrát379bb572020-05-07 03:23:13 +0200500 SECTION("empty")
501 {
502 datastore.setLeaf("/example-schema:dummy", empty_{});
503 REQUIRE_CALL(mock, write("/example-schema:dummy", std::nullopt, ""s));
504 datastore.commitChanges();
505 DatastoreAccess::Tree expected {
506 {"/example-schema:dummy", empty_{}},
507 };
508 REQUIRE(datastore.getItems("/example-schema:dummy") == expected);
509 }
510
Václav Kubernát19097f32020-10-05 10:08:29 +0200511 SECTION("bits")
512 {
513 datastore.setLeaf("/example-schema:flags", bits_{{"sign", "carry"}});
514 REQUIRE_CALL(mock, write("/example-schema:flags", std::nullopt, "carry sign"s));
515 datastore.commitChanges();
516 DatastoreAccess::Tree expected {
517 {"/example-schema:flags", bits_{{"carry", "sign"}}},
518 };
519 REQUIRE(datastore.getItems("/example-schema:flags") == expected);
520 }
521
Václav Kubernát74487df2020-06-04 01:29:28 +0200522#if not defined(yang_BACKEND)
Jan Kundrátbb525b42020-02-04 11:56:59 +0100523 SECTION("operational data")
524 {
525 MockDataSupplier mockOpsData;
526 OperationalDataSubscription opsDataSub("/example-schema:temperature", mockOpsData);
527 DatastoreAccess::Tree expected;
528 std::string xpath;
529 SECTION("temperature")
530 {
531 expected = {{"/example-schema:temperature", int32_t{22}}};
532 xpath = "/example-schema:temperature";
533 }
534
535 REQUIRE_CALL(mockOpsData, get_data(xpath)).RETURN(expected);
536 REQUIRE(datastore.getItems(xpath) == expected);
537 }
Václav Kubernát74487df2020-06-04 01:29:28 +0200538#endif
Jan Kundrátbb525b42020-02-04 11:56:59 +0100539
Václav Kubernát5b8a8f32020-05-20 00:57:22 +0200540 SECTION("leaf list")
541 {
542 DatastoreAccess::Tree expected;
543 REQUIRE_CALL(mock, write("/example-schema:addresses", std::nullopt, "0.0.0.0"s));
544 REQUIRE_CALL(mock, write("/example-schema:addresses", std::nullopt, "127.0.0.1"s));
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200545 datastore.createItem("/example-schema:addresses[.='0.0.0.0']");
546 datastore.createItem("/example-schema:addresses[.='127.0.0.1']");
Václav Kubernát5b8a8f32020-05-20 00:57:22 +0200547 datastore.commitChanges();
548 expected = {
549 {"/example-schema:addresses", special_{SpecialValue::LeafList}},
550 {"/example-schema:addresses[.='0.0.0.0']", "0.0.0.0"s},
551 {"/example-schema:addresses[.='127.0.0.1']", "127.0.0.1"s},
552 };
553 REQUIRE(datastore.getItems("/example-schema:addresses") == expected);
554
555 REQUIRE_CALL(mock, write("/example-schema:addresses", "0.0.0.0"s, std::nullopt));
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200556 datastore.deleteItem("/example-schema:addresses[.='0.0.0.0']");
Václav Kubernát5b8a8f32020-05-20 00:57:22 +0200557 datastore.commitChanges();
558 expected = {
559 {"/example-schema:addresses", special_{SpecialValue::LeafList}},
560 {"/example-schema:addresses[.='127.0.0.1']", "127.0.0.1"s},
561 };
562 REQUIRE(datastore.getItems("/example-schema:addresses") == expected);
563
564 REQUIRE_CALL(mock, write("/example-schema:addresses", "127.0.0.1"s, std::nullopt));
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200565 datastore.deleteItem("/example-schema:addresses[.='127.0.0.1']");
Václav Kubernát5b8a8f32020-05-20 00:57:22 +0200566 datastore.commitChanges();
567 expected = {};
568 REQUIRE(datastore.getItems("/example-schema:addresses") == expected);
569 }
Jan Kundrátbb525b42020-02-04 11:56:59 +0100570
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200571 SECTION("deleting a non-existing leaf-list")
Jan Kundrát3867c9e2020-06-18 20:26:45 +0200572 {
Jan Kundrát7ec214d2020-06-19 17:05:07 +0200573 catching<OnKeyNotFound>([&]{
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200574 datastore.deleteItem("/example-schema:addresses[.='non-existing']");
Jan Kundrát3867c9e2020-06-18 20:26:45 +0200575 datastore.commitChanges();
576 });
577 }
578
Jan Kundrát7ec214d2020-06-19 17:05:07 +0200579 SECTION("accessing a non-existing schema node as a leaf-list")
Jan Kundrát3867c9e2020-06-18 20:26:45 +0200580 {
Jan Kundrát7ec214d2020-06-19 17:05:07 +0200581 catching<OnInvalidSchemaPathCreate>([&]{
582 datastore.createItem("/example-schema:non-existing[.='non-existing']");
583 datastore.commitChanges();
584 });
585
586 catching<OnInvalidSchemaPathDelete>([&]{
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200587 datastore.deleteItem("/example-schema:non-existing[.='non-existing']");
Jan Kundrát3867c9e2020-06-18 20:26:45 +0200588 datastore.commitChanges();
589 });
590 }
591
Václav Kubernát7160a132020-04-03 02:11:01 +0200592 SECTION("copying data from startup refreshes the data")
593 {
594 {
595 REQUIRE(datastore.getItems("/example-schema:leafInt16") == DatastoreAccess::Tree{});
596 REQUIRE_CALL(mock, write("/example-schema:leafInt16", std::nullopt, "123"s));
597 datastore.setLeaf("/example-schema:leafInt16", int16_t{123});
598 datastore.commitChanges();
599 }
600 REQUIRE(datastore.getItems("/example-schema:leafInt16") == DatastoreAccess::Tree{{"/example-schema:leafInt16", int16_t{123}}});
601 REQUIRE_CALL(mock, write("/example-schema:leafInt16", "123"s, std::nullopt));
602 datastore.copyConfig(Datastore::Startup, Datastore::Running);
603 REQUIRE(datastore.getItems("/example-schema:leafInt16") == DatastoreAccess::Tree{});
604 }
605
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200606 SECTION("moving leaflist instances")
607 {
608 DatastoreAccess::Tree expected;
609 {
610 // sysrepo does this twice for some reason, it's possibly a bug
611 REQUIRE_CALL(mock, write("/example-schema:protocols", std::nullopt, "http"s)).TIMES(2);
612 REQUIRE_CALL(mock, write("/example-schema:protocols", std::nullopt, "ftp"s));
613 REQUIRE_CALL(mock, write("/example-schema:protocols", std::nullopt, "pop3"s));
614 REQUIRE_CALL(mock, write("/example-schema:protocols", "http"s, "ftp"s));
615 REQUIRE_CALL(mock, write("/example-schema:protocols", "ftp"s, "pop3"s));
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200616 datastore.createItem("/example-schema:protocols[.='http']");
617 datastore.createItem("/example-schema:protocols[.='ftp']");
618 datastore.createItem("/example-schema:protocols[.='pop3']");
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200619 datastore.commitChanges();
620 expected = {
621 {"/example-schema:protocols", special_{SpecialValue::LeafList}},
622 {"/example-schema:protocols[.='http']", "http"s},
623 {"/example-schema:protocols[.='ftp']", "ftp"s},
624 {"/example-schema:protocols[.='pop3']", "pop3"s},
625 };
626 REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
627 }
628
629 std::string sourcePath;
630 SECTION("begin")
631 {
632 REQUIRE_CALL(mock, write("/example-schema:protocols", std::nullopt, "pop3"s));
633 sourcePath = "/example-schema:protocols[.='pop3']";
634 datastore.moveItem(sourcePath, yang::move::Absolute::Begin);
635 datastore.commitChanges();
636 expected = {
637 {"/example-schema:protocols", special_{SpecialValue::LeafList}},
638 {"/example-schema:protocols[.='pop3']", "pop3"s},
639 {"/example-schema:protocols[.='http']", "http"s},
640 {"/example-schema:protocols[.='ftp']", "ftp"s},
641 };
642 REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
643 }
644
645 SECTION("end")
646 {
647 sourcePath = "/example-schema:protocols[.='http']";
648 REQUIRE_CALL(mock, write("/example-schema:protocols", "pop3"s, "http"s));
649 datastore.moveItem(sourcePath, yang::move::Absolute::End);
650 datastore.commitChanges();
651 expected = {
652 {"/example-schema:protocols", special_{SpecialValue::LeafList}},
653 {"/example-schema:protocols[.='ftp']", "ftp"s},
654 {"/example-schema:protocols[.='pop3']", "pop3"s},
655 {"/example-schema:protocols[.='http']", "http"s},
656 };
657 REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
658 }
659
660 SECTION("after")
661 {
662 sourcePath = "/example-schema:protocols[.='http']";
663 REQUIRE_CALL(mock, write("/example-schema:protocols", "ftp"s, "http"s));
664 datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::After, {{".", "ftp"s}}});
665 datastore.commitChanges();
666 expected = {
667 {"/example-schema:protocols", special_{SpecialValue::LeafList}},
668 {"/example-schema:protocols[.='ftp']", "ftp"s},
669 {"/example-schema:protocols[.='http']", "http"s},
670 {"/example-schema:protocols[.='pop3']", "pop3"s},
671 };
672 REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
673 }
674
675 SECTION("before")
676 {
677 sourcePath = "/example-schema:protocols[.='http']";
678 REQUIRE_CALL(mock, write("/example-schema:protocols", "ftp"s, "http"s));
679 datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::Before, {{".", "pop3"s}}});
680 datastore.commitChanges();
681 expected = {
682 {"/example-schema:protocols", special_{SpecialValue::LeafList}},
683 {"/example-schema:protocols[.='ftp']", "ftp"s},
684 {"/example-schema:protocols[.='http']", "http"s},
685 {"/example-schema:protocols[.='pop3']", "pop3"s},
686 };
687 REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
688 }
689 }
690
Jan Kundrát7ec214d2020-06-19 17:05:07 +0200691 SECTION("moving non-existing schema nodes")
692 {
693 catching<OnInvalidSchemaPathMove>([&]{
694 datastore.moveItem("/example-schema:non-existing", yang::move::Absolute::Begin);
695 datastore.commitChanges();
696 });
697 }
698
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200699 SECTION("moving list instances")
700 {
701 DatastoreAccess::Tree expected;
702 {
703 // sysrepo does this twice for some reason, it's possibly a bug
704 REQUIRE_CALL(mock, write("/example-schema:players[name='John']", std::nullopt, ""s)).TIMES(2);
705 REQUIRE_CALL(mock, write("/example-schema:players[name='John']/name", std::nullopt, "John"s));
706 REQUIRE_CALL(mock, write("/example-schema:players[name='Eve']", std::nullopt, ""s));
707 REQUIRE_CALL(mock, write("/example-schema:players[name='Eve']", ""s, ""s));
708 REQUIRE_CALL(mock, write("/example-schema:players[name='Eve']/name", std::nullopt, "Eve"s));
709 REQUIRE_CALL(mock, write("/example-schema:players[name='Adam']", std::nullopt, ""s));
710 REQUIRE_CALL(mock, write("/example-schema:players[name='Adam']/name", std::nullopt, "Adam"s));
711 REQUIRE_CALL(mock, write("/example-schema:players[name='Adam']", ""s, ""s));
Jan Kundrátcbf288b2020-06-18 20:44:39 +0200712 datastore.createItem("/example-schema:players[name='John']");
713 datastore.createItem("/example-schema:players[name='Eve']");
714 datastore.createItem("/example-schema:players[name='Adam']");
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200715 datastore.commitChanges();
716 expected = {
717 {"/example-schema:players[name='John']", special_{SpecialValue::List}},
718 {"/example-schema:players[name='John']/name", "John"s},
719 {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
720 {"/example-schema:players[name='Eve']/name", "Eve"s},
721 {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
722 {"/example-schema:players[name='Adam']/name", "Adam"s},
723 };
724 REQUIRE(datastore.getItems("/example-schema:players") == expected);
725 }
726
727 std::string sourcePath;
728 SECTION("begin")
729 {
730 sourcePath = "/example-schema:players[name='Adam']";
731 REQUIRE_CALL(mock, write("/example-schema:players[name='Adam']", std::nullopt, ""s));
732 datastore.moveItem(sourcePath, yang::move::Absolute::Begin);
733 datastore.commitChanges();
734 expected = {
735 {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
736 {"/example-schema:players[name='Adam']/name", "Adam"s},
737 {"/example-schema:players[name='John']", special_{SpecialValue::List}},
738 {"/example-schema:players[name='John']/name", "John"s},
739 {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
740 {"/example-schema:players[name='Eve']/name", "Eve"s},
741 };
742 REQUIRE(datastore.getItems("/example-schema:players") == expected);
743 }
744
745 SECTION("end")
746 {
747 sourcePath = "/example-schema:players[name='John']";
748 REQUIRE_CALL(mock, write("/example-schema:players[name='John']", ""s, ""s));
749 datastore.moveItem(sourcePath, yang::move::Absolute::End);
750 datastore.commitChanges();
751 expected = {
752 {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
753 {"/example-schema:players[name='Eve']/name", "Eve"s},
754 {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
755 {"/example-schema:players[name='Adam']/name", "Adam"s},
756 {"/example-schema:players[name='John']", special_{SpecialValue::List}},
757 {"/example-schema:players[name='John']/name", "John"s},
758 };
759 REQUIRE(datastore.getItems("/example-schema:players") == expected);
760 }
761
762 SECTION("after")
763 {
764 sourcePath = "/example-schema:players[name='John']";
765 REQUIRE_CALL(mock, write("/example-schema:players[name='John']", ""s, ""s));
766 datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::After, {{"name", "Eve"s}}});
767 datastore.commitChanges();
768 expected = {
769 {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
770 {"/example-schema:players[name='Eve']/name", "Eve"s},
771 {"/example-schema:players[name='John']", special_{SpecialValue::List}},
772 {"/example-schema:players[name='John']/name", "John"s},
773 {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
774 {"/example-schema:players[name='Adam']/name", "Adam"s},
775 };
776 REQUIRE(datastore.getItems("/example-schema:players") == expected);
777 }
778
779 SECTION("before")
780 {
781 sourcePath = "/example-schema:players[name='John']";
782 REQUIRE_CALL(mock, write("/example-schema:players[name='John']", ""s, ""s));
783 datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::Before, {{"name", "Adam"s}}});
784 datastore.commitChanges();
785 expected = {
786 {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
787 {"/example-schema:players[name='Eve']/name", "Eve"s},
788 {"/example-schema:players[name='John']", special_{SpecialValue::List}},
789 {"/example-schema:players[name='John']/name", "John"s},
790 {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
791 {"/example-schema:players[name='Adam']/name", "Adam"s},
792 };
793 REQUIRE(datastore.getItems("/example-schema:players") == expected);
794 }
795 }
796
Václav Kubernátfa96ac22020-06-18 17:03:52 +0200797 SECTION("getting /")
798 {
799 {
800 REQUIRE_CALL(mock, write("/example-schema:leafInt32", std::nullopt, "64"s));
801 datastore.setLeaf("/example-schema:leafInt32", 64);
802 datastore.commitChanges();
803 }
804
805 DatastoreAccess::Tree expected{
806 // Sysrepo always returns containers when getting values, but
807 // libnetconf does not. This is fine by the YANG standard:
808 // https://tools.ietf.org/html/rfc7950#section-7.5.7 Furthermore,
809 // NetconfAccess implementation actually only iterates over leafs,
810 // so even if libnetconf did include containers, they wouldn't get
811 // shown here anyway. With sysrepo2, this won't be necessary,
812 // because it'll use the same data structure as libnetconf, so the
813 // results will be consistent.
814#ifdef sysrepo_BACKEND
815 {"/example-schema:inventory", special_{SpecialValue::Container}},
816 {"/example-schema:lol", special_{SpecialValue::Container}},
817#endif
818 {"/example-schema:leafInt32", 64}};
819 auto items = datastore.getItems("/");
820 // This tests if we at least get the data WE added.
821 REQUIRE(std::all_of(expected.begin(), expected.end(), [items] (const auto& item) { return std::find(items.begin(), items.end(), item) != items.end(); }));
822 }
823
Václav Kubernát36986c52020-06-25 10:30:05 +0200824 SECTION("setting and removing without commit")
825 {
826 datastore.setLeaf("/example-schema:leafInt32", 64);
827 datastore.deleteItem("/example-schema:leafInt32");
828 }
829
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200830 SECTION("two key lists")
831 {
832 REQUIRE_CALL(mock, write("/example-schema:point[x='12'][y='10']", std::nullopt, ""s));
833 REQUIRE_CALL(mock, write("/example-schema:point[x='12'][y='10']/x", std::nullopt, "12"s));
834 REQUIRE_CALL(mock, write("/example-schema:point[x='12'][y='10']/y", std::nullopt, "10"s));
835 datastore.createItem("/example-schema:point[x='12'][y='10']");
836 datastore.commitChanges();
837 REQUIRE(datastore.dump(DataFormat::Json).find("example-schema:point") != std::string::npos);
838 }
839
Václav Kubernát73109382018-09-14 19:52:03 +0200840 waitForCompletionAndBitMore(seq1);
841}
Jan Kundrát6ee84792020-01-24 01:43:36 +0100842
843class RpcCb: public sysrepo::Callback {
Václav Kubernáta8789602020-07-20 15:18:19 +0200844 int action(const char *xpath, [[maybe_unused]] const ::sysrepo::S_Vals input, ::sysrepo::S_Vals_Holder output, void* priv) override
845 {
846 auto schema = reinterpret_cast<YangSchema*>(priv);
847 if (schema->dataPathToSchemaPath(xpath) == "/example-schema:ports/shutdown") {
848 auto buf = output->allocate(1);
849 buf->val(0)->set(joinPaths(xpath, "success").c_str(), true);
850 return SR_ERR_OK;
851 }
852 throw std::runtime_error("unrecognized RPC");
853 }
854
Jan Kundrát6ee84792020-01-24 01:43:36 +0100855 int rpc(const char *xpath, const ::sysrepo::S_Vals input, ::sysrepo::S_Vals_Holder output, void *) override
856 {
857 const auto nukes = "/example-schema:launch-nukes"s;
Václav Kubernáte7248b22020-06-26 15:38:59 +0200858 if (xpath == "/example-schema:noop"s || xpath == "/example-schema:fire"s) {
Jan Kundrát6ee84792020-01-24 01:43:36 +0100859 return SR_ERR_OK;
Václav Kubernáte7248b22020-06-26 15:38:59 +0200860 }
861
862 if (xpath == nukes) {
Jan Kundrát6ee84792020-01-24 01:43:36 +0100863 uint64_t kilotons = 0;
864 bool hasCities = false;
865 for (size_t i = 0; i < input->val_cnt(); ++i) {
866 const auto& val = input->val(i);
Václav Kubernátf4b6a932020-07-09 10:34:19 +0200867 if (val->xpath() == nukes + "/payload") {
868 continue; // ignore, container
869 }
870 if (val->xpath() == nukes + "/description") {
871 continue; // unused
872 }
873
Jan Kundrát6ee84792020-01-24 01:43:36 +0100874 if (val->xpath() == nukes + "/payload/kilotons") {
875 kilotons = val->data()->get_uint64();
Jan Kundrát6ee84792020-01-24 01:43:36 +0100876 } else if (std::string_view{val->xpath()}.find(nukes + "/cities") == 0) {
877 hasCities = true;
878 } else {
879 throw std::runtime_error("RPC launch-nukes: unexpected input "s + val->xpath());
880 }
881 }
882 if (kilotons == 333'666) {
883 // magic, just do not generate any output. This is important because the NETCONF RPC returns just <ok/>.
884 return SR_ERR_OK;
885 }
886 auto buf = output->allocate(2);
887 size_t i = 0;
888 buf->val(i++)->set((nukes + "/blast-radius").c_str(), uint32_t{33'666});
889 buf->val(i++)->set((nukes + "/actual-yield").c_str(), static_cast<uint64_t>(1.33 * kilotons));
890 if (hasCities) {
891 buf = output->reallocate(output->val_cnt() + 2);
892 buf->val(i++)->set((nukes + "/damaged-places/targets[city='London']/city").c_str(), "London");
893 buf->val(i++)->set((nukes + "/damaged-places/targets[city='Berlin']/city").c_str(), "Berlin");
894 }
895 return SR_ERR_OK;
896 }
897 throw std::runtime_error("unrecognized RPC");
898 }
899};
900
Václav Kubernáta8789602020-07-20 15:18:19 +0200901TEST_CASE("rpc/action") {
Jan Kundrát6ee84792020-01-24 01:43:36 +0100902 trompeloeil::sequence seq1;
Jan Kundrát6ee84792020-01-24 01:43:36 +0100903
904#ifdef sysrepo_BACKEND
Václav Kubernáte7248b22020-06-26 15:38:59 +0200905 auto datastore = std::make_shared<SysrepoAccess>("netconf-cli-test", Datastore::Running);
Jan Kundrát6ee84792020-01-24 01:43:36 +0100906#elif defined(netconf_BACKEND)
Václav Kubernáte7248b22020-06-26 15:38:59 +0200907 auto datastore = std::make_shared<NetconfAccess>(NETOPEER_SOCKET_PATH);
Václav Kubernát74487df2020-06-04 01:29:28 +0200908#elif defined(yang_BACKEND)
Václav Kubernáte7248b22020-06-26 15:38:59 +0200909 auto datastore = std::make_shared<YangAccess>();
910 datastore->addSchemaDir(schemaDir);
911 datastore->addSchemaFile(exampleSchemaFile);
Jan Kundrát6ee84792020-01-24 01:43:36 +0100912#else
913#error "Unknown backend"
914#endif
915
Václav Kubernáta8789602020-07-20 15:18:19 +0200916 auto srConn = std::make_shared<sysrepo::Connection>("netconf-cli-test-rpc");
917 auto srSession = std::make_shared<sysrepo::Session>(srConn);
918 auto srSubscription = std::make_shared<sysrepo::Subscribe>(srSession);
919 auto cb = std::make_shared<RpcCb>();
920 sysrepo::Logs{}.set_stderr(SR_LL_INF);
921 auto doNothingCb = std::make_shared<sysrepo::Callback>();
922 srSubscription->module_change_subscribe("example-schema", doNothingCb, nullptr, SR_SUBSCR_CTX_REUSE);
923 // careful here, sysrepo insists on module_change CBs being registered before RPC CBs, otherwise there's a memleak
924 srSubscription->rpc_subscribe("/example-schema:noop", cb, nullptr, SR_SUBSCR_CTX_REUSE);
925 srSubscription->rpc_subscribe("/example-schema:launch-nukes", cb, nullptr, SR_SUBSCR_CTX_REUSE);
926 srSubscription->rpc_subscribe("/example-schema:fire", cb, nullptr, SR_SUBSCR_CTX_REUSE);
927 srSubscription->action_subscribe("/example-schema:ports/shutdown", cb, datastore->schema().get(), SR_SUBSCR_CTX_REUSE);
Václav Kubernáte7248b22020-06-26 15:38:59 +0200928
Václav Kubernáta8789602020-07-20 15:18:19 +0200929 SECTION("rpc")
Jan Kundrát7ec214d2020-06-19 17:05:07 +0200930 {
Václav Kubernáta8789602020-07-20 15:18:19 +0200931 auto createTemporaryDatastore = [](const std::shared_ptr<DatastoreAccess>& datastore) {
932 return std::make_shared<YangAccess>(std::static_pointer_cast<YangSchema>(datastore->schema()));
933 };
934 ProxyDatastore proxyDatastore(datastore, createTemporaryDatastore);
Jan Kundrát6ee84792020-01-24 01:43:36 +0100935
Václav Kubernáta8789602020-07-20 15:18:19 +0200936 // ProxyDatastore cannot easily read DatastoreAccess::Tree, so we need to set the input via create/setLeaf/etc.
937 SECTION("valid")
938 {
939 std::string rpc;
940 DatastoreAccess::Tree input, output;
941
942 SECTION("noop") {
943 rpc = "/example-schema:noop";
944 proxyDatastore.initiateRpc(rpc);
945 }
946
947 SECTION("small nuke") {
948 rpc = "/example-schema:launch-nukes";
949 input = {
950 {"description", "dummy"s},
951 {"payload/kilotons", uint64_t{333'666}},
952 };
953 proxyDatastore.initiateRpc(rpc);
954 proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{333'666});
955 // no data are returned
956 }
957
958 SECTION("small nuke") {
959 rpc = "/example-schema:launch-nukes";
960 input = {
961 {"description", "dummy"s},
962 {"payload/kilotons", uint64_t{4}},
963 };
964 proxyDatastore.initiateRpc(rpc);
965 proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{4});
966
967 output = {
968 {"blast-radius", uint32_t{33'666}},
969 {"actual-yield", uint64_t{5}},
970 };
971 }
972
973 SECTION("with lists") {
974 rpc = "/example-schema:launch-nukes";
975 input = {
976 {"payload/kilotons", uint64_t{6}},
977 {"cities/targets[city='Prague']/city", "Prague"s},
978 };
979 proxyDatastore.initiateRpc(rpc);
980 proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{6});
981 proxyDatastore.createItem("/example-schema:launch-nukes/example-schema:cities/example-schema:targets[city='Prague']");
982 output = {
983 {"blast-radius", uint32_t{33'666}},
984 {"actual-yield", uint64_t{7}},
985 {"damaged-places", special_{SpecialValue::PresenceContainer}},
986 {"damaged-places/targets[city='London']", special_{SpecialValue::List}},
987 {"damaged-places/targets[city='London']/city", "London"s},
988 {"damaged-places/targets[city='Berlin']", special_{SpecialValue::List}},
989 {"damaged-places/targets[city='Berlin']/city", "Berlin"s},
990 };
991 }
992
993 SECTION("with leafref") {
994 datastore->createItem("/example-schema:person[name='Colton']");
995 datastore->commitChanges();
996
997 rpc = "/example-schema:fire";
998 input = {
999 {"whom", "Colton"s}
1000 };
1001 proxyDatastore.initiateRpc(rpc);
1002 proxyDatastore.setLeaf("/example-schema:fire/example-schema:whom", "Colton"s);
1003 }
1004
1005 catching<OnExec>([&] {REQUIRE(datastore->executeRpc(rpc, input) == output);});
Václav Kubernátaa4250a2020-07-22 00:02:23 +02001006 catching<OnExec>([&] {REQUIRE(proxyDatastore.execute() == output);});
Jan Kundrát7ec214d2020-06-19 17:05:07 +02001007 }
1008
Václav Kubernáta8789602020-07-20 15:18:19 +02001009 SECTION("non-existing RPC")
1010 {
1011 catching<OnInvalidRpcPath>([&] {datastore->executeRpc("/example-schema:non-existing", DatastoreAccess::Tree{});});
Jan Kundrát7ec214d2020-06-19 17:05:07 +02001012 }
Jan Kundrát6ee84792020-01-24 01:43:36 +01001013 }
1014
Václav Kubernáta8789602020-07-20 15:18:19 +02001015 SECTION("action")
Jan Kundrát7ec214d2020-06-19 17:05:07 +02001016 {
Václav Kubernáta8789602020-07-20 15:18:19 +02001017 std::string path;
1018 DatastoreAccess::Tree input, output;
1019
1020 output = {
1021#ifdef netconf_BACKEND
1022 {"/example-schema:ports[name='A']", special_{SpecialValue::List}},
1023 {"/example-schema:ports[name='A']/name", enum_{"A"}},
1024#endif
1025 {"success", true}
1026 };
1027 datastore->createItem("/example-schema:ports[name='A']");
1028 datastore->commitChanges();
1029 SECTION("shutdown") {
1030 path = "/example-schema:ports[name='A']/shutdown";
1031 }
1032
1033 catching<OnExec>([&] {REQUIRE(datastore->executeAction(path, input) == output);});
Jan Kundrát6ee84792020-01-24 01:43:36 +01001034 }
1035
Jan Kundrát6ee84792020-01-24 01:43:36 +01001036 waitForCompletionAndBitMore(seq1);
1037}