2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.restconf.nb.rfc8040.streams;
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;
20 import com.google.common.collect.ImmutableClassToInstanceMap;
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;
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");
65 private DOMDataBroker dataBroker;
67 private DOMDataTreeChangeService treeChange;
69 private DOMMountPointService mountPointService;
71 private DOMNotificationService notificationService;
73 private DOMDataTreeWriteTransaction tx;
75 private ArgumentCaptor<YangInstanceIdentifier> pathCaptor;
77 private ArgumentCaptor<NormalizedNode> dataCaptor;
79 private ListenersBroker listenersBroker;
80 private DatabindProvider databindProvider;
83 public void before() {
84 listenersBroker = new ListenersBroker.ServerSentEvents(dataBroker, notificationService, mountPointService);
85 databindProvider = () -> DatabindContext.ofModel(SCHEMA_CTX);
89 void createStreamTest() {
90 doReturn(ImmutableClassToInstanceMap.of(DOMDataTreeChangeService.class, treeChange))
91 .when(dataBroker).getExtensions();
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();
97 final var output = assertInstanceOf(ContainerNode.class,
98 listenersBroker.createDataChangeNotifiStream(databindProvider, BASE_URI,
99 prepareDomPayload("create-data-change-event-subscription", "toaster", "path"), SCHEMA_CTX)
103 assertEquals(new NodeIdentifier(CreateDataChangeEventSubscriptionOutput.QNAME), output.name());
104 assertEquals(1, output.size());
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)));
114 final var rcStream = QName.create("urn:ietf:params:xml:ns:yang:ietf-restconf-monitoring", "2017-01-26",
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");
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))
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))
144 .build().prettyTree().toString(), dataCaptor.getValue().prettyTree().toString());
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))
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());
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))
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());
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);
178 ContainerLike containerSchemaNode = null;
179 for (final RpcDefinition rpc : rpcModule.getRpcs()) {
180 if (rpcQName.isEqualWithoutRevision(rpc.getQName())) {
181 containerSchemaNode = rpc.getInput();
185 assertNotNull(containerSchemaNode);
187 final QName lfQName = QName.create(rpcModule.getQNameModule(), inputOutputName);
188 assertInstanceOf(LeafSchemaNode.class, containerSchemaNode.dataChildByName(lfQName));
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);
198 return Builders.containerBuilder()
199 .withNodeIdentifier(new NodeIdentifier(containerSchemaNode.getQName()))
200 .withChild(ImmutableNodes.leafNode(lfQName, o))