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