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.rests.services.impl;
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertThrows;
12 import static org.mockito.ArgumentMatchers.any;
13 import static org.mockito.Mockito.doReturn;
14 import static org.mockito.Mockito.mock;
16 import com.google.common.collect.ImmutableClassToInstanceMap;
18 import java.net.URISyntaxException;
20 import javax.ws.rs.core.MultivaluedHashMap;
21 import javax.ws.rs.core.UriBuilder;
22 import javax.ws.rs.core.UriInfo;
23 import org.junit.AfterClass;
24 import org.junit.Before;
25 import org.junit.BeforeClass;
26 import org.junit.Test;
27 import org.junit.runner.RunWith;
28 import org.mockito.Mock;
29 import org.mockito.junit.MockitoJUnitRunner;
30 import org.opendaylight.mdsal.common.api.CommitInfo;
31 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
32 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
33 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
34 import org.opendaylight.mdsal.dom.api.DOMNotificationService;
35 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
36 import org.opendaylight.restconf.nb.rfc8040.URLConstants;
37 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
38 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
39 import org.opendaylight.restconf.nb.rfc8040.streams.StreamsConfiguration;
40 import org.opendaylight.restconf.nb.rfc8040.streams.listeners.ListenerAdapter;
41 import org.opendaylight.restconf.nb.rfc8040.streams.listeners.ListenersBroker;
42 import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
43 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
44 import org.opendaylight.yangtools.concepts.ListenerRegistration;
45 import org.opendaylight.yangtools.yang.common.ErrorTag;
46 import org.opendaylight.yangtools.yang.common.ErrorType;
47 import org.opendaylight.yangtools.yang.common.QName;
48 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
49 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
50 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
52 @RunWith(MockitoJUnitRunner.StrictStubs.class)
53 public class RestconfStreamsSubscriptionServiceImplTest {
54 private static final String URI = "/rests/data/ietf-restconf-monitoring:restconf-state/streams/stream/"
55 + "toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE";
57 private static EffectiveModelContext MODEL_CONTEXT;
58 // FIXME: NETCONF-1104: this should be non-static and set up for each test separately
59 private static ListenersBroker LISTENERS_BROKER;
62 private DOMDataBroker dataBroker;
64 private UriInfo uriInfo;
66 private DOMNotificationService notificationService;
68 private StreamsConfiguration configurationWs;
69 private StreamsConfiguration configurationSse;
71 private DatabindProvider databindProvider;
74 public static void beforeClass() {
75 MODEL_CONTEXT = YangParserTestUtils.parseYangResourceDirectory("/notifications");
78 "data-change-event-subscription/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE";
79 final ListenerAdapter adapter = new ListenerAdapter(YangInstanceIdentifier.of(
80 QName.create("http://netconfcentral.org/ns/toaster", "2009-11-20", "toaster")),
81 name, NotificationOutputType.JSON);
82 LISTENERS_BROKER = ListenersBroker.getInstance();
83 LISTENERS_BROKER.setDataChangeListeners(Map.of(name, adapter));
87 public static void afterClass() {
88 if (LISTENERS_BROKER != null) {
89 LISTENERS_BROKER.setDataChangeListeners(Map.of());
90 LISTENERS_BROKER = null;
95 public void setUp() throws URISyntaxException {
96 final DOMDataTreeWriteTransaction wTx = mock(DOMDataTreeWriteTransaction.class);
97 doReturn(wTx).when(dataBroker).newWriteOnlyTransaction();
98 doReturn(CommitInfo.emptyFluentFuture()).when(wTx).commit();
100 DOMDataTreeChangeService dataTreeChangeService = mock(DOMDataTreeChangeService.class);
101 doReturn(mock(ListenerRegistration.class)).when(dataTreeChangeService)
102 .registerDataTreeChangeListener(any(), any());
104 doReturn(ImmutableClassToInstanceMap.of(DOMDataTreeChangeService.class, dataTreeChangeService))
105 .when(dataBroker).getExtensions();
107 doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
108 doReturn(UriBuilder.fromUri("http://localhost:8181")).when(uriInfo).getBaseUriBuilder();
109 doReturn(new URI("http://127.0.0.1/" + URI)).when(uriInfo).getAbsolutePath();
111 databindProvider = () -> DatabindContext.ofModel(MODEL_CONTEXT);
112 configurationWs = new StreamsConfiguration(0, 100, 10, false);
113 configurationSse = new StreamsConfiguration(0, 100, 10, true);
117 public void testSubscribeToStreamSSE() {
118 LISTENERS_BROKER.registerDataChangeListener(
119 IdentifierCodec.deserialize("toaster:toaster/toasterStatus", MODEL_CONTEXT),
120 "data-change-event-subscription/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE",
121 NotificationOutputType.XML);
122 final var streamsSubscriptionService = new RestconfStreamsSubscriptionServiceImpl(dataBroker,
123 notificationService, databindProvider, configurationSse);
124 final var response = streamsSubscriptionService.subscribeToStream(
125 "data-change-event-subscription/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE", uriInfo);
126 assertEquals("http://localhost:8181/" + URLConstants.BASE_PATH + "/" + URLConstants.SSE_SUBPATH
127 + "/data-change-event-subscription/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE",
128 response.getLocation().toString());
132 public void testSubscribeToStreamWS() {
133 LISTENERS_BROKER.registerDataChangeListener(
134 IdentifierCodec.deserialize("toaster:toaster/toasterStatus", MODEL_CONTEXT),
135 "data-change-event-subscription/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE",
136 NotificationOutputType.XML);
137 final var streamsSubscriptionService = new RestconfStreamsSubscriptionServiceImpl(dataBroker,
138 notificationService, databindProvider, configurationWs);
139 final var response = streamsSubscriptionService.subscribeToStream(
140 "data-change-event-subscription/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE", uriInfo);
141 assertEquals("ws://localhost:8181/" + URLConstants.BASE_PATH
142 + "/data-change-event-subscription/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE",
143 response.getLocation().toString());
147 public void testSubscribeToStreamMissingDatastoreInPath() {
148 final RestconfStreamsSubscriptionServiceImpl streamsSubscriptionService =
149 new RestconfStreamsSubscriptionServiceImpl(dataBroker, notificationService, databindProvider,
151 final var errors = assertThrows(RestconfDocumentedException.class,
152 () -> streamsSubscriptionService.subscribeToStream("toaster:toaster/toasterStatus/scope=ONE", uriInfo))
154 assertEquals(1, errors.size());
155 final var error = errors.get(0);
156 assertEquals(ErrorType.APPLICATION, error.getErrorType());
157 assertEquals(ErrorTag.OPERATION_FAILED, error.getErrorTag());
158 assertEquals("Bad type of notification of sal-remote", error.getErrorMessage());
162 public void testSubscribeToStreamMissingScopeInPath() {
163 final RestconfStreamsSubscriptionServiceImpl streamsSubscriptionService =
164 new RestconfStreamsSubscriptionServiceImpl(dataBroker, notificationService, databindProvider,
166 final var errors = assertThrows(RestconfDocumentedException.class,
167 () -> streamsSubscriptionService.subscribeToStream("toaster:toaster/toasterStatus/datastore=OPERATIONAL",
168 uriInfo)).getErrors();
169 assertEquals(1, errors.size());
170 final var error = errors.get(0);
171 assertEquals(ErrorType.APPLICATION, error.getErrorType());
172 assertEquals(ErrorTag.OPERATION_FAILED, error.getErrorTag());
173 assertEquals("Bad type of notification of sal-remote", error.getErrorMessage());