Clean up NetconfMessageTransformerTest
[netconf.git] / plugins / netconf-client-mdsal / src / test / java / org / opendaylight / netconf / client / mdsal / impl / NetconfMessageTransformerTest.java
1 /*
2  * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.client.mdsal.impl;
9
10 import static org.hamcrest.CoreMatchers.instanceOf;
11 import static org.hamcrest.MatcherAssert.assertThat;
12 import static org.junit.Assert.assertEquals;
13 import static org.junit.Assert.assertNotNull;
14 import static org.junit.Assert.assertNull;
15 import static org.junit.Assert.assertTrue;
16 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_CONTENT;
17 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_QNAME;
18 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.GET_SCHEMA_QNAME;
19 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_CANDIDATE_NODEID;
20 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME;
21 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME;
22 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_NODEID;
23 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME;
24 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_NODEID;
25 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME;
26 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_GET_NODEID;
27 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_GET_QNAME;
28 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_LOCK_QNAME;
29 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_RUNNING_NODEID;
30 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.createEditConfigStructure;
31 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.toFilterStructure;
32 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.toId;
33 import static org.opendaylight.netconf.common.mdsal.NormalizedDataUtil.NETCONF_DATA_QNAME;
34
35 import java.io.IOException;
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Optional;
41 import java.util.Set;
42 import org.custommonkey.xmlunit.Diff;
43 import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier;
44 import org.custommonkey.xmlunit.XMLUnit;
45 import org.junit.AfterClass;
46 import org.junit.Before;
47 import org.junit.BeforeClass;
48 import org.junit.Test;
49 import org.opendaylight.mdsal.binding.runtime.spi.BindingRuntimeHelpers;
50 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
51 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
52 import org.opendaylight.netconf.api.messages.NetconfMessage;
53 import org.opendaylight.netconf.api.xml.XmlUtil;
54 import org.opendaylight.netconf.client.mdsal.AbstractBaseSchemasTest;
55 import org.opendaylight.netconf.client.mdsal.MonitoringSchemaSourceProvider;
56 import org.opendaylight.netconf.common.mdsal.NormalizedDataUtil;
57 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.IetfNetconfService;
58 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState;
59 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Capabilities;
60 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Datastores;
61 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Schemas;
62 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Sessions;
63 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Statistics;
64 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.datastores.Datastore;
65 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.datastores.datastore.Locks;
66 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.datastores.datastore.locks.lock.type.partial.lock.PartialLock;
67 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.schemas.Schema;
68 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.sessions.Session;
69 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfConfigChange;
70 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.netconf.config.change.Edit;
71 import org.opendaylight.yangtools.yang.common.QName;
72 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
73 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
74 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
75 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
76 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
77 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
78 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
79 import org.opendaylight.yangtools.yang.data.api.schema.DOMSourceAnyxmlNode;
80 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
81 import org.opendaylight.yangtools.yang.data.api.schema.MountPointContext;
82 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
83 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
84 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
85 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
86 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
87 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
88 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
89 import org.w3c.dom.Element;
90 import org.w3c.dom.Node;
91 import org.xml.sax.SAXException;
92
93 public class NetconfMessageTransformerTest extends AbstractBaseSchemasTest {
94
95     private static final String REVISION_EXAMPLE_SERVER_FARM = "2018-08-07";
96     private static final String URN_EXAMPLE_SERVER_FARM = "urn:example:server-farm";
97
98     private static final String REVISION_EXAMPLE_SERVER_FARM_2 = "2019-05-20";
99     private static final String URN_EXAMPLE_SERVER_FARM_2 = "urn:example:server-farm-2";
100
101     private static final String URN_EXAMPLE_CONFLICT = "urn:example:conflict";
102
103     private static final String URN_EXAMPLE_AUGMENTED_ACTION = "urn:example:augmented-action";
104
105     private static final String URN_EXAMPLE_RPCS_ACTIONS_OUTPUTS = "urn:example:rpcs-actions-outputs";
106
107     private static final QName SERVER_QNAME =
108             QName.create(URN_EXAMPLE_SERVER_FARM, REVISION_EXAMPLE_SERVER_FARM, "server");
109     private static final QName RESET_QNAME = QName.create(SERVER_QNAME, "reset");
110     private static final Absolute RESET_SERVER_PATH = Absolute.of(SERVER_QNAME, RESET_QNAME);
111     private static final QName APPLICATIONS_QNAME = QName.create(URN_EXAMPLE_SERVER_FARM_2,
112             REVISION_EXAMPLE_SERVER_FARM_2, "applications");
113     private static final QName APPLICATION_QNAME = QName.create(APPLICATIONS_QNAME, "application");
114     private static final QName KILL_QNAME = QName.create(APPLICATION_QNAME, "kill");
115     private static final Absolute KILL_SERVER_APP_PATH =
116             Absolute.of(SERVER_QNAME, APPLICATIONS_QNAME, APPLICATION_QNAME, KILL_QNAME);
117
118     private static final QName DEVICE_QNAME =
119             QName.create(URN_EXAMPLE_SERVER_FARM, REVISION_EXAMPLE_SERVER_FARM, "device");
120     private static final QName START_QNAME = QName.create(DEVICE_QNAME, "start");
121     private static final Absolute START_DEVICE_PATH = Absolute.of(DEVICE_QNAME, START_QNAME);
122     private static final QName INTERFACE_QNAME = QName.create(DEVICE_QNAME, "interface");
123     private static final QName ENABLE_QNAME = QName.create(INTERFACE_QNAME, "enable");
124     private static final Absolute ENABLE_INTERFACE_PATH = Absolute.of(DEVICE_QNAME, INTERFACE_QNAME, ENABLE_QNAME);
125
126     private static final QName DISABLE_QNAME = QName.create(URN_EXAMPLE_AUGMENTED_ACTION, "disable");
127     private static final Absolute DISABLE_INTERFACE_PATH = Absolute.of(DEVICE_QNAME, INTERFACE_QNAME, DISABLE_QNAME);
128
129     private static final QName CHECK_WITH_OUTPUT_QNAME =
130             QName.create(URN_EXAMPLE_RPCS_ACTIONS_OUTPUTS, "check-with-output");
131     private static final Absolute CHECK_WITH_OUTPUT_INTERFACE_PATH =
132             Absolute.of(DEVICE_QNAME, INTERFACE_QNAME, CHECK_WITH_OUTPUT_QNAME);
133     private static final QName CHECK_WITHOUT_OUTPUT_QNAME =
134             QName.create(URN_EXAMPLE_RPCS_ACTIONS_OUTPUTS, "check-without-output");
135     private static final Absolute CHECK_WITHOUT_OUTPUT_INTERFACE_PATH =
136             Absolute.of(DEVICE_QNAME, INTERFACE_QNAME, CHECK_WITHOUT_OUTPUT_QNAME);
137     private static final QName RPC_WITH_OUTPUT_QNAME =
138             QName.create(URN_EXAMPLE_RPCS_ACTIONS_OUTPUTS, "rpc-with-output");
139     private static final QName RPC_WITHOUT_OUTPUT_QNAME =
140             QName.create(URN_EXAMPLE_RPCS_ACTIONS_OUTPUTS, "rpc-without-output");
141
142     private static final QName BOX_OUT_QNAME =
143             QName.create(URN_EXAMPLE_SERVER_FARM, REVISION_EXAMPLE_SERVER_FARM, "box-out");
144     private static final QName BOX_IN_QNAME = QName.create(BOX_OUT_QNAME, "box-in");
145     private static final QName OPEN_QNAME = QName.create(BOX_IN_QNAME, "open");
146     private static final Absolute OPEN_BOXES_PATH = Absolute.of(BOX_OUT_QNAME, BOX_IN_QNAME, OPEN_QNAME);
147
148     private static final QName FOO_QNAME = QName.create(URN_EXAMPLE_CONFLICT, "foo");
149     private static final QName BAR_QNAME = QName.create(URN_EXAMPLE_CONFLICT, "bar");
150     private static final QName XYZZY_QNAME = QName.create(URN_EXAMPLE_CONFLICT, "xyzzy");
151     private static final Absolute XYZZY_FOO_PATH = Absolute.of(FOO_QNAME, XYZZY_QNAME);
152     private static final Absolute XYZZY_BAR_PATH = Absolute.of(BAR_QNAME, XYZZY_QNAME);
153
154     private static final QName CONFLICT_CHOICE_QNAME = QName.create(URN_EXAMPLE_CONFLICT, "conflict-choice");
155     private static final QName CHOICE_CONT_QNAME = QName.create(URN_EXAMPLE_CONFLICT, "choice-cont");
156     private static final QName CHOICE_ACTION_QNAME = QName.create(URN_EXAMPLE_CONFLICT, "choice-action");
157     private static final Absolute CHOICE_ACTION_PATH =
158             Absolute.of(CONFLICT_CHOICE_QNAME, CHOICE_CONT_QNAME, CHOICE_CONT_QNAME, CHOICE_ACTION_QNAME);
159
160     private static EffectiveModelContext PARTIAL_SCHEMA;
161     private static EffectiveModelContext SCHEMA;
162     private static EffectiveModelContext ACTION_SCHEMA;
163
164     private NetconfMessageTransformer actionNetconfMessageTransformer;
165     private NetconfMessageTransformer netconfMessageTransformer;
166
167     @BeforeClass
168     public static void beforeClass() {
169         PARTIAL_SCHEMA = BindingRuntimeHelpers.createEffectiveModel(NetconfState.class);
170         SCHEMA = BindingRuntimeHelpers.createEffectiveModel(IetfNetconfService.class, NetconfState.class,
171             NetconfConfigChange.class);
172         ACTION_SCHEMA = YangParserTestUtils.parseYangResources(NetconfMessageTransformerTest.class,
173             "/schemas/example-server-farm.yang","/schemas/example-server-farm-2.yang",
174             "/schemas/conflicting-actions.yang", "/schemas/augmented-action.yang",
175             "/schemas/rpcs-actions-outputs.yang");
176     }
177
178     @AfterClass
179     public static void afterClass() {
180         PARTIAL_SCHEMA = null;
181         SCHEMA = null;
182         ACTION_SCHEMA = null;
183     }
184
185     @Before
186     public void setUp() throws Exception {
187         XMLUnit.setIgnoreWhitespace(true);
188         XMLUnit.setIgnoreAttributeOrder(true);
189         XMLUnit.setIgnoreComments(true);
190
191         netconfMessageTransformer = getTransformer(SCHEMA);
192         actionNetconfMessageTransformer = new NetconfMessageTransformer(MountPointContext.of(ACTION_SCHEMA),
193             true, BASE_SCHEMAS.getBaseSchema());
194     }
195
196     @Test
197     public void testLockRequestBaseSchemaNotPresent() throws Exception {
198         final var transformer = getTransformer(PARTIAL_SCHEMA);
199         final var netconfMessage = transformer.toRpcRequest(NETCONF_LOCK_QNAME,
200                 NetconfBaseOps.getLockContent(NETCONF_CANDIDATE_NODEID));
201         assertEquals("""
202             <rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="m-0">
203                 <lock>
204                     <target>
205                         <candidate/>
206                     </target>
207                 </lock>
208             </rpc>
209             """, XmlUtil.toString(netconfMessage.getDocument()));
210     }
211
212     @Test
213     public void testCreateSubscriberNotificationSchemaNotPresent() throws Exception {
214         final var transformer = new NetconfMessageTransformer(MountPointContext.of(SCHEMA), true,
215             BASE_SCHEMAS.getBaseSchemaWithNotifications());
216         var netconfMessage = transformer.toRpcRequest(CREATE_SUBSCRIPTION_RPC_QNAME, CREATE_SUBSCRIPTION_RPC_CONTENT);
217         assertEquals("""
218             <rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="m-0">
219                 <create-subscription xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"/>
220             </rpc>
221             """, XmlUtil.toString(netconfMessage.getDocument()));
222     }
223
224     @Test
225     public void tesLockSchemaRequest() throws Exception {
226         final NetconfMessageTransformer transformer = getTransformer(PARTIAL_SCHEMA);
227         final String result = "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><ok/></rpc-reply>";
228
229         transformer.toRpcResult(
230             RpcResultBuilder.success(new NetconfMessage(XmlUtil.readXmlToDocument(result))).build(),
231             NETCONF_LOCK_QNAME);
232     }
233
234     @Test
235     public void testRpcEmptyBodyWithOutputDefinedSchemaResult() throws Exception {
236         final String result = "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><ok/></rpc-reply>";
237
238         final var domRpcResult = actionNetconfMessageTransformer.toRpcResult(
239             RpcResultBuilder.success(new NetconfMessage(XmlUtil.readXmlToDocument(result))).build(),
240             RPC_WITH_OUTPUT_QNAME);
241         assertNotNull(domRpcResult);
242     }
243
244     @Test
245     public void testRpcEmptyBodyWithoutOutputDefinedSchemaResult() throws Exception {
246         final String result = "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><ok/></rpc-reply>";
247
248         final var domRpcResult = actionNetconfMessageTransformer.toRpcResult(
249             RpcResultBuilder.success(new NetconfMessage(XmlUtil.readXmlToDocument(result))).build(),
250             RPC_WITHOUT_OUTPUT_QNAME);
251         assertNotNull(domRpcResult);
252     }
253
254     @Test
255     public void testDiscardChangesRequest() throws Exception {
256         final var netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_DISCARD_CHANGES_QNAME, null);
257         assertEquals("""
258             <rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="m-0">
259                 <discard-changes/>
260             </rpc>
261             """, XmlUtil.toString(netconfMessage.getDocument()));
262     }
263
264     @Test
265     public void testGetSchemaRequest() throws Exception {
266         final var netconfMessage = netconfMessageTransformer.toRpcRequest(GET_SCHEMA_QNAME,
267                 MonitoringSchemaSourceProvider.createGetSchemaRequest("module", Optional.of("2012-12-12")));
268         assertSimilarXml(netconfMessage, """
269             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
270               <get-schema xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
271                 <format>yang</format>
272                 <identifier>module</identifier>
273                 <version>2012-12-12</version>
274               </get-schema>
275             </rpc>""");
276     }
277
278     @Test
279     public void testGetSchemaResponse() throws Exception {
280         final var transformer = getTransformer(SCHEMA);
281         final var response = new NetconfMessage(XmlUtil.readXmlToDocument("""
282             <rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
283               <data xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
284                 <schema xmlns="http://www.w3.org/2001/XMLSchema">Random YANG SCHEMA</schema>
285               </data>
286             </rpc-reply>"""));
287         final var compositeNodeRpcResult = transformer.toRpcResult(RpcResultBuilder.success(response).build(),
288             GET_SCHEMA_QNAME);
289         assertTrue(compositeNodeRpcResult.errors().isEmpty());
290         assertNotNull(compositeNodeRpcResult.value());
291         final var schemaContent = ((DOMSourceAnyxmlNode) compositeNodeRpcResult.value()
292                 .body().iterator().next()).body();
293         assertEquals("""
294             <data xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
295                 <schema xmlns="http://www.w3.org/2001/XMLSchema">Random YANG SCHEMA</schema>
296             </data>
297             """, XmlUtil.toString((Element) schemaContent.getNode()));
298     }
299
300     @Test
301     public void testGetConfigResponse() throws Exception {
302         final var response = new NetconfMessage(XmlUtil.readXmlToDocument("""
303             <rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
304               <data>
305                  <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
306                    <schemas>
307                      <schema>
308                        <identifier>module</identifier>
309                        <version>2012-12-12</version>
310                        <format xmlns:x="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">x:yang</format>
311                      </schema>
312                    </schemas>
313                  </netconf-state>
314                </data>
315              </rpc-reply>"""));
316
317         final var transformer = getTransformer(SCHEMA);
318         final var compositeNodeRpcResult = transformer.toRpcResult(RpcResultBuilder.success(response).build(),
319             NETCONF_GET_CONFIG_QNAME);
320         assertTrue(compositeNodeRpcResult.errors().isEmpty());
321         assertNotNull(compositeNodeRpcResult.value());
322
323         final var values = MonitoringSchemaSourceProvider.createGetSchemaRequest(
324             "module", Optional.of("2012-12-12")).body();
325
326         final var keys = new HashMap<QName, Object>();
327         for (var value : values) {
328             keys.put(value.name().getNodeType(), value.body());
329         }
330
331         final var schemaNode = Builders.mapEntryBuilder()
332             .withNodeIdentifier(NodeIdentifierWithPredicates.of(Schema.QNAME, keys))
333             .withValue(values)
334             .build();
335
336         final var data = (DOMSourceAnyxmlNode) compositeNodeRpcResult.value().getChildByArg(toId(NETCONF_DATA_QNAME));
337
338         final var nodeResult = NormalizedDataUtil.transformDOMSourceToNormalizedNode(SCHEMA, data.body());
339         final var result = (ContainerNode) nodeResult.getResult().data();
340         final var state = (ContainerNode) result.getChildByArg(toId(NetconfState.QNAME));
341         final var schemas = (ContainerNode) state.getChildByArg(toId(Schemas.QNAME));
342         final var schemaParent = (MapNode) schemas.getChildByArg(toId(Schema.QNAME));
343         assertEquals(1, schemaParent.body().size());
344
345         assertEquals(schemaNode, schemaParent.body().iterator().next());
346     }
347
348     @Test
349     public void testGetConfigLeafRequest() throws Exception {
350         final var filter = toFilterStructure(
351                 YangInstanceIdentifier.of(toId(NetconfState.QNAME), toId(Schemas.QNAME), toId(Schema.QNAME),
352                     NodeIdentifierWithPredicates.of(Schema.QNAME),
353                     toId(QName.create(Schemas.QNAME, "version"))), SCHEMA);
354
355         final var source = NetconfBaseOps.getSourceNode(NETCONF_RUNNING_NODEID);
356
357         final var netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_CONFIG_QNAME,
358                 NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID, source, filter));
359
360         assertSimilarXml(netconfMessage, """
361             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
362               <get-config>
363                 <filter type="subtree">
364                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
365                     <schemas>
366                       <schema>
367                         <version/>
368                       </schema>
369                     </schemas>
370                   </netconf-state>
371                 </filter>
372                 <source>
373                   <running/>
374                 </source>
375               </get-config>
376             </rpc>""");
377     }
378
379     @Test
380     public void testGetConfigRequest() throws Exception {
381         final var filter = toFilterStructure(
382                 YangInstanceIdentifier.of(toId(NetconfState.QNAME), toId(Schemas.QNAME)), SCHEMA);
383
384         final var source = NetconfBaseOps.getSourceNode(NETCONF_RUNNING_NODEID);
385
386         final var netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_CONFIG_QNAME,
387                 NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID, source, filter));
388
389         assertSimilarXml(netconfMessage, """
390             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
391               <get-config>
392                 <filter type="subtree">
393                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
394                     <schemas/>
395                   </netconf-state>
396                 </filter>
397                 <source>
398                  <running/>
399                 </source>
400               </get-config>
401             </rpc>""");
402     }
403
404     @Test
405     public void testEditConfigRequest() throws Exception {
406         final var values = MonitoringSchemaSourceProvider.createGetSchemaRequest(
407             "module", Optional.of("2012-12-12")).body();
408
409         final var keys = new HashMap<QName, Object>();
410         for (var value : values) {
411             keys.put(value.name().getNodeType(), value.body());
412         }
413
414         final var schemaNode = Builders.mapEntryBuilder()
415             .withNodeIdentifier(NodeIdentifierWithPredicates.of(Schema.QNAME, keys))
416             .withValue(values)
417             .build();
418
419         final var id = YangInstanceIdentifier.builder()
420                 .node(NetconfState.QNAME).node(Schemas.QNAME).node(Schema.QNAME)
421                 .nodeWithKey(Schema.QNAME, keys).build();
422         final var editConfigStructure =
423                 createEditConfigStructure(BASE_SCHEMAS.getBaseSchemaWithNotifications().getEffectiveModelContext(), id,
424                     Optional.empty(), Optional.ofNullable(schemaNode));
425
426         final var target = NetconfBaseOps.getTargetNode(NETCONF_CANDIDATE_NODEID);
427
428         final var wrap = NetconfMessageTransformUtil.wrap(NETCONF_EDIT_CONFIG_NODEID, editConfigStructure, target);
429         final var netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_EDIT_CONFIG_QNAME, wrap);
430
431         assertSimilarXml(netconfMessage, """
432             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
433               <edit-config>
434                 <target>
435                   <candidate/>
436                 </target>
437                 <config>
438                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
439                     <schemas>
440                       <schema>
441                         <identifier>module</identifier>
442                         <version>2012-12-12</version>
443                         <format>yang</format>
444                       </schema>
445                     </schemas>
446                   </netconf-state>
447                 </config>
448               </edit-config>
449             </rpc>""");
450     }
451
452     private static void assertSimilarXml(final NetconfMessage netconfMessage, final String xmlContent)
453             throws SAXException, IOException {
454         final Diff diff = XMLUnit.compareXML(netconfMessage.getDocument(), XmlUtil.readXmlToDocument(xmlContent));
455         diff.overrideElementQualifier(new ElementNameAndAttributeQualifier());
456         assertTrue(diff.toString(), diff.similar());
457     }
458
459     @Test
460     public void testGetRequest() throws Exception {
461         final var capability = QName.create(Capabilities.QNAME, "capability");
462         final var filter = toFilterStructure(
463                 YangInstanceIdentifier.of(toId(NetconfState.QNAME), toId(Capabilities.QNAME), toId(capability),
464                     new NodeWithValue<>(capability, "a:b:c")), SCHEMA);
465
466         final var netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
467                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filter));
468
469         assertSimilarXml(netconfMessage, """
470             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
471               <get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
472                 <filter type="subtree">
473                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
474                     <capabilities>
475                       <capability>a:b:c</capability>
476                     </capabilities>
477                   </netconf-state>
478                 </filter>
479               </get>
480             </rpc>""");
481     }
482
483     @Test
484     public void testGetLeafList() throws IOException, SAXException {
485         final var filter = toFilterStructure(YangInstanceIdentifier.of(
486             NetconfState.QNAME,
487             Capabilities.QNAME,
488             QName.create(Capabilities.QNAME, "capability")), SCHEMA);
489         final var netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
490                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filter));
491
492         assertSimilarXml(netconfMessage, """
493             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
494               <get>
495                 <filter type="subtree">
496                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
497                     <capabilities>
498                       <capability/>
499                     </capabilities>
500                   </netconf-state>
501                 </filter>
502               </get>
503             </rpc>""");
504     }
505
506     @Test
507     public void testGetList() throws IOException, SAXException {
508         final var filter = toFilterStructure(YangInstanceIdentifier.of(
509             NetconfState.QNAME,
510             Datastores.QNAME,
511             QName.create(Datastores.QNAME, "datastore")), SCHEMA);
512         final var netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
513                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filter));
514
515         assertSimilarXml(netconfMessage, """
516             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
517               <get>
518                 <filter type="subtree">
519                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
520                     <datastores>
521                       <datastore/>
522                     </datastores>
523                   </netconf-state>
524                 </filter>
525               </get>
526             </rpc>""");
527     }
528
529     private static NetconfMessageTransformer getTransformer(final EffectiveModelContext schema) {
530         return new NetconfMessageTransformer(MountPointContext.of(schema), true, BASE_SCHEMAS.getBaseSchema());
531     }
532
533     @Test
534     public void testCommitResponse() throws Exception {
535         final NetconfMessage response = new NetconfMessage(XmlUtil.readXmlToDocument(
536                 "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><ok/></rpc-reply>"
537         ));
538         final DOMRpcResult compositeNodeRpcResult = netconfMessageTransformer.toRpcResult(
539             RpcResultBuilder.success(response).build(),
540             NETCONF_COMMIT_QNAME);
541         assertTrue(compositeNodeRpcResult.errors().isEmpty());
542         assertNull(compositeNodeRpcResult.value());
543     }
544
545     @Test
546     public void getActionsTest() {
547         Set<Absolute> schemaPaths = new HashSet<>();
548         schemaPaths.add(RESET_SERVER_PATH);
549         schemaPaths.add(START_DEVICE_PATH);
550         schemaPaths.add(ENABLE_INTERFACE_PATH);
551         schemaPaths.add(OPEN_BOXES_PATH);
552         schemaPaths.add(KILL_SERVER_APP_PATH);
553         schemaPaths.add(XYZZY_FOO_PATH);
554         schemaPaths.add(XYZZY_BAR_PATH);
555         schemaPaths.add(CHOICE_ACTION_PATH);
556         schemaPaths.add(DISABLE_INTERFACE_PATH);
557         schemaPaths.add(CHECK_WITH_OUTPUT_INTERFACE_PATH);
558         schemaPaths.add(CHECK_WITHOUT_OUTPUT_INTERFACE_PATH);
559
560         var actions = NetconfMessageTransformer.getActions(ACTION_SCHEMA);
561         assertEquals(schemaPaths.size(), actions.size());
562
563         for (var path : schemaPaths) {
564             assertNotNull("Action for " + path + " not found", actions.get(path));
565         }
566     }
567
568     @Test
569     public void toActionRequestListTopLevelTest() {
570         QName nameQname = QName.create(SERVER_QNAME, "name");
571         List<PathArgument> nodeIdentifiers = new ArrayList<>();
572         nodeIdentifiers.add(new NodeIdentifier(SERVER_QNAME));
573         nodeIdentifiers.add(NodeIdentifierWithPredicates.of(SERVER_QNAME, nameQname, "test"));
574         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
575
576         ContainerNode data = initInputAction(QName.create(SERVER_QNAME, "reset-at"), "now");
577
578         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
579                 RESET_SERVER_PATH, domDataTreeIdentifier, data);
580
581         Node childAction = checkBasePartOfActionRequest(actionRequest);
582
583         Node childServer = childAction.getFirstChild();
584         checkNode(childServer, "server", "server", URN_EXAMPLE_SERVER_FARM);
585
586         Node childName = childServer.getFirstChild();
587         checkNode(childName, "name", "name", URN_EXAMPLE_SERVER_FARM);
588
589         Node childTest = childName.getFirstChild();
590         assertEquals(childTest.getNodeValue(), "test");
591
592         checkAction(RESET_QNAME, childName.getNextSibling(), "reset-at", "reset-at", "now");
593     }
594
595     @Test
596     public void toActionRequestContainerTopLevelTest() {
597         List<PathArgument> nodeIdentifiers = List.of(NodeIdentifier.create(DEVICE_QNAME));
598         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
599
600         ContainerNode payload = initInputAction(QName.create(DEVICE_QNAME, "start-at"), "now");
601         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
602                 START_DEVICE_PATH, domDataTreeIdentifier, payload);
603
604         Node childAction = checkBasePartOfActionRequest(actionRequest);
605
606         Node childDevice = childAction.getFirstChild();
607         checkNode(childDevice, "device", "device", URN_EXAMPLE_SERVER_FARM);
608
609         checkAction(START_QNAME, childDevice.getFirstChild(), "start-at", "start-at", "now");
610     }
611
612     @Test
613     public void toActionRequestContainerInContainerTest() {
614         List<PathArgument> nodeIdentifiers = new ArrayList<>();
615         nodeIdentifiers.add(NodeIdentifier.create(BOX_OUT_QNAME));
616         nodeIdentifiers.add(NodeIdentifier.create(BOX_IN_QNAME));
617
618         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
619
620         ContainerNode payload = initInputAction(QName.create(BOX_OUT_QNAME, "start-at"), "now");
621         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
622                 OPEN_BOXES_PATH, domDataTreeIdentifier, payload);
623
624         Node childAction = checkBasePartOfActionRequest(actionRequest);
625
626         Node childBoxOut = childAction.getFirstChild();
627         checkNode(childBoxOut, "box-out", "box-out", URN_EXAMPLE_SERVER_FARM);
628
629         Node childBoxIn = childBoxOut.getFirstChild();
630         checkNode(childBoxIn, "box-in", "box-in", URN_EXAMPLE_SERVER_FARM);
631
632         Node action = childBoxIn.getFirstChild();
633         checkNode(action, OPEN_QNAME.getLocalName(), OPEN_QNAME.getLocalName(), OPEN_QNAME.getNamespace().toString());
634     }
635
636     @Test
637     public void toActionRequestListInContainerTest() {
638         QName nameQname = QName.create(INTERFACE_QNAME, "name");
639
640         List<PathArgument> nodeIdentifiers = new ArrayList<>();
641         nodeIdentifiers.add(NodeIdentifier.create(DEVICE_QNAME));
642         nodeIdentifiers.add(NodeIdentifier.create(INTERFACE_QNAME));
643         nodeIdentifiers.add(NodeIdentifierWithPredicates.of(INTERFACE_QNAME, nameQname, "test"));
644
645         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
646
647         ContainerNode payload = initEmptyInputAction(INTERFACE_QNAME);
648         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
649                 ENABLE_INTERFACE_PATH, domDataTreeIdentifier, payload);
650
651         Node childAction = checkBasePartOfActionRequest(actionRequest);
652
653         Node childDevice = childAction.getFirstChild();
654         checkNode(childDevice, "device", "device", URN_EXAMPLE_SERVER_FARM);
655
656         Node childInterface = childDevice.getFirstChild();
657         checkNode(childInterface, "interface", "interface", URN_EXAMPLE_SERVER_FARM);
658
659         Node childName = childInterface.getFirstChild();
660         checkNode(childName, "name", "name", nameQname.getNamespace().toString());
661
662         Node childTest = childName.getFirstChild();
663         assertEquals(childTest.getNodeValue(), "test");
664
665         Node action = childInterface.getLastChild();
666         checkNode(action, ENABLE_QNAME.getLocalName(), ENABLE_QNAME.getLocalName(),
667                 ENABLE_QNAME.getNamespace().toString());
668     }
669
670     @Test
671     public void toActionRequestListInContainerAugmentedIntoListTest() {
672         QName serverNameQname = QName.create(SERVER_QNAME, "name");
673         QName applicationNameQname = QName.create(APPLICATION_QNAME, "name");
674
675         List<PathArgument> nodeIdentifiers = new ArrayList<>();
676         nodeIdentifiers.add(NodeIdentifier.create(SERVER_QNAME));
677         nodeIdentifiers.add(NodeIdentifierWithPredicates.of(SERVER_QNAME, serverNameQname, "testServer"));
678         nodeIdentifiers.add(NodeIdentifier.create(APPLICATIONS_QNAME));
679         nodeIdentifiers.add(NodeIdentifier.create(APPLICATION_QNAME));
680         nodeIdentifiers.add(NodeIdentifierWithPredicates.of(APPLICATION_QNAME,
681                 applicationNameQname, "testApplication"));
682
683         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
684
685         ContainerNode payload = initEmptyInputAction(APPLICATION_QNAME);
686         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
687                 KILL_SERVER_APP_PATH, domDataTreeIdentifier, payload);
688
689         Node childAction = checkBasePartOfActionRequest(actionRequest);
690
691         Node childServer = childAction.getFirstChild();
692         checkNode(childServer, "server", "server", URN_EXAMPLE_SERVER_FARM);
693
694         Node childServerName = childServer.getFirstChild();
695         checkNode(childServerName, "name", "name", URN_EXAMPLE_SERVER_FARM);
696
697         Node childServerNameTest = childServerName.getFirstChild();
698         assertEquals(childServerNameTest.getNodeValue(), "testServer");
699
700         Node childApplications = childServer.getLastChild();
701         checkNode(childApplications, "applications", "applications", URN_EXAMPLE_SERVER_FARM_2);
702
703         Node childApplication = childApplications.getFirstChild();
704         checkNode(childApplication, "application", "application", URN_EXAMPLE_SERVER_FARM_2);
705
706         Node childApplicationName = childApplication.getFirstChild();
707         checkNode(childApplicationName, "name", "name", URN_EXAMPLE_SERVER_FARM_2);
708
709         Node childApplicationNameTest = childApplicationName.getFirstChild();
710         assertEquals(childApplicationNameTest.getNodeValue(), "testApplication");
711
712         Node childKillAction = childApplication.getLastChild();
713         checkNode(childApplication, "application", "application", URN_EXAMPLE_SERVER_FARM_2);
714         checkNode(childKillAction, KILL_QNAME.getLocalName(), KILL_QNAME.getLocalName(),
715                 KILL_QNAME.getNamespace().toString());
716     }
717
718     @Test
719     public void toActionRequestConflictingInListTest() {
720         QName barInputQname = QName.create(BAR_QNAME, "bar");
721         QName barIdQname = QName.create(BAR_QNAME, "bar-id");
722         Byte barInput = 1;
723
724         List<PathArgument> nodeIdentifiers = new ArrayList<>();
725         nodeIdentifiers.add(NodeIdentifier.create(BAR_QNAME));
726         nodeIdentifiers.add(NodeIdentifierWithPredicates.of(BAR_QNAME, barIdQname, "test"));
727
728         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
729
730         ContainerNode payload = Builders.containerBuilder()
731             .withNodeIdentifier(NodeIdentifier.create(QName.create(barInputQname, "input")))
732             .withChild(ImmutableNodes.leafNode(barInputQname, barInput))
733             .build();
734
735         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
736                 XYZZY_BAR_PATH, domDataTreeIdentifier, payload);
737
738         Node childAction = checkBasePartOfActionRequest(actionRequest);
739
740         Node childBar = childAction.getFirstChild();
741         checkNode(childBar, "bar", "bar", URN_EXAMPLE_CONFLICT);
742
743         Node childBarId = childBar.getFirstChild();
744         checkNode(childBarId, "bar-id", "bar-id", URN_EXAMPLE_CONFLICT);
745
746         Node childTest = childBarId.getFirstChild();
747         assertEquals(childTest.getNodeValue(), "test");
748
749         Node action = childBar.getLastChild();
750         checkNode(action, XYZZY_QNAME.getLocalName(), XYZZY_QNAME.getLocalName(),
751                 XYZZY_QNAME.getNamespace().toString());
752     }
753
754     @Test
755     public void toActionRequestConflictingInContainerTest() {
756         QName fooInputQname = QName.create(FOO_QNAME, "foo");
757
758         List<PathArgument> nodeIdentifiers = new ArrayList<>();
759         nodeIdentifiers.add(NodeIdentifier.create(FOO_QNAME));
760         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
761         ContainerNode payload = initInputAction(fooInputQname, "test");
762
763         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
764                 XYZZY_FOO_PATH, domDataTreeIdentifier, payload);
765
766         Node childAction = checkBasePartOfActionRequest(actionRequest);
767
768         Node childBar = childAction.getFirstChild();
769         checkNode(childBar, "foo", "foo", URN_EXAMPLE_CONFLICT);
770
771         Node action = childBar.getLastChild();
772         checkNode(action, XYZZY_QNAME.getLocalName(), XYZZY_QNAME.getLocalName(),
773                 XYZZY_QNAME.getNamespace().toString());
774     }
775
776     @Test
777     public void toActionRequestChoiceTest() {
778         List<PathArgument> nodeIdentifiers = new ArrayList<>();
779         nodeIdentifiers.add(NodeIdentifier.create(CONFLICT_CHOICE_QNAME));
780         nodeIdentifiers.add(NodeIdentifier.create(CHOICE_CONT_QNAME));
781         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
782         NormalizedNode payload = initEmptyInputAction(CHOICE_ACTION_QNAME);
783
784         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
785                 CHOICE_ACTION_PATH, domDataTreeIdentifier, payload);
786
787         Node childAction = checkBasePartOfActionRequest(actionRequest);
788
789         Node childChoiceCont = childAction.getFirstChild();
790         checkNode(childChoiceCont, "choice-cont", "choice-cont", URN_EXAMPLE_CONFLICT);
791
792         Node action = childChoiceCont.getLastChild();
793         checkNode(action, CHOICE_ACTION_QNAME.getLocalName(), CHOICE_ACTION_QNAME.getLocalName(),
794                 CHOICE_ACTION_QNAME.getNamespace().toString());
795     }
796
797     @Test
798     public void toAugmentedActionRequestListInContainerTest() {
799         QName nameQname = QName.create(INTERFACE_QNAME, "name");
800
801         List<PathArgument> nodeIdentifiers = new ArrayList<>();
802         nodeIdentifiers.add(NodeIdentifier.create(DEVICE_QNAME));
803         nodeIdentifiers.add(NodeIdentifier.create(INTERFACE_QNAME));
804         nodeIdentifiers.add(NodeIdentifierWithPredicates.of(INTERFACE_QNAME, nameQname, "test"));
805
806         DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
807
808         NormalizedNode payload = initEmptyInputAction(INTERFACE_QNAME);
809         NetconfMessage actionRequest = actionNetconfMessageTransformer.toActionRequest(
810                 DISABLE_INTERFACE_PATH, domDataTreeIdentifier, payload);
811
812         Node childAction = checkBasePartOfActionRequest(actionRequest);
813
814         Node childDevice = childAction.getFirstChild();
815         checkNode(childDevice, "device", "device", URN_EXAMPLE_SERVER_FARM);
816
817         Node childInterface = childDevice.getFirstChild();
818         checkNode(childInterface, "interface", "interface", URN_EXAMPLE_SERVER_FARM);
819
820         Node childName = childInterface.getFirstChild();
821         checkNode(childName, "name", "name", nameQname.getNamespace().toString());
822
823         Node childTest = childName.getFirstChild();
824         assertEquals(childTest.getNodeValue(), "test");
825
826         Node action = childInterface.getLastChild();
827         checkNode(action, DISABLE_QNAME.getLocalName(), DISABLE_QNAME.getLocalName(),
828                 DISABLE_QNAME.getNamespace().toString());
829     }
830
831     @Test
832     public void toActionResultTest() throws Exception {
833         var message = new NetconfMessage(XmlUtil.readXmlToDocument("""
834             <rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
835               <reset-finished-at xmlns="urn:example:server-farm">now</reset-finished-at>
836             </rpc-reply>"""));
837         final var actionResult = actionNetconfMessageTransformer.toActionResult(RESET_SERVER_PATH, message);
838         assertNotNull(actionResult);
839         final var containerNode = actionResult.getOutput().orElseThrow();
840         assertNotNull(containerNode);
841         assertEquals("now", containerNode.body().iterator().next().body());
842     }
843
844     @Test
845     public void toActionEmptyBodyWithOutputDefinedResultTest() throws Exception {
846         final var message = new NetconfMessage(XmlUtil.readXmlToDocument("""
847             <rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
848               <ok/>
849             </rpc-reply>"""));
850         final var actionResult =
851             actionNetconfMessageTransformer.toActionResult(CHECK_WITH_OUTPUT_INTERFACE_PATH, message);
852         assertNotNull(actionResult);
853         assertTrue(actionResult.getOutput().isEmpty());
854     }
855
856     @Test
857     public void toActionEmptyBodyWithoutOutputDefinedResultTest() throws Exception {
858         final var message = new NetconfMessage(XmlUtil.readXmlToDocument("""
859             <rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
860               <ok/>
861             </rpc-reply>"""));
862         final var actionResult =
863             actionNetconfMessageTransformer.toActionResult(CHECK_WITHOUT_OUTPUT_INTERFACE_PATH, message);
864         assertNotNull(actionResult);
865         assertTrue(actionResult.getOutput().isEmpty());
866     }
867
868     @Test
869     public void getTwoNonOverlappingFieldsTest() throws IOException, SAXException {
870         // preparation of the fields
871         final var parentYiid = YangInstanceIdentifier.of(NetconfState.QNAME);
872         final var netconfStartTimeField = YangInstanceIdentifier.of(Statistics.QNAME,
873                 QName.create(Statistics.QNAME, "netconf-start-time"));
874         final var datastoresField = YangInstanceIdentifier.of(Datastores.QNAME);
875
876         // building filter structure and NETCONF message
877         final var filterStructure = toFilterStructure(
878             List.of(FieldsFilter.of(parentYiid, List.of(netconfStartTimeField, datastoresField))), SCHEMA);
879         final var netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
880                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filterStructure));
881
882         // testing
883         assertSimilarXml(netconfMessage, """
884             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
885               <get>
886                 <filter type="subtree">
887                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
888                     <statistics>
889                       <netconf-start-time/>
890                     </statistics>
891                     <datastores/>
892                   </netconf-state>
893                 </filter>
894               </get>
895             </rpc>""");
896     }
897
898     @Test
899     public void getOverlappingFieldsTest() throws IOException, SAXException {
900         // preparation of the fields
901         final var parentYiid = YangInstanceIdentifier.of(NetconfState.QNAME);
902         final var capabilitiesField = YangInstanceIdentifier.of(Capabilities.QNAME);
903         final var capabilityField = YangInstanceIdentifier.of(Capabilities.QNAME,
904                 QName.create(Capabilities.QNAME, "capability"));
905         final var datastoreField = YangInstanceIdentifier.of(Datastores.QNAME);
906         final var locksFields = YangInstanceIdentifier.of(toId(Datastores.QNAME),
907                 toId(Datastore.QNAME), NodeIdentifierWithPredicates.of(Datastore.QNAME), toId(Locks.QNAME));
908
909         // building filter structure and NETCONF message
910         final var filterStructure = toFilterStructure(
911                 List.of(FieldsFilter.of(parentYiid,
912                     List.of(capabilitiesField, capabilityField, datastoreField, locksFields))),
913                 SCHEMA);
914         final var netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
915                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filterStructure));
916
917         // testing
918         assertSimilarXml(netconfMessage, """
919             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
920               <get>
921                 <filter type="subtree">
922                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
923                     <capabilities/>
924                     <datastores/>
925                   </netconf-state>
926                 </filter>
927               </get>
928             </rpc>""");
929     }
930
931     @Test
932     public void getOverlappingFieldsWithEmptyFieldTest() throws IOException, SAXException {
933         // preparation of the fields
934         final var parentYiid = YangInstanceIdentifier.of(NetconfState.QNAME);
935         final var capabilitiesField = YangInstanceIdentifier.of(Capabilities.QNAME);
936
937         // building filter structure and NETCONF message
938         final var filterStructure = toFilterStructure(
939                 List.of(FieldsFilter.of(parentYiid, List.of(capabilitiesField, YangInstanceIdentifier.of()))),
940                 SCHEMA);
941         final var netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
942                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filterStructure));
943
944         // testing
945         assertSimilarXml(netconfMessage, """
946             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
947             <get>
948             <filter type="subtree">
949             <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"/>
950             </filter>
951             </get>
952             </rpc>""");
953     }
954
955     @Test
956     public void getSpecificFieldsUnderListTest() throws IOException, SAXException {
957         // preparation of the fields
958         final var parentYiid = YangInstanceIdentifier.of(toId(NetconfState.QNAME),
959                 toId(Schemas.QNAME), toId(Schema.QNAME), NodeIdentifierWithPredicates.of(Schema.QNAME));
960         final var versionField = YangInstanceIdentifier.of(
961                 QName.create(Schema.QNAME, "version"));
962         final var identifierField = YangInstanceIdentifier.of(
963                 QName.create(Schema.QNAME, "namespace"));
964
965         // building filter structure and NETCONF message
966         final var filterStructure = toFilterStructure(
967             List.of(FieldsFilter.of(parentYiid, List.of(versionField, identifierField))), SCHEMA);
968         final var netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
969                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filterStructure));
970
971         // testing
972         assertSimilarXml(netconfMessage, """
973             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
974               <get>
975                 <filter type="subtree">
976                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
977                     <schemas>
978                       <schema>
979                         <version/>
980                         <namespace/>
981                         <identifier/>
982                         <format/>
983                       </schema>
984                     </schemas>
985                   </netconf-state>
986                 </filter>
987               </get>
988             </rpc>""");
989     }
990
991     @Test
992     public void getSpecificFieldsUnderMultipleLists() throws IOException, SAXException {
993         // preparation of the fields
994         final var parentYiid = YangInstanceIdentifier.of(NetconfState.QNAME, Datastores.QNAME);
995         final var partialLockYiid = YangInstanceIdentifier.of(toId(Datastore.QNAME),
996                 NodeIdentifierWithPredicates.of(Datastore.QNAME), toId(Locks.QNAME),
997                 toId(QName.create(Locks.QNAME, "lock-type").intern()), toId(PartialLock.QNAME),
998                 NodeIdentifierWithPredicates.of(PartialLock.QNAME));
999         final var lockedTimeField = partialLockYiid.node(
1000                 QName.create(Locks.QNAME, "locked-time").intern());
1001         final var lockedBySessionField = partialLockYiid.node(
1002                 QName.create(Locks.QNAME, "locked-by-session").intern());
1003
1004         // building filter structure and NETCONF message
1005         final var filterStructure = toFilterStructure(
1006             List.of(FieldsFilter.of(parentYiid, List.of(lockedTimeField, lockedBySessionField))),
1007             SCHEMA);
1008         final var netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
1009                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filterStructure));
1010
1011         // testing
1012         assertSimilarXml(netconfMessage, """
1013             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
1014               <get>
1015                 <filter type="subtree">
1016                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
1017                     <datastores>
1018                       <datastore>
1019                         <locks>
1020                           <partial-lock>
1021                             <locked-time/>
1022                             <locked-by-session/>
1023                             <lock-id/>
1024                           </partial-lock>
1025                         </locks>
1026                         <name/>
1027                       </datastore>
1028                     </datastores>
1029                   </netconf-state>
1030                 </filter>
1031               </get>
1032             </rpc>""");
1033     }
1034
1035     @Test
1036     public void getWholeListsUsingFieldsTest() throws IOException, SAXException {
1037         // preparation of the fields
1038         final var parentYiid = YangInstanceIdentifier.of(NetconfState.QNAME);
1039         final var datastoreListField = YangInstanceIdentifier.of(toId(Datastores.QNAME),
1040                 toId(Datastore.QNAME), NodeIdentifierWithPredicates.of(Datastore.QNAME));
1041         final var sessionListField = YangInstanceIdentifier.of(toId(Sessions.QNAME),
1042                 toId(Session.QNAME), NodeIdentifierWithPredicates.of(Session.QNAME));
1043
1044         // building filter structure and NETCONF message
1045         final var filterStructure = toFilterStructure(
1046                 List.of(FieldsFilter.of(parentYiid, List.of(datastoreListField, sessionListField))), SCHEMA);
1047         final var netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
1048                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filterStructure));
1049
1050         // testing
1051         assertSimilarXml(netconfMessage, """
1052             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
1053               <get>
1054                 <filter type="subtree">
1055                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
1056                     <datastores>
1057                       <datastore/>
1058                     </datastores>
1059                     <sessions>
1060                       <session/>
1061                     </sessions>
1062                   </netconf-state>
1063                 </filter>
1064               </get>
1065             </rpc>""");
1066     }
1067
1068     @Test
1069     public void getSpecificListEntriesWithSpecificFieldsTest() throws IOException, SAXException {
1070         // preparation of the fields
1071         final var parentYiid = YangInstanceIdentifier.of(NetconfState.QNAME, Sessions.QNAME);
1072         final var sessionId = QName.create(Session.QNAME, "session-id").intern();
1073         final var session1Field = YangInstanceIdentifier.of(toId(Session.QNAME),
1074                 NodeIdentifierWithPredicates.of(Session.QNAME, sessionId, 1));
1075         final var session2TransportField = YangInstanceIdentifier.of(toId(Session.QNAME),
1076                 NodeIdentifierWithPredicates.of(Session.QNAME, sessionId, 2),
1077                 toId(QName.create(Session.QNAME, "transport").intern()));
1078
1079         // building filter structure and NETCONF message
1080         final var filterStructure = toFilterStructure(
1081                 List.of(FieldsFilter.of(parentYiid, List.of(session1Field, session2TransportField))), SCHEMA);
1082         final var netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
1083                 NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, filterStructure));
1084
1085         // testing
1086         assertSimilarXml(netconfMessage, """
1087             <rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
1088               <get>
1089                 <filter type="subtree">
1090                   <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
1091                     <sessions>
1092                       <session>
1093                         <session-id>1</session-id>
1094                       </session>
1095                       <session>
1096                         <session-id>2</session-id>
1097                         <transport/>
1098                       </session>
1099                     </sessions>
1100                   </netconf-state>
1101                 </filter>
1102               </get>
1103             </rpc>""");
1104     }
1105
1106     @Test
1107     // Proof that YANGTOOLS-1362 works on DOM level
1108     public void testConfigChangeToNotification() throws SAXException, IOException {
1109         final var message = new NetconfMessage(XmlUtil.readXmlToDocument(
1110             "<notification xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n"
1111             + " <eventTime>2021-11-11T11:26:16Z</eventTime> \n"
1112             + "  <netconf-config-change xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-notifications\">\n"
1113             + "     <changed-by> \n"
1114             + "       <username>root</username> \n"
1115             + "       <session-id>3</session-id> \n"
1116             + "     </changed-by> \n"
1117             + "     <datastore>running</datastore> \n"
1118             + "     <edit> \n"
1119             + "        <target xmlns:ncm=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\">/ncm:netconf-state"
1120             + "/ncm:datastores/ncm:datastore[ncm:name='running']</target>\n"
1121             + "        <operation>replace</operation> \n"
1122             + "     </edit> \n"
1123             + "  </netconf-config-change> \n"
1124             + "</notification>"));
1125
1126         final var change = netconfMessageTransformer.toNotification(message).getBody();
1127         final var editList = change.getChildByArg(new NodeIdentifier(Edit.QNAME));
1128         assertThat(editList, instanceOf(UnkeyedListNode.class));
1129         final var edits = ((UnkeyedListNode) editList).body();
1130         assertEquals(1, edits.size());
1131         final var edit = edits.iterator().next();
1132         final var target = edit.getChildByArg(new NodeIdentifier(QName.create(Edit.QNAME, "target"))).body();
1133         assertThat(target, instanceOf(YangInstanceIdentifier.class));
1134
1135         final var args = ((YangInstanceIdentifier) target).getPathArguments();
1136         assertEquals(4, args.size());
1137     }
1138
1139     private static void checkAction(final QName actionQname, final Node action , final String inputLocalName,
1140             final String inputNodeName, final String inputValue) {
1141         checkNode(action, actionQname.getLocalName(), actionQname.getLocalName(),
1142                 actionQname.getNamespace().toString());
1143
1144         Node childResetAt = action.getFirstChild();
1145         checkNode(childResetAt, inputLocalName, inputNodeName, actionQname.getNamespace().toString());
1146
1147         Node firstChild = childResetAt.getFirstChild();
1148         assertEquals(firstChild.getNodeValue(), inputValue);
1149     }
1150
1151     private static Node checkBasePartOfActionRequest(final NetconfMessage actionRequest) {
1152         Node baseRpc = actionRequest.getDocument().getFirstChild();
1153         checkNode(baseRpc, "rpc", "rpc", NormalizedDataUtil.NETCONF_QNAME.getNamespace().toString());
1154         assertTrue(baseRpc.getLocalName().equals("rpc"));
1155         assertTrue(baseRpc.getNodeName().equals("rpc"));
1156
1157         Node messageId = baseRpc.getAttributes().getNamedItem("message-id");
1158         assertNotNull(messageId);
1159         assertTrue(messageId.getNodeValue().contains("m-"));
1160         Node childAction = baseRpc.getFirstChild();
1161
1162         checkNode(childAction, "action", "action", "urn:ietf:params:xml:ns:yang:1");
1163         return childAction;
1164     }
1165
1166     private static DOMDataTreeIdentifier prepareDataTreeId(final List<PathArgument> nodeIdentifiers) {
1167         YangInstanceIdentifier yangInstanceIdentifier =
1168                 YangInstanceIdentifier.builder().append(nodeIdentifiers).build();
1169         DOMDataTreeIdentifier domDataTreeIdentifier =
1170                 new DOMDataTreeIdentifier(org.opendaylight.mdsal.common.api.LogicalDatastoreType.CONFIGURATION,
1171                         yangInstanceIdentifier);
1172         return domDataTreeIdentifier;
1173     }
1174
1175     private static ContainerNode initInputAction(final QName qname, final String value) {
1176         return Builders.containerBuilder()
1177             .withNodeIdentifier(NodeIdentifier.create(QName.create(qname, "input")))
1178             .withChild(ImmutableNodes.leafNode(qname, value))
1179             .build();
1180     }
1181
1182     private static ContainerNode initEmptyInputAction(final QName qname) {
1183         return Builders.containerBuilder()
1184             .withNodeIdentifier(NodeIdentifier.create(QName.create(qname, "input")))
1185             .build();
1186     }
1187
1188     private static void checkNode(final Node childServer, final String expectedLocalName, final String expectedNodeName,
1189             final String expectedNamespace) {
1190         assertNotNull(childServer);
1191         assertEquals(childServer.getLocalName(), expectedLocalName);
1192         assertEquals(childServer.getNodeName(), expectedNodeName);
1193         assertEquals(childServer.getNamespaceURI(), expectedNamespace);
1194     }
1195 }