Expose streams with all supported encodings
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / streams / ListenersBrokerTest.java
1 /*
2  * Copyright (c) 2016 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.restconf.nb.rfc8040.streams;
9
10 import static org.hamcrest.CoreMatchers.startsWith;
11 import static org.hamcrest.MatcherAssert.assertThat;
12 import static org.junit.jupiter.api.Assertions.assertEquals;
13 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
14 import static org.junit.jupiter.api.Assertions.assertNotNull;
15 import static org.junit.jupiter.api.Assertions.assertThrows;
16 import static org.mockito.ArgumentMatchers.eq;
17 import static org.mockito.Mockito.doNothing;
18 import static org.mockito.Mockito.doReturn;
19
20 import com.google.common.collect.ImmutableClassToInstanceMap;
21 import java.net.URI;
22 import java.util.UUID;
23 import org.junit.jupiter.api.BeforeEach;
24 import org.junit.jupiter.api.Test;
25 import org.junit.jupiter.api.extension.ExtendWith;
26 import org.mockito.ArgumentCaptor;
27 import org.mockito.Captor;
28 import org.mockito.Mock;
29 import org.mockito.junit.jupiter.MockitoExtension;
30 import org.opendaylight.mdsal.common.api.CommitInfo;
31 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
32 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
33 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
34 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
35 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
36 import org.opendaylight.mdsal.dom.api.DOMNotificationService;
37 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
38 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
39 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.restconf.state.streams.stream.Access;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.remote.rev140114.CreateDataChangeEventSubscriptionOutput;
42 import org.opendaylight.yangtools.yang.common.ErrorTag;
43 import org.opendaylight.yangtools.yang.common.ErrorType;
44 import org.opendaylight.yangtools.yang.common.QName;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
46 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
47 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
48 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
49 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
51 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
52 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
53 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
54 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
55 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
56 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
57 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
58
59 @ExtendWith(MockitoExtension.class)
60 class ListenersBrokerTest {
61     private static final EffectiveModelContext SCHEMA_CTX = YangParserTestUtils.parseYangResourceDirectory("/streams");
62     private static final URI BASE_URI = URI.create("baseURI");
63
64     @Mock
65     private DOMDataBroker dataBroker;
66     @Mock
67     private DOMDataTreeChangeService treeChange;
68     @Mock
69     private DOMMountPointService mountPointService;
70     @Mock
71     private DOMNotificationService notificationService;
72     @Mock
73     private DOMDataTreeWriteTransaction tx;
74     @Captor
75     private ArgumentCaptor<YangInstanceIdentifier> pathCaptor;
76     @Captor
77     private ArgumentCaptor<NormalizedNode> dataCaptor;
78
79     private ListenersBroker listenersBroker;
80     private DatabindProvider databindProvider;
81
82     @BeforeEach
83     public void before() {
84         listenersBroker = new ListenersBroker.ServerSentEvents(dataBroker, notificationService, mountPointService);
85         databindProvider = () -> DatabindContext.ofModel(SCHEMA_CTX);
86     }
87
88     @Test
89     void createStreamTest() {
90         doReturn(ImmutableClassToInstanceMap.of(DOMDataTreeChangeService.class, treeChange))
91             .when(dataBroker).getExtensions();
92
93         doReturn(tx).when(dataBroker).newWriteOnlyTransaction();
94         doNothing().when(tx).put(eq(LogicalDatastoreType.OPERATIONAL), pathCaptor.capture(), dataCaptor.capture());
95         doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
96
97         final var output = assertInstanceOf(ContainerNode.class,
98             listenersBroker.createDataChangeNotifiStream(databindProvider, BASE_URI,
99                 prepareDomPayload("create-data-change-event-subscription", "toaster", "path"), SCHEMA_CTX)
100             .getOrThrow()
101             .orElse(null));
102
103         assertEquals(new NodeIdentifier(CreateDataChangeEventSubscriptionOutput.QNAME), output.name());
104         assertEquals(1, output.size());
105
106         final var streamName = assertInstanceOf(LeafNode.class,
107             output.childByArg(new NodeIdentifier(
108                 QName.create(CreateDataChangeEventSubscriptionOutput.QNAME, "stream-name"))));
109         final var name = assertInstanceOf(String.class, streamName.body());
110         assertEquals(45, name.length());
111         assertThat(name, startsWith("urn:uuid:"));
112         assertNotNull(UUID.fromString(name.substring(9)));
113
114         final var rcStream = QName.create("urn:ietf:params:xml:ns:yang:ietf-restconf-monitoring", "2017-01-26",
115             "stream");
116         final var rcName = QName.create(rcStream, "name");
117         final var streamId = NodeIdentifierWithPredicates.of(rcStream, rcName, name);
118         final var rcEncoding = QName.create(rcStream, "encoding");
119         final var rcLocation = QName.create(rcStream, "location");
120
121         assertEquals(YangInstanceIdentifier.of(
122             new NodeIdentifier(QName.create(rcStream, "restconf-state")),
123             new NodeIdentifier(QName.create(rcStream, "streams")),
124             new NodeIdentifier(rcStream),
125             streamId), pathCaptor.getValue());
126         assertEquals(Builders.mapEntryBuilder()
127             .withNodeIdentifier(streamId)
128             .withChild(ImmutableNodes.leafNode(rcName, name))
129             .withChild(ImmutableNodes.leafNode(QName.create(rcStream, "description"),
130                 "Events occuring in CONFIGURATION datastore under /toaster:toaster"))
131             .withChild(Builders.mapBuilder()
132                 .withNodeIdentifier(new NodeIdentifier(Access.QNAME))
133                 .withChild(Builders.mapEntryBuilder()
134                     .withNodeIdentifier(NodeIdentifierWithPredicates.of(Access.QNAME, rcEncoding, "json"))
135                     .withChild(ImmutableNodes.leafNode(rcEncoding, "json"))
136                     .withChild(ImmutableNodes.leafNode(rcLocation, "rests/streams/json/" + name))
137                     .build())
138                 .withChild(Builders.mapEntryBuilder()
139                     .withNodeIdentifier(NodeIdentifierWithPredicates.of(Access.QNAME, rcEncoding, "xml"))
140                     .withChild(ImmutableNodes.leafNode(rcEncoding, "xml"))
141                     .withChild(ImmutableNodes.leafNode(rcLocation, "rests/streams/xml/" + name))
142                     .build())
143                 .build())
144             .build().prettyTree().toString(), dataCaptor.getValue().prettyTree().toString());
145     }
146
147     @Test
148     void createStreamWrongValueTest() {
149         final var payload = prepareDomPayload("create-data-change-event-subscription", "String value", "path");
150         final var errors = assertThrows(RestconfDocumentedException.class,
151             () -> listenersBroker.createDataChangeNotifiStream(databindProvider, BASE_URI, payload, SCHEMA_CTX))
152             .getErrors();
153         assertEquals(1, errors.size());
154         final var error = errors.get(0);
155         assertEquals(ErrorType.APPLICATION, error.getErrorType());
156         assertEquals(ErrorTag.OPERATION_FAILED, error.getErrorTag());
157         assertEquals("Instance identifier was not normalized correctly", error.getErrorMessage());
158     }
159
160     @Test
161     void createStreamWrongInputRpcTest() {
162         final var payload = prepareDomPayload("create-data-change-event-subscription2", "toaster", "path2");
163         final var errors = assertThrows(RestconfDocumentedException.class,
164             () -> listenersBroker.createDataChangeNotifiStream(databindProvider, BASE_URI, payload, SCHEMA_CTX))
165             .getErrors();
166         assertEquals(1, errors.size());
167         final var error = errors.get(0);
168         assertEquals(ErrorType.APPLICATION, error.getErrorType());
169         assertEquals(ErrorTag.OPERATION_FAILED, error.getErrorTag());
170         assertEquals("Instance identifier was not normalized correctly", error.getErrorMessage());
171     }
172
173     private static ContainerNode prepareDomPayload(final String rpcName, final String toasterValue,
174             final String inputOutputName) {
175         final var rpcModule = SCHEMA_CTX.findModules("sal-remote").iterator().next();
176         final QName rpcQName = QName.create(rpcModule.getQNameModule(), rpcName);
177
178         ContainerLike containerSchemaNode = null;
179         for (final RpcDefinition rpc : rpcModule.getRpcs()) {
180             if (rpcQName.isEqualWithoutRevision(rpc.getQName())) {
181                 containerSchemaNode = rpc.getInput();
182                 break;
183             }
184         }
185         assertNotNull(containerSchemaNode);
186
187         final QName lfQName = QName.create(rpcModule.getQNameModule(), inputOutputName);
188         assertInstanceOf(LeafSchemaNode.class, containerSchemaNode.dataChildByName(lfQName));
189
190         final Object o;
191         if ("toaster".equals(toasterValue)) {
192             final QName rpcQname = QName.create("http://netconfcentral.org/ns/toaster", "2009-11-20", toasterValue);
193             o = YangInstanceIdentifier.of(rpcQname);
194         } else {
195             o = toasterValue;
196         }
197
198         return Builders.containerBuilder()
199             .withNodeIdentifier(new NodeIdentifier(containerSchemaNode.getQName()))
200             .withChild(ImmutableNodes.leafNode(lfQName, o))
201             .build();
202     }
203 }