Bump upstreams
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / streams / DataTreeChangeStreamTest.java
1 /*
2  * Copyright (c) 2017 Red Hat, 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.restconf.nb.rfc8040.streams;
9
10 import static org.hamcrest.CoreMatchers.allOf;
11 import static org.hamcrest.CoreMatchers.containsString;
12 import static org.hamcrest.MatcherAssert.assertThat;
13 import static org.junit.Assert.fail;
14
15 import com.google.common.util.concurrent.Uninterruptibles;
16 import java.io.IOException;
17 import java.net.URI;
18 import java.net.URISyntaxException;
19 import java.nio.file.Files;
20 import java.nio.file.Paths;
21 import java.util.concurrent.CountDownLatch;
22 import java.util.concurrent.TimeUnit;
23 import org.json.JSONException;
24 import org.json.JSONObject;
25 import org.junit.AfterClass;
26 import org.junit.Before;
27 import org.junit.BeforeClass;
28 import org.junit.Test;
29 import org.opendaylight.mdsal.binding.api.DataBroker;
30 import org.opendaylight.mdsal.binding.dom.adapter.test.AbstractConcurrentDataBrokerTest;
31 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
32 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
33 import org.opendaylight.mdsal.dom.api.DOMDataBroker.DataTreeChangeExtension;
34 import org.opendaylight.restconf.api.query.ChangedLeafNodesOnlyParam;
35 import org.opendaylight.restconf.api.query.ChildNodesOnlyParam;
36 import org.opendaylight.restconf.api.query.LeafNodesOnlyParam;
37 import org.opendaylight.restconf.api.query.SkipNotificationDataParam;
38 import org.opendaylight.restconf.server.api.DatabindContext;
39 import org.opendaylight.restconf.server.api.EventStreamGetParams;
40 import org.opendaylight.restconf.server.mdsal.MdsalRestconfStreamRegistry;
41 import org.opendaylight.restconf.server.mdsal.streams.dtcl.DataTreeChangeSource;
42 import org.opendaylight.restconf.server.spi.DatabindProvider;
43 import org.opendaylight.restconf.server.spi.RestconfStream;
44 import org.opendaylight.restconf.server.spi.RestconfStream.EncodingName;
45 import org.opendaylight.restconf.server.spi.RestconfStream.Sender;
46 import org.opendaylight.yang.gen.v1.augment.instance.identifier.patch.module.rev220218.PatchCont1Builder;
47 import org.opendaylight.yang.gen.v1.augment.instance.identifier.patch.module.rev220218.patch.cont.patch.choice1.PatchCase1Builder;
48 import org.opendaylight.yang.gen.v1.augment.instance.identifier.patch.module.rev220218.patch.cont.patch.choice2.PatchCase11Builder;
49 import org.opendaylight.yang.gen.v1.augment.instance.identifier.patch.module.rev220218.patch.cont.patch.choice2.patch.case11.patch.sub.choice11.PatchSubCase11Builder;
50 import org.opendaylight.yang.gen.v1.augment.instance.identifier.patch.module.rev220218.patch.cont.patch.choice2.patch.case11.patch.sub.choice11.patch.sub.case11.patch.sub.sub.choice11.PatchSubSubCase11Builder;
51 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.PatchCont;
52 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.PatchContBuilder;
53 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1;
54 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1Builder;
55 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1Key;
56 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev231103.NotificationOutputTypeGrouping.NotificationOutputType;
57 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
58 import org.opendaylight.yangtools.yang.binding.util.BindingMap;
59 import org.opendaylight.yangtools.yang.common.QName;
60 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
61 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
62 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
63 import org.skyscreamer.jsonassert.JSONAssert;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66 import org.xmlunit.assertj.XmlAssert;
67
68 public class DataTreeChangeStreamTest extends AbstractConcurrentDataBrokerTest {
69     private static final class TestHandler implements Sender {
70         private CountDownLatch notificationLatch = new CountDownLatch(1);
71         private volatile String lastNotification;
72
73         @Override
74         public void endOfStream() {
75             // No-op
76         }
77
78         @Override
79         public void sendDataMessage(final String data) {
80             lastNotification = data;
81             notificationLatch.countDown();
82         }
83
84         void assertGot(final String json) throws Exception {
85             // FIXME: use awaitility
86             if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 500, TimeUnit.SECONDS)) {
87                 fail("Timed out waiting for notification for: " + json);
88             }
89
90             LOG.info("lastNotification: {}", lastNotification);
91             final String withFakeDate = withFakeDate(lastNotification);
92             LOG.info("Comparing: \n{}\n{}", json, withFakeDate);
93
94             JSONAssert.assertEquals(json, withFakeDate, false);
95             lastNotification = null;
96             notificationLatch = new CountDownLatch(1);
97         }
98
99         void assertXmlSimilar(final String xml) throws Exception {
100             awaitUntilNotification(xml);
101
102             LOG.info("lastNotification: {}", lastNotification);
103             final String withFakeDate = withFakeXmlDate(lastNotification);
104             LOG.info("Comparing: \n{}\n{}", xml, withFakeDate);
105
106             XmlAssert.assertThat(xml).and(withFakeDate).ignoreWhitespace().ignoreChildNodesOrder().areSimilar();
107             lastNotification = null;
108             notificationLatch = new CountDownLatch(1);
109         }
110
111         String awaitUntilNotification(final String xml) {
112             // FIXME: use awaitility
113             if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 500, TimeUnit.SECONDS)) {
114                 fail("Timed out waiting for notification for: " + xml);
115             }
116             return lastNotification;
117         }
118     }
119
120     private static final Logger LOG = LoggerFactory.getLogger(DataTreeChangeStreamTest.class);
121
122     private static final String JSON_NOTIF_LEAVES_CREATE = "/listener-adapter-test/notif-leaves-create.json";
123     private static final String JSON_NOTIF_LEAVES_UPDATE =  "/listener-adapter-test/notif-leaves-update.json";
124     private static final String JSON_NOTIF_LEAVES_DELETE = "/listener-adapter-test/notif-leaves-delete.json";
125     private static final String JSON_NOTIF_CHANGED_LEAVES_CREATE =
126             "/listener-adapter-test/notif-changed-leaves-create.json";
127     private static final String JSON_NOTIF_CHANGED_LEAVES_UPDATE =
128             "/listener-adapter-test/notif-changed-leaves-update.json";
129     private static final String JSON_NOTIF_CHANGED_LEAVES_DELETE =
130             "/listener-adapter-test/notif-changed-leaves-delete.json";
131     private static final String JSON_NOTIF_CHILD_NODES_ONLY_CREATE =
132             "/listener-adapter-test/notif-child-nodes-only-create.json";
133     private static final String JSON_NOTIF_CHILD_NODES_ONLY_UPDATE1 =
134         "/listener-adapter-test/notif-child-nodes-only-update1.json";
135     private static final String JSON_NOTIF_CHILD_NODES_ONLY_UPDATE2 =
136         "/listener-adapter-test/notif-child-nodes-only-update2.json";
137     private static final String JSON_NOTIF_CHILD_NODES_ONLY_DELETE =
138             "/listener-adapter-test/notif-child-nodes-only-delete.json";
139
140     private static final String XML_NOTIF_LEAVES_CREATE = "/listener-adapter-test/notif-leaves-create.xml";
141     private static final String XML_NOTIF_LEAVES_UPDATE =  "/listener-adapter-test/notif-leaves-update.xml";
142     private static final String XML_NOTIF_LEAVES_DELETE =  "/listener-adapter-test/notif-leaves-delete.xml";
143     private static final String XML_NOTIF_CHANGED_LEAVES_CREATE =
144             "/listener-adapter-test/notif-changed-leaves-create.xml";
145     private static final String XML_NOTIF_CHANGED_LEAVES_UPDATE =
146             "/listener-adapter-test/notif-changed-leaves-update.xml";
147     private static final String XML_NOTIF_CHANGED_LEAVES_DELETE =
148             "/listener-adapter-test/notif-changed-leaves-delete.xml";
149     private static final String XML_NOTIF_CHILD_NODES_ONLY_CREATE =
150         "/listener-adapter-test/notif-child-nodes-only-create.xml";
151     private static final String XML_NOTIF_CHILD_NODES_ONLY_UPDATE1 =
152         "/listener-adapter-test/notif-child-nodes-only-update1.xml";
153     private static final String XML_NOTIF_CHILD_NODES_ONLY_UPDATE2 =
154         "/listener-adapter-test/notif-child-nodes-only-update2.xml";
155     private static final String XML_NOTIF_CHILD_NODES_ONLY_DELETE =
156         "/listener-adapter-test/notif-child-nodes-only-delete.xml";
157
158     private static final String JSON_NOTIF_CONT_CREATE = "/listener-adapter-test/notif-cont-create.json";
159     private static final String JSON_NOTIF_CONT_UPDATE = "/listener-adapter-test/notif-cont-update.json";
160     private static final String JSON_NOTIF_CONT_DELETE = "/listener-adapter-test/notif-cont-delete.json";
161     private static final String JSON_NOTIF_LIST_CREATE = "/listener-adapter-test/notif-list-create.json";
162     private static final String JSON_NOTIF_LIST_UPDATE = "/listener-adapter-test/notif-list-update.json";
163     private static final String JSON_NOTIF_LIST_DELETE = "/listener-adapter-test/notif-list-delete.json";
164     private static final String JSON_NOTIF_WITHOUT_DATA_CONT_CREATE =
165             "/listener-adapter-test/notif-without-data-cont-create.json";
166     private static final String JSON_NOTIF_WITHOUT_DATA_CONT_UPDATE =
167             "/listener-adapter-test/notif-without-data-cont-update.json";
168     private static final String JSON_NOTIF_WITHOUT_DATA_CONT_DELETE =
169             "/listener-adapter-test/notif-without-data-cont-delete.json";
170     private static final String JSON_NOTIF_WITHOUT_DATA_LIST_CREATE =
171             "/listener-adapter-test/notif-without-data-list-create.json";
172     private static final String JSON_NOTIF_WITHOUT_DATA_LIST_UPDATE =
173             "/listener-adapter-test/notif-without-data-list-update.json";
174     private static final String JSON_NOTIF_WITHOUT_DATA_LIST_DELETE =
175             "/listener-adapter-test/notif-without-data-list-delete.json";
176
177     private static final String XML_NOTIF_CONT_CREATE = "/listener-adapter-test/notif-cont-create.xml";
178     private static final String XML_NOTIF_CONT_UPDATE = "/listener-adapter-test/notif-cont-update.xml";
179     private static final String XML_NOTIF_CONT_DELETE = "/listener-adapter-test/notif-cont-delete.xml";
180     private static final String XML_NOTIF_LIST_CREATE = "/listener-adapter-test/notif-list-create.xml";
181     private static final String XML_NOTIF_LIST_UPDATE = "/listener-adapter-test/notif-list-update.xml";
182     private static final String XML_NOTIF_LIST_DELETE = "/listener-adapter-test/notif-list-delete.xml";
183     private static final String XML_NOTIF_WITHOUT_DATA_CONT_CREATE =
184             "/listener-adapter-test/notif-without-data-cont-create.xml";
185     private static final String XML_NOTIF_WITHOUT_DATA_CONT_UPDATE =
186             "/listener-adapter-test/notif-without-data-cont-update.xml";
187     private static final String XML_NOTIF_WITHOUT_DATA_CONT_DELETE =
188             "/listener-adapter-test/notif-without-data-cont-delete.xml";
189     private static final String XML_NOTIF_WITHOUT_DATA_LIST_CREATE =
190             "/listener-adapter-test/notif-without-data-list-create.xml";
191     private static final String XML_NOTIF_WITHOUT_DATA_LIST_UPDATE =
192             "/listener-adapter-test/notif-without-data-list-update.xml";
193     private static final String XML_NOTIF_WITHOUT_DATA_LIST_DELETE =
194             "/listener-adapter-test/notif-without-data-list-delete.xml";
195
196     private static final YangInstanceIdentifier PATCH_CONT_YIID = YangInstanceIdentifier.of(PatchCont.QNAME);
197
198     private static final YangInstanceIdentifier MY_LIST1_YIID = YangInstanceIdentifier.builder()
199             .node(PatchCont.QNAME)
200             .node(MyList1.QNAME)
201             .nodeWithKey(MyList1.QNAME, QName.create(PatchCont.QNAME.getModule(), "name"), "Althea")
202             .build();
203
204     private static EffectiveModelContext SCHEMA_CONTEXT;
205
206     private DataBroker dataBroker;
207     private DOMDataBroker domDataBroker;
208     private DatabindProvider databindProvider;
209     private RestconfStream.Registry streamRegistry;
210
211     @BeforeClass
212     public static void beforeClass() {
213         SCHEMA_CONTEXT = YangParserTestUtils.parseYangResourceDirectory("/instanceidentifier/yang");
214     }
215
216     @AfterClass
217     public static void afterClass() {
218         SCHEMA_CONTEXT = null;
219     }
220
221     @Before
222     public void setUp() throws Exception {
223         dataBroker = getDataBroker();
224         domDataBroker = getDomBroker();
225         databindProvider = () -> DatabindContext.ofModel(SCHEMA_CONTEXT);
226         streamRegistry = new MdsalRestconfStreamRegistry(domDataBroker);
227     }
228
229     TestHandler createHandler(final YangInstanceIdentifier path, final String streamName,
230             final NotificationOutputType outputType, final boolean leafNodesOnly, final boolean skipNotificationData,
231             final boolean changedLeafNodesOnly, final boolean childNodesOnly) throws Exception {
232         final var stream = streamRegistry.createStream(URI.create("baseURI"),
233             new DataTreeChangeSource(databindProvider,
234                 domDataBroker.extension(DataTreeChangeExtension.class),
235                 LogicalDatastoreType.CONFIGURATION, path), "test")
236             .getOrThrow();
237         final var handler = new TestHandler();
238         stream.addSubscriber(handler,
239             switch (outputType) {
240                 case JSON -> EncodingName.RFC8040_JSON;
241                 case XML -> EncodingName.RFC8040_XML;
242             },
243             new EventStreamGetParams(null, null, null,
244                 leafNodesOnly ? LeafNodesOnlyParam.of(true) : null,
245                 skipNotificationData ? SkipNotificationDataParam.of(true) : null,
246                 changedLeafNodesOnly ? ChangedLeafNodesOnlyParam.of(true) : null,
247                 childNodesOnly ? ChildNodesOnlyParam.of(true) : null));
248         return handler;
249     }
250
251     @Test
252     public void testJsonNotifsLeaves() throws Exception {
253         final var handler = createHandler(PATCH_CONT_YIID, "Casey", NotificationOutputType.JSON,
254             true, false, false, false);
255
256         var writeTransaction = dataBroker.newWriteOnlyTransaction();
257         final var iid = InstanceIdentifier.create(PatchCont.class);
258         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
259             .addAugmentation(new PatchCont1Builder()
260                 .setPatchChoice1(new PatchCase1Builder().setCaseLeaf1("ChoiceLeaf").build())
261                 .setLeaf1("AugmentLeaf")
262                 .build())
263             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build()))
264             .build());
265         writeTransaction.commit();
266         handler.assertGot(getNotifJson(JSON_NOTIF_LEAVES_CREATE));
267
268         writeTransaction = dataBroker.newWriteOnlyTransaction();
269         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
270             .addAugmentation(new PatchCont1Builder()
271                 .setPatchChoice1(new PatchCase1Builder().setCaseLeaf1("ChoiceUpdate").build())
272                 .setLeaf1("AugmentLeaf")
273                 .build())
274             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf12("Bertha").setName("Althea").build()))
275             .build());
276         writeTransaction.commit();
277         handler.assertGot(getNotifJson(JSON_NOTIF_LEAVES_UPDATE));
278
279         writeTransaction = dataBroker.newWriteOnlyTransaction();
280         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
281         writeTransaction.commit();
282         handler.assertGot(getNotifJson(JSON_NOTIF_LEAVES_DELETE));
283     }
284
285     @Test
286     public void testJsonNotifsChangedLeaves() throws Exception {
287         final var handler = createHandler(PATCH_CONT_YIID, "Casey", NotificationOutputType.JSON, false, false, true,
288             false);
289
290         var writeTransaction = dataBroker.newWriteOnlyTransaction();
291         final var iid = InstanceIdentifier.create(PatchCont.class);
292         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
293             .addAugmentation(new PatchCont1Builder()
294                 .setPatchChoice2(new PatchCase11Builder()
295                     .setPatchSubChoice11(new PatchSubCase11Builder()
296                         .setPatchSubSubChoice11(new PatchSubSubCase11Builder().setCaseLeaf11("ChoiceLeaf").build())
297                         .build())
298                     .build())
299                 .setLeaf1("AugmentLeaf")
300                 .build())
301             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build()))
302             .build());
303         writeTransaction.commit();
304         handler.assertGot(getNotifJson(JSON_NOTIF_CHANGED_LEAVES_CREATE));
305
306         writeTransaction = dataBroker.newWriteOnlyTransaction();
307         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
308             .addAugmentation(new PatchCont1Builder()
309                 .setPatchChoice2(new PatchCase11Builder()
310                     .setPatchSubChoice11(new PatchSubCase11Builder()
311                         .setPatchSubSubChoice11(new PatchSubSubCase11Builder().setCaseLeaf11("ChoiceUpdate").build())
312                         .build())
313                     .build())
314                 .setLeaf1("AugmentLeaf")
315                 .build())
316             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf12("Bertha").setName("Althea").build()))
317             .build());
318         writeTransaction.commit();
319         handler.assertGot(getNotifJson(JSON_NOTIF_CHANGED_LEAVES_UPDATE));
320
321         writeTransaction = dataBroker.newWriteOnlyTransaction();
322         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
323         writeTransaction.commit();
324         handler.assertGot(getNotifJson(JSON_NOTIF_CHANGED_LEAVES_DELETE));
325     }
326
327     @Test
328     public void testJsonChildNodesOnly() throws Exception {
329         final var handler = createHandler(PATCH_CONT_YIID, "Casey", NotificationOutputType.JSON, false, false, false,
330             true);
331
332         final var iid = InstanceIdentifier.create(PatchCont.class).child(MyList1.class, new MyList1Key("Althea"));
333         var writeTransaction = dataBroker.newWriteOnlyTransaction();
334         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid,
335             new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build());
336         writeTransaction.commit();
337         handler.assertGot(getNotifJson(JSON_NOTIF_CHILD_NODES_ONLY_CREATE));
338
339         writeTransaction = dataBroker.newWriteOnlyTransaction();
340         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid,
341             new MyList1Builder().setMyLeaf11("Bertha").setName("Althea").build());
342         writeTransaction.commit();
343         handler.assertGot(getNotifJson(JSON_NOTIF_CHILD_NODES_ONLY_UPDATE1));
344
345         writeTransaction = dataBroker.newWriteOnlyTransaction();
346         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid,
347             new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build());
348         writeTransaction.commit();
349         handler.assertGot(getNotifJson(JSON_NOTIF_CHILD_NODES_ONLY_UPDATE2));
350
351         writeTransaction = dataBroker.newWriteOnlyTransaction();
352         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
353         writeTransaction.commit();
354         handler.assertGot(getNotifJson(JSON_NOTIF_CHILD_NODES_ONLY_DELETE));
355     }
356
357     @Test
358     public void testXmlLeavesOnly() throws Exception {
359         final var handler = createHandler(PATCH_CONT_YIID, "Casey", NotificationOutputType.XML, true, false, false,
360             false);
361
362         var writeTransaction = dataBroker.newWriteOnlyTransaction();
363         final var iid = InstanceIdentifier.create(PatchCont.class);
364         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
365             .addAugmentation(new PatchCont1Builder()
366                 .setPatchChoice1(new PatchCase1Builder().setCaseLeaf1("ChoiceLeaf").build())
367                 .setLeaf1("AugmentLeaf")
368                 .build())
369             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build()))
370             .build());
371         writeTransaction.commit();
372         handler.assertXmlSimilar(getResultXml(XML_NOTIF_LEAVES_CREATE));
373
374         writeTransaction = dataBroker.newWriteOnlyTransaction();
375         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
376             .addAugmentation(new PatchCont1Builder()
377                 .setPatchChoice1(new PatchCase1Builder().setCaseLeaf1("ChoiceUpdate").build())
378                 .setLeaf1("AugmentLeaf")
379                 .build())
380             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf12("Bertha").setName("Althea").build()))
381             .build());
382         writeTransaction.commit();
383         handler.assertXmlSimilar(getResultXml(XML_NOTIF_LEAVES_UPDATE));
384
385         writeTransaction = dataBroker.newWriteOnlyTransaction();
386         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
387         writeTransaction.commit();
388
389         // xmlunit cannot compare deeper children it seems out of the box so just check the iid encoding
390         final String notification = handler.awaitUntilNotification("");
391         assertThat(notification, allOf(
392             containsString("<path xmlns:a=\"instance:identifier:patch:module\">/a:patch-cont"
393                 + "/a:my-list1[a:name='Althea']/a:my-leaf11</path>"),
394             containsString("<path xmlns:a=\"instance:identifier:patch:module\">/a:patch-cont"
395                 + "/a:my-list1[a:name='Althea']/a:my-leaf12</path>"),
396             containsString("<path xmlns:a=\"instance:identifier:patch:module\">/a:patch-cont"
397                 + "/a:my-list1[a:name='Althea']/a:name</path>"),
398             containsString("<path xmlns:a=\"instance:identifier:patch:module\" "
399                 + "xmlns:b=\"augment:instance:identifier:patch:module\">/a:patch-cont/b:leaf1</path>"),
400             containsString("<path xmlns:a=\"instance:identifier:patch:module\" "
401                 + "xmlns:b=\"augment:instance:identifier:patch:module\">/a:patch-cont/b:case-leaf1</path>")));
402     }
403
404     @Test
405     public void testXmlChangedLeavesOnly() throws Exception {
406         final var handler = createHandler(PATCH_CONT_YIID, "Casey", NotificationOutputType.XML, false, false, true,
407             false);
408
409         var writeTransaction = dataBroker.newWriteOnlyTransaction();
410         final var iid = InstanceIdentifier.create(PatchCont.class);
411         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
412             .addAugmentation(new PatchCont1Builder()
413                 .setPatchChoice2(new PatchCase11Builder()
414                     .setPatchSubChoice11(new PatchSubCase11Builder()
415                         .setPatchSubSubChoice11(new PatchSubSubCase11Builder().setCaseLeaf11("ChoiceLeaf").build())
416                         .build())
417                     .build())
418                 .setLeaf1("AugmentLeaf")
419                 .build())
420             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build()))
421             .build());
422         writeTransaction.commit();
423         handler.assertXmlSimilar(getResultXml(XML_NOTIF_CHANGED_LEAVES_CREATE));
424
425         writeTransaction = dataBroker.newWriteOnlyTransaction();
426         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
427             .addAugmentation(new PatchCont1Builder()
428                 .setPatchChoice2(new PatchCase11Builder()
429                     .setPatchSubChoice11(new PatchSubCase11Builder()
430                         .setPatchSubSubChoice11(new PatchSubSubCase11Builder().setCaseLeaf11("ChoiceUpdate").build())
431                         .build())
432                     .build())
433                 .setLeaf1("AugmentLeaf")
434                 .build())
435             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf12("Bertha").setName("Althea").build()))
436             .build());
437         writeTransaction.commit();
438         handler.assertXmlSimilar(getResultXml(XML_NOTIF_CHANGED_LEAVES_UPDATE));
439
440         writeTransaction = dataBroker.newWriteOnlyTransaction();
441         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
442         writeTransaction.commit();
443
444         // xmlunit cannot compare deeper children it seems out of the box so just check the iid encoding
445         final String notification = handler.awaitUntilNotification("");
446         assertThat(notification, allOf(
447             containsString("<path xmlns:a=\"instance:identifier:patch:module\">/a:patch-cont"
448                 + "/a:my-list1[a:name='Althea']/a:my-leaf11</path>"),
449             containsString("<path xmlns:a=\"instance:identifier:patch:module\">/a:patch-cont"
450                 + "/a:my-list1[a:name='Althea']/a:my-leaf12</path>"),
451             containsString("<path xmlns:a=\"instance:identifier:patch:module\">/a:patch-cont"
452                 + "/a:my-list1[a:name='Althea']/a:name</path>"),
453             containsString("<path xmlns:a=\"instance:identifier:patch:module\" "
454                 + "xmlns:b=\"augment:instance:identifier:patch:module\">/a:patch-cont/b:leaf1</path>"),
455             containsString("<path xmlns:a=\"instance:identifier:patch:module\" "
456                 + "xmlns:b=\"augment:instance:identifier:patch:module\">/a:patch-cont/b:case-leaf11</path>")));
457     }
458
459     @Test
460     public void testXmlChildNodesOnly() throws Exception {
461         final var handler = createHandler(PATCH_CONT_YIID, "Casey", NotificationOutputType.XML, false, false, false,
462             true);
463
464         final var iid = InstanceIdentifier.create(PatchCont.class).child(MyList1.class, new MyList1Key("Althea"));
465         var writeTransaction = dataBroker.newWriteOnlyTransaction();
466         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid,
467             new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build());
468         writeTransaction.commit();
469         handler.assertXmlSimilar(getResultXml(XML_NOTIF_CHILD_NODES_ONLY_CREATE));
470
471         writeTransaction = dataBroker.newWriteOnlyTransaction();
472         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid,
473             new MyList1Builder().setMyLeaf11("Bertha").setName("Althea").build());
474         writeTransaction.commit();
475         handler.assertXmlSimilar(getResultXml(XML_NOTIF_CHILD_NODES_ONLY_UPDATE1));
476
477         writeTransaction = dataBroker.newWriteOnlyTransaction();
478         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid,
479             new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build());
480         writeTransaction.commit();
481         handler.assertXmlSimilar(getResultXml(XML_NOTIF_CHILD_NODES_ONLY_UPDATE2));
482
483         writeTransaction = dataBroker.newWriteOnlyTransaction();
484         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
485         writeTransaction.commit();
486         handler.assertXmlSimilar(getResultXml(XML_NOTIF_CHILD_NODES_ONLY_DELETE));
487     }
488
489     @Test
490     public void testJsonContNotifications() throws Exception {
491         jsonNotifications(PATCH_CONT_YIID, false, JSON_NOTIF_CONT_CREATE,
492                 JSON_NOTIF_CONT_UPDATE, JSON_NOTIF_CONT_DELETE);
493     }
494
495     @Test
496     public void testJsonListNotifications() throws Exception {
497         jsonNotifications(MY_LIST1_YIID, false, JSON_NOTIF_LIST_CREATE,
498                 JSON_NOTIF_LIST_UPDATE, JSON_NOTIF_LIST_DELETE);
499     }
500
501     @Test
502     public void testJsonContNotificationsWithoutData() throws Exception {
503         jsonNotifications(PATCH_CONT_YIID, true, JSON_NOTIF_WITHOUT_DATA_CONT_CREATE,
504                 JSON_NOTIF_WITHOUT_DATA_CONT_UPDATE, JSON_NOTIF_WITHOUT_DATA_CONT_DELETE);
505     }
506
507     @Test
508     public void testJsonListNotificationsWithoutData() throws Exception {
509         jsonNotifications(MY_LIST1_YIID, true, JSON_NOTIF_WITHOUT_DATA_LIST_CREATE,
510                 JSON_NOTIF_WITHOUT_DATA_LIST_UPDATE, JSON_NOTIF_WITHOUT_DATA_LIST_DELETE);
511     }
512
513     @Test
514     public void testXmlContNotifications() throws Exception {
515         xmlNotifications(PATCH_CONT_YIID, false, XML_NOTIF_CONT_CREATE,
516                 XML_NOTIF_CONT_UPDATE, XML_NOTIF_CONT_DELETE);
517     }
518
519     @Test
520     public void testXmlListNotifications() throws Exception {
521         xmlNotifications(MY_LIST1_YIID, false, XML_NOTIF_LIST_CREATE,
522                 XML_NOTIF_LIST_UPDATE, XML_NOTIF_LIST_DELETE);
523     }
524
525     @Test
526     public void testXmlContNotificationsWithoutData() throws Exception {
527         xmlNotifications(PATCH_CONT_YIID, true, XML_NOTIF_WITHOUT_DATA_CONT_CREATE,
528                 XML_NOTIF_WITHOUT_DATA_CONT_UPDATE, XML_NOTIF_WITHOUT_DATA_CONT_DELETE);
529     }
530
531     @Test
532     public void testXmlListNotificationsWithoutData() throws Exception {
533         xmlNotifications(MY_LIST1_YIID, true, XML_NOTIF_WITHOUT_DATA_LIST_CREATE,
534                 XML_NOTIF_WITHOUT_DATA_LIST_UPDATE, XML_NOTIF_WITHOUT_DATA_LIST_DELETE);
535     }
536
537     static String withFakeDate(final String in) throws JSONException {
538         final JSONObject doc = new JSONObject(in);
539         final JSONObject notification = doc.getJSONObject("ietf-restconf:notification");
540         if (notification == null) {
541             return in;
542         }
543         notification.put("event-time", "someDate");
544         return doc.toString();
545     }
546
547     static String withFakeXmlDate(final String in) {
548         return in.replaceAll("<eventTime>.*</eventTime>", "<eventTime>someDate</eventTime>");
549     }
550
551     private String getNotifJson(final String path) throws IOException, URISyntaxException, JSONException {
552         return withFakeDate(Files.readString(Paths.get(getClass().getResource(path).toURI())));
553     }
554
555     private String getResultXml(final String path) throws IOException, URISyntaxException, JSONException {
556         return withFakeXmlDate(Files.readString(Paths.get(getClass().getResource(path).toURI())));
557     }
558
559     private void jsonNotifications(final YangInstanceIdentifier pathYiid, final boolean skipData,
560             final String jsonNotifCreate, final String jsonNotifUpdate, final String jsonNotifDelete) throws Exception {
561         final var handler = createHandler(pathYiid, "Casey", NotificationOutputType.JSON, false, skipData, false,
562             false);
563
564         var writeTransaction = dataBroker.newWriteOnlyTransaction();
565         var builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
566         final var iid = InstanceIdentifier.create(PatchCont.class)
567                 .child(MyList1.class, new MyList1Key("Althea"));
568         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
569         writeTransaction.commit();
570         handler.assertGot(getNotifJson(jsonNotifCreate));
571
572         writeTransaction = dataBroker.newWriteOnlyTransaction();
573         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
574         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
575         writeTransaction.commit();
576         handler.assertGot(getNotifJson(jsonNotifUpdate));
577
578         writeTransaction = dataBroker.newWriteOnlyTransaction();
579         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
580         writeTransaction.commit();
581         handler.assertGot(getNotifJson(jsonNotifDelete));
582     }
583
584     private void xmlNotifications(final YangInstanceIdentifier pathYiid, final boolean skipData,
585             final String xmlNotifCreate, final String xmlNotifUpdate, final String xmlNotifDelete) throws Exception {
586         final var handler = createHandler(pathYiid, "Casey", NotificationOutputType.XML, false, skipData, false, false);
587
588         var writeTransaction = dataBroker.newWriteOnlyTransaction();
589         var builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
590         final var iid = InstanceIdentifier.create(PatchCont.class)
591                 .child(MyList1.class, new MyList1Key("Althea"));
592         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
593         writeTransaction.commit();
594         handler.assertXmlSimilar(getResultXml(xmlNotifCreate));
595
596         writeTransaction = dataBroker.newWriteOnlyTransaction();
597         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
598         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
599         writeTransaction.commit();
600         handler.assertXmlSimilar(getResultXml(xmlNotifUpdate));
601
602         writeTransaction = dataBroker.newWriteOnlyTransaction();
603         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
604         writeTransaction.commit();
605         handler.assertXmlSimilar(getResultXml(xmlNotifDelete));
606     }
607 }