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