7749946fd4924f22c95f07bddfe59f2c5a688e19
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / rests / services / impl / RestconfStreamsSubscriptionServiceImplTest.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.rests.services.impl;
9
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;
15
16 import com.google.common.collect.ImmutableClassToInstanceMap;
17 import java.io.FileNotFoundException;
18 import java.net.URI;
19 import java.net.URISyntaxException;
20 import java.util.Map;
21 import javax.ws.rs.core.MultivaluedHashMap;
22 import javax.ws.rs.core.UriBuilder;
23 import javax.ws.rs.core.UriInfo;
24 import org.junit.AfterClass;
25 import org.junit.Before;
26 import org.junit.BeforeClass;
27 import org.junit.Test;
28 import org.junit.runner.RunWith;
29 import org.mockito.Mock;
30 import org.mockito.junit.MockitoJUnitRunner;
31 import org.opendaylight.mdsal.common.api.CommitInfo;
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.DOMNotificationService;
36 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
37 import org.opendaylight.restconf.nb.rfc8040.TestRestconfUtils;
38 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
39 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
40 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
41 import org.opendaylight.restconf.nb.rfc8040.streams.StreamsConfiguration;
42 import org.opendaylight.restconf.nb.rfc8040.streams.listeners.ListenerAdapter;
43 import org.opendaylight.restconf.nb.rfc8040.streams.listeners.ListenersBroker;
44 import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
45 import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
46 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
47 import org.opendaylight.yangtools.concepts.ListenerRegistration;
48 import org.opendaylight.yangtools.yang.common.ErrorTag;
49 import org.opendaylight.yangtools.yang.common.ErrorType;
50 import org.opendaylight.yangtools.yang.common.QName;
51 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
52 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
53 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
54 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
55
56 @RunWith(MockitoJUnitRunner.StrictStubs.class)
57 public class RestconfStreamsSubscriptionServiceImplTest {
58
59     private static final String URI = "/rests/data/ietf-restconf-monitoring:restconf-state/streams/stream/"
60             + "toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE";
61
62     @Mock
63     private DOMDataBroker dataBroker;
64     @Mock
65     private UriInfo uriInfo;
66     @Mock
67     private DOMNotificationService notificationService;
68
69     private StreamsConfiguration configurationWs;
70     private StreamsConfiguration configurationSse;
71
72     private EffectiveModelContext modelContext;
73     private DatabindProvider databindProvider;
74
75     @Before
76     public void setUp() throws FileNotFoundException, URISyntaxException {
77         final DOMDataTreeWriteTransaction wTx = mock(DOMDataTreeWriteTransaction.class);
78         doReturn(wTx).when(dataBroker).newWriteOnlyTransaction();
79         doReturn(CommitInfo.emptyFluentFuture()).when(wTx).commit();
80
81         DOMDataTreeChangeService dataTreeChangeService = mock(DOMDataTreeChangeService.class);
82         doReturn(mock(ListenerRegistration.class)).when(dataTreeChangeService)
83                 .registerDataTreeChangeListener(any(), any());
84
85         doReturn(ImmutableClassToInstanceMap.of(DOMDataTreeChangeService.class, dataTreeChangeService))
86                 .when(dataBroker).getExtensions();
87
88         doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
89         doReturn(UriBuilder.fromUri("http://localhost:8181")).when(uriInfo).getBaseUriBuilder();
90         doReturn(new URI("http://127.0.0.1/" + URI)).when(uriInfo).getAbsolutePath();
91
92         modelContext = YangParserTestUtils.parseYangFiles(TestRestconfUtils.loadFiles("/notifications"));
93         databindProvider = () -> DatabindContext.ofModel(modelContext);
94         configurationWs = new StreamsConfiguration(0, 100, 10, false);
95         configurationSse = new StreamsConfiguration(0, 100, 10, true);
96     }
97
98     @BeforeClass
99     public static void setUpBeforeTest() {
100         final String name =
101             "data-change-event-subscription/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE";
102         final ListenerAdapter adapter = new ListenerAdapter(YangInstanceIdentifier.create(new NodeIdentifier(
103             QName.create("http://netconfcentral.org/ns/toaster", "2009-11-20", "toaster"))),
104             name, NotificationOutputType.JSON);
105         ListenersBroker.getInstance().setDataChangeListeners(Map.of(name, adapter));
106     }
107
108     @AfterClass
109     public static void setUpAfterTest() {
110         ListenersBroker.getInstance().setDataChangeListeners(Map.of());
111     }
112
113     @Test
114     public void testSubscribeToStreamSSE() {
115         ListenersBroker.getInstance().registerDataChangeListener(
116                 IdentifierCodec.deserialize("toaster:toaster/toasterStatus", modelContext),
117                 "data-change-event-subscription/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE",
118                 NotificationOutputType.XML);
119         final RestconfStreamsSubscriptionServiceImpl streamsSubscriptionService =
120                 new RestconfStreamsSubscriptionServiceImpl(dataBroker, notificationService, databindProvider,
121                         configurationSse);
122         final NormalizedNodePayload response = streamsSubscriptionService.subscribeToStream(
123             "data-change-event-subscription/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE", uriInfo);
124         assertEquals("http://localhost:8181/" + RestconfConstants.BASE_URI_PATTERN
125                 + "/" + RestconfConstants.NOTIF
126                 + "/data-change-event-subscription/toaster:toaster/toasterStatus/"
127                 + "datastore=OPERATIONAL/scope=ONE", response.getNewHeaders().get("Location").toString());
128     }
129
130     @Test
131     public void testSubscribeToStreamWS() {
132         ListenersBroker.getInstance().registerDataChangeListener(
133                 IdentifierCodec.deserialize("toaster:toaster/toasterStatus", modelContext),
134                 "data-change-event-subscription/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE",
135                 NotificationOutputType.XML);
136         final RestconfStreamsSubscriptionServiceImpl streamsSubscriptionService =
137                 new RestconfStreamsSubscriptionServiceImpl(dataBroker, notificationService, databindProvider,
138                         configurationWs);
139         final NormalizedNodePayload response = streamsSubscriptionService.subscribeToStream(
140             "data-change-event-subscription/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE", uriInfo);
141         assertEquals("ws://localhost:8181/" + RestconfConstants.BASE_URI_PATTERN
142                 + "/data-change-event-subscription/toaster:toaster/toasterStatus/"
143                 + "datastore=OPERATIONAL/scope=ONE", response.getNewHeaders().get("Location").toString());
144     }
145
146     @Test
147     public void testSubscribeToStreamMissingDatastoreInPath() {
148         final RestconfStreamsSubscriptionServiceImpl streamsSubscriptionService =
149                 new RestconfStreamsSubscriptionServiceImpl(dataBroker, notificationService, databindProvider,
150                         configurationWs);
151         final var errors = assertThrows(RestconfDocumentedException.class,
152             () -> streamsSubscriptionService.subscribeToStream("toaster:toaster/toasterStatus/scope=ONE", uriInfo))
153             .getErrors();
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());
159     }
160
161     @Test
162     public void testSubscribeToStreamMissingScopeInPath() {
163         final RestconfStreamsSubscriptionServiceImpl streamsSubscriptionService =
164                 new RestconfStreamsSubscriptionServiceImpl(dataBroker, notificationService, databindProvider,
165                         configurationWs);
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());
174     }
175 }