Migrate restconf/restconf-nb tests to JUnit5
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / legacy / RestconfDocumentedExceptionMapperTest.java
1 /*
2  * Copyright © 2019 FRINX s.r.o. 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.legacy;
9
10 import static org.junit.jupiter.api.Assertions.assertEquals;
11 import static org.junit.jupiter.api.Assumptions.assumeTrue;
12 import static org.junit.jupiter.params.provider.Arguments.arguments;
13 import static org.mockito.Mockito.doReturn;
14 import static org.mockito.Mockito.mock;
15
16 import java.util.List;
17 import java.util.stream.Stream;
18 import javax.ws.rs.core.HttpHeaders;
19 import javax.ws.rs.core.MediaType;
20 import javax.ws.rs.core.Response;
21 import javax.ws.rs.core.Response.Status;
22 import org.json.JSONException;
23 import org.json.JSONObject;
24 import org.json.XML;
25 import org.junit.jupiter.api.BeforeAll;
26 import org.junit.jupiter.params.ParameterizedTest;
27 import org.junit.jupiter.params.provider.Arguments;
28 import org.junit.jupiter.params.provider.MethodSource;
29 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
30 import org.opendaylight.restconf.common.errors.RestconfError;
31 import org.opendaylight.restconf.nb.jaxrs.JaxRsMediaTypes;
32 import org.opendaylight.restconf.nb.rfc8040.ErrorTagMapping;
33 import org.opendaylight.restconf.server.api.DatabindContext;
34 import org.opendaylight.yangtools.yang.common.ErrorTag;
35 import org.opendaylight.yangtools.yang.common.ErrorType;
36 import org.opendaylight.yangtools.yang.common.QName;
37 import org.opendaylight.yangtools.yang.common.QNameModule;
38 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
39 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
40 import org.skyscreamer.jsonassert.JSONAssert;
41
42 class RestconfDocumentedExceptionMapperTest {
43     private static final QNameModule MONITORING_MODULE_INFO =
44         QNameModule.ofRevision("instance:identifier:patch:module", "2015-11-21");
45
46     private static RestconfDocumentedExceptionMapper exceptionMapper;
47
48     @BeforeAll
49     static void setupExceptionMapper() {
50         final var schemaContext = YangParserTestUtils.parseYangResources(
51                 RestconfDocumentedExceptionMapperTest.class, "/restconf/impl/ietf-restconf@2017-01-26.yang",
52                 "/instanceidentifier/yang/instance-identifier-patch-module.yang");
53         exceptionMapper = new RestconfDocumentedExceptionMapper(() -> DatabindContext.ofModel(schemaContext),
54             ErrorTagMapping.RFC8040);
55     }
56
57     /**
58      * Testing entries 0 - 6: testing of media types and empty responses.
59      * Testing entries 7 - 8: testing of deriving of status codes from error entries.
60      * Testing entries 9 - 10: testing of serialization of different optional fields of error entries (JSON/XML).
61      *
62      * @return Testing data for parametrized test.
63      */
64     static Stream<Arguments> data() {
65         final RestconfDocumentedException sampleComplexError =
66             new RestconfDocumentedException("general message", new IllegalStateException("cause"), List.of(
67                 new RestconfError(ErrorType.APPLICATION, ErrorTag.BAD_ATTRIBUTE, "message 1", "app tag #1"),
68                 new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED,
69                     "message 2", "app tag #2", "my info"),
70                 new RestconfError(ErrorType.RPC, ErrorTag.DATA_MISSING,
71                     "message 3", " app tag #3", "my error info", YangInstanceIdentifier.builder()
72                     .node(QName.create(MONITORING_MODULE_INFO, "patch-cont"))
73                     .node(QName.create(MONITORING_MODULE_INFO, "my-list1"))
74                     .nodeWithKey(QName.create(MONITORING_MODULE_INFO, "my-list1"),
75                         QName.create(MONITORING_MODULE_INFO, "name"), "sample")
76                     .node(QName.create(MONITORING_MODULE_INFO, "my-leaf12"))
77                     .build())));
78
79         return Stream.of(
80             arguments(
81                 "Mapping of the exception with one error entry but null status code. This status code should"
82                     + " be derived from single error entry; JSON output",
83                 new RestconfDocumentedException("Sample error message"),
84                 mockHttpHeaders(MediaType.APPLICATION_JSON_TYPE, List.of(JaxRsMediaTypes.APPLICATION_YANG_PATCH_JSON)),
85                 Response.status(Status.INTERNAL_SERVER_ERROR)
86                     .type(JaxRsMediaTypes.APPLICATION_YANG_DATA_JSON)
87                     .entity("""
88                             {
89                               "errors": {
90                                 "error": [
91                                   {
92                                     "error-tag": "operation-failed",
93                                     "error-message": "Sample error message",
94                                     "error-type": "application"
95                                   }
96                                 ]
97                               }
98                             }""")
99                     .build()
100             ),
101             arguments(
102                 "Mapping of the exception with two error entries but null status code. This status code should"
103                     + "be derived from the first error entry that is specified; XML output",
104                 new RestconfDocumentedException("general message", new IllegalStateException("cause"), List.of(
105                     new RestconfError(ErrorType.APPLICATION, ErrorTag.BAD_ATTRIBUTE, "message 1"),
106                     new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "message 2"))),
107                 mockHttpHeaders(MediaType.APPLICATION_JSON_TYPE, List.of(JaxRsMediaTypes.APPLICATION_YANG_PATCH_XML)),
108                 Response.status(Status.BAD_REQUEST)
109                     .type(JaxRsMediaTypes.APPLICATION_YANG_DATA_XML)
110                     .entity("""
111                             <errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">
112                               <error>
113                                 <error-message>message 1</error-message>
114                                 <error-tag>bad-attribute</error-tag>
115                                 <error-type>application</error-type>
116                               </error>
117                               <error>
118                                 <error-message>message 2</error-message>
119                                 <error-tag>operation-failed</error-tag>
120                                 <error-type>application</error-type>
121                               </error>
122                             </errors>""")
123                     .build()
124             ),
125             arguments("Mapping of the exception with three entries and optional entries set: error app tag"
126                     + " (the first error), error info (the second error), and error path (the last error); JSON output",
127                 sampleComplexError, mockHttpHeaders(MediaType.APPLICATION_JSON_TYPE, List.of(
128                     MediaType.APPLICATION_JSON_TYPE)),
129                 Response.status(Status.BAD_REQUEST)
130                     .type(JaxRsMediaTypes.APPLICATION_YANG_DATA_JSON)
131                     .entity("""
132                             {
133                               "errors": {
134                                 "error": [
135                                   {
136                                     "error-tag": "bad-attribute",
137                                     "error-app-tag": "app tag #1",
138                                     "error-message": "message 1",
139                                     "error-type": "application"
140                                   },
141                                   {
142                                     "error-tag": "operation-failed",
143                                     "error-app-tag": "app tag #2",
144                                     "error-info": "my info",
145                                     "error-message": "message 2",
146                                     "error-type": "application"
147                                   },
148                                   {
149                                     "error-tag": "data-missing",
150                                     "error-app-tag": " app tag #3",
151                                     "error-info": "my error info",
152                                     "error-message": "message 3",
153                                     "error-path": "/instance-identifier-patch-module:patch-cont/\
154                             my-list1[name='sample']/my-leaf12",
155                                     "error-type": "rpc"
156                                   }
157                                 ]
158                               }
159                             }""")
160                     .build()),
161             arguments("Mapping of the exception with three entries and optional entries set: error app tag"
162                     + " (the first error), error info (the second error), and error path (the last error); XML output",
163                 sampleComplexError, mockHttpHeaders(JaxRsMediaTypes.APPLICATION_YANG_PATCH_JSON,
164                     List.of(JaxRsMediaTypes.APPLICATION_YANG_DATA_XML)),
165                 Response.status(Status.BAD_REQUEST)
166                     .type(JaxRsMediaTypes.APPLICATION_YANG_DATA_XML)
167                     .entity("""
168                             <errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">
169                               <error>
170                                 <error-type>application</error-type>
171                                 <error-message>message 1</error-message>
172                                 <error-tag>bad-attribute</error-tag>
173                                 <error-app-tag>app tag #1</error-app-tag>
174                               </error>
175                               <error>
176                                 <error-type>application</error-type>
177                                 <error-message>message 2</error-message>
178                                 <error-tag>operation-failed</error-tag>
179                                 <error-app-tag>app tag #2</error-app-tag>
180                                 <error-info>my info</error-info></error>
181                               <error>
182                                 <error-type>rpc</error-type>
183                                 <error-path xmlns:a="instance:identifier:patch:module">/a:patch-cont/\
184                             a:my-list1[a:name='sample']/a:my-leaf12</error-path>
185                                 <error-message>message 3</error-message>
186                                 <error-tag>data-missing</error-tag>
187                                 <error-app-tag> app tag #3</error-app-tag>
188                                 <error-info>my error info</error-info>
189                               </error>
190                             </errors>""")
191                     .build()
192             )
193          );
194     }
195
196     @ParameterizedTest
197     @MethodSource("data")
198     void testMappingOfExceptionToResponse(String testDescription, RestconfDocumentedException thrownException,
199         HttpHeaders httpHeaders, Response expectedResponse) throws JSONException {
200         exceptionMapper.setHttpHeaders(httpHeaders);
201         final Response response = exceptionMapper.toResponse(thrownException);
202         compareResponseWithExpectation(expectedResponse, response);
203     }
204
205     @ParameterizedTest
206     @MethodSource("data")
207     void testFormattingJson(String testDescription, RestconfDocumentedException thrownException,
208         HttpHeaders httpHeaders, Response expectedResponse) throws JSONException {
209         assumeTrue(expectedResponse.getMediaType().equals(JaxRsMediaTypes.APPLICATION_YANG_DATA_JSON));
210
211         exceptionMapper.setHttpHeaders(httpHeaders);
212         final Response response = exceptionMapper.toResponse(thrownException);
213         assertEquals(expectedResponse.getEntity().toString(), response.getEntity().toString());
214     }
215
216     private static HttpHeaders mockHttpHeaders(final MediaType contentType, final List<MediaType> acceptedTypes) {
217         final HttpHeaders httpHeaders = mock(HttpHeaders.class);
218         doReturn(contentType).when(httpHeaders).getMediaType();
219         doReturn(acceptedTypes).when(httpHeaders).getAcceptableMediaTypes();
220         return httpHeaders;
221     }
222
223     private static void compareResponseWithExpectation(final Response expectedResponse, final Response actualResponse)
224             throws JSONException {
225         final String errorMessage = String.format("Actual response %s doesn't equal to expected response %s",
226                 actualResponse, expectedResponse);
227         assertEquals(expectedResponse.getStatus(), actualResponse.getStatus(), errorMessage);
228         assertEquals(expectedResponse.getMediaType(), actualResponse.getMediaType(), errorMessage);
229         if (JaxRsMediaTypes.APPLICATION_YANG_DATA_JSON.equals(expectedResponse.getMediaType())) {
230             JSONAssert.assertEquals(expectedResponse.getEntity().toString(),
231                     actualResponse.getEntity().toString(), true);
232         } else {
233             final JSONObject expectedResponseInJson = XML.toJSONObject(expectedResponse.getEntity().toString());
234             final JSONObject actualResponseInJson = XML.toJSONObject(actualResponse.getEntity().toString());
235             JSONAssert.assertEquals(expectedResponseInJson, actualResponseInJson, true);
236         }
237     }
238 }