2 * Copyright © 2019 FRINX s.r.o. 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.jersey.providers.errors;
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;
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;
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.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;
42 @RunWith(Parameterized.class)
43 public class RestconfDocumentedExceptionMapperTest {
44 private static final QNameModule MONITORING_MODULE_INFO =
45 QNameModule.ofRevision("instance:identifier:patch:module", "2015-11-21");
47 private static RestconfDocumentedExceptionMapper exceptionMapper;
50 public static void setupExceptionMapper() {
51 final var schemaContext = YangParserTestUtils.parseYangResources(
52 RestconfDocumentedExceptionMapperTest.class, "/restconf/impl/ietf-restconf@2017-01-26.yang",
53 "/instanceidentifier/yang/instance-identifier-patch-module.yang");
54 exceptionMapper = new RestconfDocumentedExceptionMapper(() -> DatabindContext.ofModel(schemaContext));
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).
62 * @return Testing data for parametrized test.
64 @Parameters(name = "{index}: {0}: {1}")
65 public static Iterable<Object[]> data() {
66 final RestconfDocumentedException sampleComplexError =
67 new RestconfDocumentedException("general message", new IllegalStateException("cause"), List.of(
68 new RestconfError(ErrorType.APPLICATION, ErrorTag.BAD_ATTRIBUTE, "message 1", "app tag #1"),
69 new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED,
70 "message 2", "app tag #2", "my info"),
71 new RestconfError(ErrorType.RPC, ErrorTag.DATA_MISSING,
72 "message 3", " app tag #3", "my error info", YangInstanceIdentifier.builder()
73 .node(QName.create(MONITORING_MODULE_INFO, "patch-cont"))
74 .node(QName.create(MONITORING_MODULE_INFO, "my-list1"))
75 .nodeWithKey(QName.create(MONITORING_MODULE_INFO, "my-list1"),
76 QName.create(MONITORING_MODULE_INFO, "name"), "sample")
77 .node(QName.create(MONITORING_MODULE_INFO, "my-leaf12"))
80 return Arrays.asList(new Object[][] {
82 "Mapping of the exception with one error entry but null status code. This status code should"
83 + "be derived from single error entry; JSON output",
84 new RestconfDocumentedException("Sample error message"),
85 mockHttpHeaders(MediaType.APPLICATION_JSON_TYPE, List.of(JaxRsMediaTypes.APPLICATION_YANG_PATCH_JSON)),
86 Response.status(Status.INTERNAL_SERVER_ERROR)
87 .type(JaxRsMediaTypes.APPLICATION_YANG_DATA_JSON)
93 "error-tag": "operation-failed",
94 "error-message": "Sample error message",
95 "error-type": "application"
103 "Mapping of the exception with two error entries but null status code. This status code should"
104 + "be derived from the first error entry that is specified; XML output",
105 new RestconfDocumentedException("general message", new IllegalStateException("cause"), List.of(
106 new RestconfError(ErrorType.APPLICATION, ErrorTag.BAD_ATTRIBUTE, "message 1"),
107 new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "message 2"))),
108 mockHttpHeaders(MediaType.APPLICATION_JSON_TYPE, List.of(JaxRsMediaTypes.APPLICATION_YANG_PATCH_XML)),
109 Response.status(Status.BAD_REQUEST)
110 .type(JaxRsMediaTypes.APPLICATION_YANG_DATA_XML)
112 <errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">
114 <error-message>message 1</error-message>
115 <error-tag>bad-attribute</error-tag>
116 <error-type>application</error-type>
119 <error-message>message 2</error-message>
120 <error-tag>operation-failed</error-tag>
121 <error-type>application</error-type>
127 "Mapping of the exception with three entries and optional entries set: error app tag (the first error),"
128 + " error info (the second error), and error path (the last error); JSON output",
129 sampleComplexError, mockHttpHeaders(MediaType.APPLICATION_JSON_TYPE, List.of(
130 MediaType.APPLICATION_JSON_TYPE)),
131 Response.status(Status.BAD_REQUEST)
132 .type(JaxRsMediaTypes.APPLICATION_YANG_DATA_JSON)
138 "error-tag": "bad-attribute",
139 "error-app-tag": "app tag #1",
140 "error-message": "message 1",
141 "error-type": "application"
144 "error-tag": "operation-failed",
145 "error-app-tag": "app tag #2",
146 "error-info": "my info",
147 "error-message": "message 2",
148 "error-type": "application"
151 "error-tag": "data-missing",
152 "error-app-tag": " app tag #3",
153 "error-info": "my error info",
154 "error-message": "message 3",
155 "error-path": "/instance-identifier-patch-module:patch-cont/\
156 my-list1[name='sample']/my-leaf12",
165 "Mapping of the exception with three entries and optional entries set: error app tag (the first error),"
166 + " error info (the second error), and error path (the last error); XML output",
167 sampleComplexError, mockHttpHeaders(JaxRsMediaTypes.APPLICATION_YANG_PATCH_JSON,
168 List.of(JaxRsMediaTypes.APPLICATION_YANG_DATA_XML)),
169 Response.status(Status.BAD_REQUEST)
170 .type(JaxRsMediaTypes.APPLICATION_YANG_DATA_XML)
172 <errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">
174 <error-type>application</error-type>
175 <error-message>message 1</error-message>
176 <error-tag>bad-attribute</error-tag>
177 <error-app-tag>app tag #1</error-app-tag>
180 <error-type>application</error-type>
181 <error-message>message 2</error-message>
182 <error-tag>operation-failed</error-tag>
183 <error-app-tag>app tag #2</error-app-tag>
184 <error-info>my info</error-info></error>
186 <error-type>rpc</error-type>
187 <error-path xmlns:a="instance:identifier:patch:module">/a:patch-cont/\
188 a:my-list1[a:name='sample']/a:my-leaf12</error-path>
189 <error-message>message 3</error-message>
190 <error-tag>data-missing</error-tag>
191 <error-app-tag> app tag #3</error-app-tag>
192 <error-info>my error info</error-info>
201 public String testDescription;
203 public RestconfDocumentedException thrownException;
205 public HttpHeaders httpHeaders;
207 public Response expectedResponse;
210 public void testMappingOfExceptionToResponse() throws JSONException {
211 exceptionMapper.setHttpHeaders(httpHeaders);
212 final Response response = exceptionMapper.toResponse(thrownException);
213 compareResponseWithExpectation(expectedResponse, response);
217 public void testFormattingJson() throws JSONException {
218 assumeTrue(expectedResponse.getMediaType().equals(JaxRsMediaTypes.APPLICATION_YANG_DATA_JSON));
220 exceptionMapper.setHttpHeaders(httpHeaders);
221 final Response response = exceptionMapper.toResponse(thrownException);
222 assertEquals(expectedResponse.getEntity().toString(), response.getEntity().toString());
225 private static HttpHeaders mockHttpHeaders(final MediaType contentType, final List<MediaType> acceptedTypes) {
226 final HttpHeaders httpHeaders = mock(HttpHeaders.class);
227 doReturn(contentType).when(httpHeaders).getMediaType();
228 doReturn(acceptedTypes).when(httpHeaders).getAcceptableMediaTypes();
232 private static void compareResponseWithExpectation(final Response expectedResponse, final Response actualResponse)
233 throws JSONException {
234 final String errorMessage = String.format("Actual response %s doesn't equal to expected response %s",
235 actualResponse, expectedResponse);
236 assertEquals(errorMessage, expectedResponse.getStatus(), actualResponse.getStatus());
237 assertEquals(errorMessage, expectedResponse.getMediaType(), actualResponse.getMediaType());
238 if (JaxRsMediaTypes.APPLICATION_YANG_DATA_JSON.equals(expectedResponse.getMediaType())) {
239 JSONAssert.assertEquals(expectedResponse.getEntity().toString(),
240 actualResponse.getEntity().toString(), true);
242 final JSONObject expectedResponseInJson = XML.toJSONObject(expectedResponse.getEntity().toString());
243 final JSONObject actualResponseInJson = XML.toJSONObject(actualResponse.getEntity().toString());
244 JSONAssert.assertEquals(expectedResponseInJson, actualResponseInJson, true);