Move ErrorTags
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / jaxrs / RestconfModulesGetTest.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  * Copyright (c) 2023 PANTHEON.tech, s.r.o.
4  *
5  * This program and the accompanying materials are made available under the
6  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7  * and is available at http://www.eclipse.org/legal/epl-v10.html
8  */
9 package org.opendaylight.restconf.nb.jaxrs;
10
11 import static org.junit.jupiter.api.Assertions.assertEquals;
12 import static org.mockito.Mockito.doReturn;
13
14 import com.google.common.io.CharStreams;
15 import java.io.IOException;
16 import java.io.Reader;
17 import java.util.Optional;
18 import java.util.function.Consumer;
19 import javax.ws.rs.container.AsyncResponse;
20 import org.junit.jupiter.api.Test;
21 import org.junit.jupiter.api.extension.ExtendWith;
22 import org.mockito.Mock;
23 import org.mockito.junit.jupiter.MockitoExtension;
24 import org.opendaylight.mdsal.dom.api.DOMActionService;
25 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
26 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
27 import org.opendaylight.mdsal.dom.api.DOMRpcService;
28 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
29 import org.opendaylight.mdsal.dom.api.DOMSchemaService.YangTextSourceExtension;
30 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
31 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
32 import org.opendaylight.restconf.api.ApiPath;
33 import org.opendaylight.restconf.common.errors.RestconfError;
34 import org.opendaylight.restconf.nb.rfc8040.ErrorTags;
35 import org.opendaylight.yangtools.yang.common.ErrorTag;
36 import org.opendaylight.yangtools.yang.common.ErrorType;
37 import org.opendaylight.yangtools.yang.common.QName;
38 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
39 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
40 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
41
42 @ExtendWith(MockitoExtension.class)
43 class RestconfModulesGetTest extends AbstractRestconfTest {
44     private static final EffectiveModelContext MODEL_CONTEXT =
45         YangParserTestUtils.parseYangResourceDirectory("/parser-identifier");
46     private static final EffectiveModelContext MODEL_CONTEXT_ON_MOUNT_POINT =
47         YangParserTestUtils.parseYangResourceDirectory("/parser-identifier");
48     private static final String TEST_MODULE_NAME = "test-module";
49     private static final String TEST_MODULE_REVISION = "2016-06-02";
50     private static final String TEST_MODULE_NAMESPACE = "test:module";
51     private static final ApiPath MOUNT_POINT_IDENT = apiPath("mount-point:mount-container/point-number/yang-ext:mount");
52     private static final YangInstanceIdentifier MOUNT_IID = YangInstanceIdentifier.of(
53         QName.create("mount:point", "2016-06-02", "mount-container"),
54         QName.create("mount:point", "2016-06-02", "point-number"));
55
56     @Mock
57     private YangTextSourceExtension sourceProvider;
58     @Mock
59     private DOMSchemaService schemaService;
60
61     @Override
62     EffectiveModelContext modelContext() {
63         return MODEL_CONTEXT;
64     }
65
66     /**
67      * Positive test of getting <code>SchemaExportContext</code>. Expected module name, revision and namespace are
68      * verified.
69      */
70     @Test
71     void toSchemaExportContextFromIdentifierTest() {
72         assertEquals("""
73             module test-module {
74               namespace test:module;
75               prefix testm;
76               yang-version 1;
77               revision 2016-06-02 {
78                 description
79                   "Initial revision.";
80               }
81             }
82             """, assertYang(null, TEST_MODULE_NAME, TEST_MODULE_REVISION));
83     }
84
85     /**
86      * Test of getting <code>SchemaExportContext</code> when desired module is not found.
87      * <code>SchemaExportContext</code> should not be created and exception is thrown.
88      */
89     @Test
90     void toSchemaExportContextFromIdentifierNotFoundTest() {
91         final var error = assertError(ar -> restconf.modulesYinGET("not-existing-module", "2016-01-01", ar));
92         assertEquals("Source not-existing-module@2016-01-01 not found", error.getErrorMessage());
93         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
94         assertEquals(ErrorType.APPLICATION, error.getErrorType());
95     }
96
97     /**
98      * Negative test trying to get <code>SchemaExportContext</code> with invalid identifier. Test is expected to fail
99      * with <code>RestconfDocumentedException</code> error type, error tag and error status code are compared to
100      * expected values.
101      */
102     @Test
103     void toSchemaExportContextFromIdentifierInvalidIdentifierNegativeTest() {
104         final var error = assertError(ar -> restconf.modulesYangGET(TEST_MODULE_REVISION, TEST_MODULE_NAME, ar));
105         assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
106         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
107         assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
108     }
109
110     /**
111      * Positive test of getting <code>SchemaExportContext</code> for module behind mount point.
112      * Expected module name, revision and namespace are verified.
113      */
114     @Test
115     void toSchemaExportContextFromIdentifierMountPointTest() {
116         mockMountPoint();
117
118         final var content = assertYang(MOUNT_POINT_IDENT, TEST_MODULE_NAME, TEST_MODULE_REVISION);
119         assertEquals("""
120             module test-module {
121               namespace test:module;
122               prefix testm;
123               yang-version 1;
124               revision 2016-06-02 {
125                 description
126                   "Initial revision.";
127               }
128             }
129             """, content);
130     }
131
132     /**
133      * Negative test of getting <code>SchemaExportContext</code> when desired module is not found behind mount point.
134      * <code>SchemaExportContext</code> should not be created and exception is thrown.
135      */
136     @Test
137     void toSchemaExportContextFromIdentifierMountPointNotFoundTest() {
138         mockMountPoint();
139
140         final var error = assertError(
141             ar -> restconf.modulesYangGET(MOUNT_POINT_IDENT, "not-existing-module", "2016-01-01", ar));
142         assertEquals("Source not-existing-module@2016-01-01 not found", error.getErrorMessage());
143         assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
144         assertEquals(ErrorType.APPLICATION, error.getErrorType());
145     }
146
147     /**
148      * Negative test trying to get <code>SchemaExportContext</code> behind mount point with invalid identifier. Test is
149      * expected to fail with <code>RestconfDocumentedException</code> error type, error tag and error status code are
150      * compared to expected values.
151      */
152     @Test
153     void toSchemaExportContextFromIdentifierMountPointInvalidIdentifierNegativeTest() {
154         mockMountPoint();
155
156         final var error = assertError(
157             ar -> restconf.modulesYangGET(MOUNT_POINT_IDENT, TEST_MODULE_REVISION, TEST_MODULE_NAME, ar));
158         assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
159         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
160         assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
161     }
162
163     @Test
164     void toSchemaExportContextFromIdentifierNullSchemaContextBehindMountPointNegativeTest() {
165         doReturn(Optional.of(schemaService)).when(mountPoint).getService(DOMSchemaService.class);
166         doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(MOUNT_IID);
167
168         final var error = assertError(
169             ar -> restconf.modulesYangGET(MOUNT_POINT_IDENT, TEST_MODULE_NAME, TEST_MODULE_REVISION, ar));
170         assertEquals("Mount point 'mount-point:mount-container/point-number' does not have any models",
171             error.getErrorMessage());
172         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
173         assertEquals(ErrorTags.RESOURCE_DENIED_TRANSPORT, error.getErrorTag());
174     }
175
176     /**
177      * Positive test of getting <code>SchemaExportContext</code> behind mount point for module without revision.
178      * Expected module name, prefix and namespace are verified.
179      */
180     @Test
181     void toSchemaExportContextFromIdentifierNullRevisionTest() {
182         mockMountPoint();
183
184         final var content = assertYang(MOUNT_POINT_IDENT, "module-without-revision", null);
185
186         assertEquals("""
187             module module-without-revision {
188               namespace module:without:revision;
189               prefix mwr;
190             }
191             """, content);
192     }
193
194     private void mockMountPoint() {
195         doReturn(Optional.of(new FixedDOMSchemaService(MODEL_CONTEXT_ON_MOUNT_POINT))).when(mountPoint)
196             .getService(DOMSchemaService.class);
197         doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(MOUNT_IID);
198         doReturn(Optional.of(mountPointService)).when(mountPoint).getService(DOMMountPointService.class);
199         doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
200         doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
201         doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
202         doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
203     }
204
205     /**
206      * Positive test of getting <code>SchemaExportContext</code> for module without revision.
207      * Expected module name, prefix and namespace are verified.
208      */
209     @Test
210     void validateAndGetModuleWithoutRevisionTest() {
211         final var content = assertYang(null, "module-without-revision", null);
212
213         assertEquals("""
214             module module-without-revision {
215               namespace module:without:revision;
216               prefix mwr;
217             }
218             """, content);
219     }
220
221     /**
222      * Negative test of module revision validation when supplied revision is not parsable as revision. Test fails
223      * catching <code>RestconfDocumentedException</code>.
224      */
225     @Test
226     void validateAndGetRevisionNotParsableTest() {
227         final var error = assertInvalidValue("module", "not-parsable-as-date");
228         assertEquals("Supplied revision is not in expected date format YYYY-mm-dd", error.getErrorMessage());
229     }
230
231     /**
232      * Negative test of module name validation when there is no module name. Test fails catching
233      * <code>RestconfDocumentedException</code> and checking for correct error type, error tag and error status code.
234      */
235     @Test
236     void validateAndGetModulNameNotSuppliedTest() {
237         final var error = assertInvalidValue(null, null);
238         assertEquals("Module name must be supplied", error.getErrorMessage());
239     }
240
241     /**
242      * Negative test of module name validation when supplied name is not parsable as module name on the first
243      * character. Test fails catching <code>RestconfDocumentedException</code> and checking for correct error type,
244      * error tag and error status code.
245      */
246     @Test
247     void validateAndGetModuleNameNotParsableFirstTest() {
248         final var error = assertInvalidValue("01-not-parsable-as-name-on-first-char", null);
249         assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
250     }
251
252     /**
253      * Negative test of module name validation when supplied name is not parsable as module name on any of the
254      * characters after the first character. Test fails catching <code>RestconfDocumentedException</code> and checking
255      * for correct error type, error tag and error status code.
256      */
257     @Test
258     public void validateAndGetModuleNameNotParsableNextTest() {
259         final var error = assertInvalidValue("not-parsable-as-name-after-first-char*", null);
260         assertEquals("Supplied name has not expected identifier format", error.getErrorMessage());
261     }
262
263     /**
264      * Negative test of module name validation when supplied name is empty. Test fails catching
265      * <code>RestconfDocumentedException</code> and checking for correct error type, error tag and error status code.
266      */
267     @Test
268     void validateAndGetModuleNameEmptyTest() {
269         final var error = assertInvalidValue("", null);
270         assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
271     }
272
273     private String assertYang(final ApiPath mountPath, final String fileName, final String revision) {
274         final Consumer<AsyncResponse> invocation = mountPath != null
275             ? ar -> restconf.modulesYangGET(mountPath, fileName, revision, ar)
276                 : ar -> restconf.modulesYangGET(fileName, revision, ar);
277         try (var reader = assertEntity(Reader.class, 200, invocation)) {
278             return CharStreams.toString(reader);
279         } catch (IOException e) {
280             throw new AssertionError(e);
281         }
282     }
283
284     private RestconfError assertInvalidValue(final String fileName, final String revision) {
285         final var error = assertError(ar -> restconf.modulesYangGET(fileName, revision, ar));
286         assertEquals(ErrorType.PROTOCOL, error.getErrorType());
287         assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
288         return error;
289     }
290 }