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