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