f4ddb352ad3858f0e86d876d233cbfedc9ff7dc7
[netconf.git] / restconf / restconf-nb-bierman02 / src / test / java / org / opendaylight / controller / sal / restconf / impl / test / RestconfDocumentedExceptionMapperTest.java
1 /*
2  * Copyright (c) 2014 Brocade Communications 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.controller.sal.restconf.impl.test;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertFalse;
12 import static org.junit.Assert.assertNotNull;
13 import static org.junit.Assert.assertNull;
14 import static org.junit.Assert.assertTrue;
15 import static org.junit.Assert.fail;
16 import static org.mockito.ArgumentMatchers.any;
17 import static org.mockito.Mockito.mock;
18 import static org.mockito.Mockito.reset;
19 import static org.mockito.Mockito.when;
20
21 import com.google.common.collect.Iterators;
22 import com.google.gson.JsonArray;
23 import com.google.gson.JsonElement;
24 import com.google.gson.JsonParser;
25 import java.io.ByteArrayInputStream;
26 import java.io.ByteArrayOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.InputStreamReader;
30 import java.util.Arrays;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Map.Entry;
36 import java.util.Set;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39 import javax.ws.rs.core.Application;
40 import javax.ws.rs.core.MediaType;
41 import javax.ws.rs.core.Response;
42 import javax.ws.rs.core.Response.Status;
43 import javax.ws.rs.core.UriInfo;
44 import javax.xml.namespace.NamespaceContext;
45 import javax.xml.xpath.XPath;
46 import javax.xml.xpath.XPathConstants;
47 import javax.xml.xpath.XPathExpression;
48 import javax.xml.xpath.XPathFactory;
49 import org.glassfish.jersey.server.ResourceConfig;
50 import org.glassfish.jersey.test.JerseyTest;
51 import org.junit.Before;
52 import org.junit.BeforeClass;
53 import org.junit.Ignore;
54 import org.junit.Test;
55 import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
56 import org.opendaylight.netconf.sal.rest.api.Draft02;
57 import org.opendaylight.netconf.sal.rest.api.RestconfService;
58 import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader;
59 import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext;
60 import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeJsonBodyWriter;
61 import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeXmlBodyWriter;
62 import org.opendaylight.netconf.sal.rest.impl.RestconfDocumentedExceptionMapper;
63 import org.opendaylight.netconf.sal.rest.impl.XmlNormalizedNodeBodyReader;
64 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
65 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
66 import org.opendaylight.restconf.common.errors.RestconfError;
67 import org.opendaylight.yangtools.util.xml.UntrustedXML;
68 import org.opendaylight.yangtools.yang.common.ErrorTag;
69 import org.opendaylight.yangtools.yang.common.ErrorType;
70 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
71 import org.slf4j.Logger;
72 import org.slf4j.LoggerFactory;
73 import org.w3c.dom.Document;
74 import org.w3c.dom.Node;
75 import org.w3c.dom.NodeList;
76 import org.xml.sax.SAXException;
77
78 /**
79  * Unit tests for RestconfDocumentedExceptionMapper.
80  *
81  * @author Thomas Pantelis
82  */
83 public class RestconfDocumentedExceptionMapperTest extends JerseyTest {
84
85     interface ErrorInfoVerifier {
86         void verifyXML(Node errorInfoNode);
87
88         void verifyJson(JsonElement errorInfoElement);
89     }
90
91     static class SimpleErrorInfoVerifier implements ErrorInfoVerifier {
92
93         String expTextContent;
94
95         SimpleErrorInfoVerifier(final String expErrorInfo) {
96             expTextContent = expErrorInfo;
97         }
98
99         void verifyContent(final String actualContent) {
100             assertNotNull("Actual \"error-info\" text content is null", actualContent);
101             assertTrue("", actualContent.contains(expTextContent));
102         }
103
104         @Override
105         public void verifyXML(final Node errorInfoNode) {
106             verifyContent(errorInfoNode.getTextContent());
107         }
108
109         @Override
110         public void verifyJson(final JsonElement errorInfoElement) {
111             verifyContent(errorInfoElement.getAsString());
112         }
113     }
114
115     private static final Logger LOG = LoggerFactory.getLogger(RestconfDocumentedExceptionMapperTest.class);
116     private static final String IETF_RESTCONF = "ietf-restconf";
117     static RestconfService mockRestConf = mock(RestconfService.class);
118
119     static XPath XPATH = XPathFactory.newInstance().newXPath();
120     static XPathExpression ERROR_LIST;
121     static XPathExpression ERROR_TYPE;
122     static XPathExpression ERROR_TAG;
123     static XPathExpression ERROR_MESSAGE;
124     static XPathExpression ERROR_APP_TAG;
125     static XPathExpression ERROR_INFO;
126
127     private static EffectiveModelContext schemaContext;
128
129     @BeforeClass
130     public static void init() throws Exception {
131         schemaContext = TestUtils.loadSchemaContext("/modules");
132
133         final NamespaceContext nsContext = new NamespaceContext() {
134             @Override
135             public Iterator<String> getPrefixes(final String namespaceURI) {
136                 return Iterators.singletonIterator(IETF_RESTCONF);
137             }
138
139             @Override
140             public String getPrefix(final String namespaceURI) {
141                 return null;
142             }
143
144             @Override
145             public String getNamespaceURI(final String prefix) {
146                 return IETF_RESTCONF.equals(prefix) ? Draft02.RestConfModule.NAMESPACE : null;
147             }
148         };
149
150         XPATH.setNamespaceContext(nsContext);
151         ERROR_LIST = XPATH.compile("ietf-restconf:errors/ietf-restconf:error");
152         ERROR_TYPE = XPATH.compile("ietf-restconf:error-type");
153         ERROR_TAG = XPATH.compile("ietf-restconf:error-tag");
154         ERROR_MESSAGE = XPATH.compile("ietf-restconf:error-message");
155         ERROR_APP_TAG = XPATH.compile("ietf-restconf:error-app-tag");
156         ERROR_INFO = XPATH.compile("ietf-restconf:error-info");
157     }
158
159     @Override
160     @Before
161     public void setUp() throws Exception {
162         reset(mockRestConf);
163         super.setUp();
164     }
165
166     @Override
167     protected Application configure() {
168         ResourceConfig resourceConfig = new ResourceConfig();
169         ControllerContext controllerContext = TestRestconfUtils.newControllerContext(schemaContext);
170         resourceConfig = resourceConfig.registerInstances(mockRestConf,
171                 new XmlNormalizedNodeBodyReader(controllerContext), new JsonNormalizedNodeBodyReader(controllerContext),
172                 new NormalizedNodeJsonBodyWriter(), new NormalizedNodeXmlBodyWriter(),
173                 new RestconfDocumentedExceptionMapper(controllerContext));
174         return resourceConfig;
175     }
176
177     void stageMockEx(final RestconfDocumentedException ex) {
178         reset(mockRestConf);
179         when(mockRestConf.readOperationalData(any(String.class), any(UriInfo.class))).thenThrow(ex);
180     }
181
182     void testJsonResponse(final RestconfDocumentedException ex, final Status expStatus, final ErrorType expErrorType,
183             final ErrorTag expErrorTag, final String expErrorMessage, final String expErrorAppTag,
184             final ErrorInfoVerifier errorInfoVerifier) throws Exception {
185
186         stageMockEx(ex);
187
188         final Response resp = target("/operational/foo").request(MediaType.APPLICATION_JSON).get();
189
190         final InputStream stream = verifyResponse(resp, MediaType.APPLICATION_JSON, expStatus);
191
192         verifyJsonResponseBody(stream, expErrorType, expErrorTag, expErrorMessage, expErrorAppTag, errorInfoVerifier);
193     }
194
195     @Test
196     public void testToJsonResponseWithMessageOnly() throws Exception {
197
198         testJsonResponse(new RestconfDocumentedException("mock error"), Status.INTERNAL_SERVER_ERROR,
199                 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null, null);
200
201
202         // To test verification code
203         // String json =
204         // "{ errors: {" +
205         // "    error: [{" +
206         // "      error-tag : \"operation-failed\"" +
207         // "      ,error-type : \"application\"" +
208         // "      ,error-message : \"An error occurred\"" +
209         // "      ,error-info : {" +
210         // "        session-id: \"123\"" +
211         // "        ,address: \"1.2.3.4\"" +
212         // "      }" +
213         // "    }]" +
214         // "  }" +
215         // "}";
216         //
217         // verifyJsonResponseBody( new java.io.StringBufferInputStream(json ),
218         // ErrorType.APPLICATION,
219         // ErrorTag.OPERATION_FAILED, "An error occurred", null,
220         // com.google.common.collect.ImmutableMap.of( "session-id", "123",
221         // "address", "1.2.3.4" ) );
222     }
223
224     @Test
225     public void testToJsonResponseWithInUseErrorTag() throws Exception {
226
227         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.IN_USE),
228                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.IN_USE, "mock error", null, null);
229     }
230
231     @Test
232     public void testToJsonResponseWithInvalidValueErrorTag() throws Exception {
233
234         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.RPC, ErrorTag.INVALID_VALUE),
235                 Status.BAD_REQUEST, ErrorType.RPC, ErrorTag.INVALID_VALUE, "mock error", null, null);
236
237     }
238
239     @Test
240     public void testToJsonResponseWithTooBigErrorTag() throws Exception {
241
242         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.TRANSPORT, ErrorTag.TOO_BIG),
243                 Status.REQUEST_ENTITY_TOO_LARGE, ErrorType.TRANSPORT, ErrorTag.TOO_BIG, "mock error", null, null);
244
245     }
246
247     @Test
248     public void testToJsonResponseWithMissingAttributeErrorTag() throws Exception {
249
250         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE),
251                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE, "mock error", null, null);
252     }
253
254     @Test
255     public void testToJsonResponseWithBadAttributeErrorTag() throws Exception {
256
257         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE),
258                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE, "mock error", null, null);
259     }
260
261     @Test
262     public void testToJsonResponseWithUnknownAttributeErrorTag() throws Exception {
263
264         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE),
265                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE, "mock error", null, null);
266     }
267
268     @Test
269     public void testToJsonResponseWithBadElementErrorTag() throws Exception {
270
271         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT),
272                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT, "mock error", null, null);
273     }
274
275     @Test
276     public void testToJsonResponseWithUnknownElementErrorTag() throws Exception {
277
278         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT),
279                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT, "mock error", null, null);
280     }
281
282     @Test
283     public void testToJsonResponseWithUnknownNamespaceErrorTag() throws Exception {
284
285         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE),
286                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE, "mock error", null, null);
287     }
288
289     @Test
290     public void testToJsonResponseWithMalformedMessageErrorTag() throws Exception {
291
292         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE),
293                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, "mock error", null, null);
294     }
295
296     @Test
297     public void testToJsonResponseWithAccessDeniedErrorTag() throws Exception {
298
299         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.ACCESS_DENIED),
300                 Status.FORBIDDEN, ErrorType.PROTOCOL, ErrorTag.ACCESS_DENIED, "mock error", null, null);
301     }
302
303     @Test
304     public void testToJsonResponseWithLockDeniedErrorTag() throws Exception {
305
306         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.LOCK_DENIED),
307                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.LOCK_DENIED, "mock error", null, null);
308     }
309
310     @Test
311     public void testToJsonResponseWithResourceDeniedErrorTag() throws Exception {
312
313         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.RESOURCE_DENIED),
314                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.RESOURCE_DENIED, "mock error", null, null);
315     }
316
317     @Test
318     public void testToJsonResponseWithRollbackFailedErrorTag() throws Exception {
319
320         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.ROLLBACK_FAILED),
321                 Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.ROLLBACK_FAILED, "mock error", null, null);
322     }
323
324     @Test
325     public void testToJsonResponseWithDataExistsErrorTag() throws Exception {
326
327         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS),
328                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, "mock error", null, null);
329     }
330
331     @Test
332     public void testToJsonResponseWithDataMissingErrorTag() throws Exception {
333
334         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING),
335                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, "mock error", null, null);
336     }
337
338     @Test
339     public void testToJsonResponseWithOperationNotSupportedErrorTag() throws Exception {
340
341         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL,
342                 ErrorTag.OPERATION_NOT_SUPPORTED), Status.NOT_IMPLEMENTED, ErrorType.PROTOCOL,
343                 ErrorTag.OPERATION_NOT_SUPPORTED, "mock error", null, null);
344     }
345
346     @Test
347     public void testToJsonResponseWithOperationFailedErrorTag() throws Exception {
348
349         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED),
350                 Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED, "mock error", null, null);
351     }
352
353     @Test
354     public void testToJsonResponseWithPartialOperationErrorTag() throws Exception {
355
356         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.PARTIAL_OPERATION),
357                 Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.PARTIAL_OPERATION, "mock error", null, null);
358     }
359
360     @Test
361     public void testToJsonResponseWithErrorAppTag() throws Exception {
362
363         testJsonResponse(new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION,
364                 ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag")), Status.BAD_REQUEST, ErrorType.APPLICATION,
365                 ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", null);
366     }
367
368     @Test
369     @Ignore // FIXME : find why it return "error-type" RPC no expected APPLICATION
370     public void testToJsonResponseWithMultipleErrors() throws Exception {
371
372         final List<RestconfError> errorList = Arrays.asList(
373                 new RestconfError(ErrorType.APPLICATION, ErrorTag.LOCK_DENIED, "mock error1"),
374                 new RestconfError(ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2"));
375         stageMockEx(new RestconfDocumentedException("mock", null, errorList));
376
377         final Response resp = target("/operational/foo").request(MediaType.APPLICATION_JSON).get();
378
379         final InputStream stream = verifyResponse(resp, MediaType.APPLICATION_JSON, Status.CONFLICT);
380
381         final JsonArray arrayElement = parseJsonErrorArrayElement(stream);
382
383         assertEquals("\"error\" Json array element length", 2, arrayElement.size());
384
385         verifyJsonErrorNode(
386                 arrayElement.get(0), ErrorType.APPLICATION, ErrorTag.LOCK_DENIED, "mock error1", null, null);
387
388         verifyJsonErrorNode(arrayElement.get(1), ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2", null, null);
389     }
390
391     @Test
392     public void testToJsonResponseWithErrorInfo() throws Exception {
393
394         final String errorInfo = "<address>1.2.3.4</address> <session-id>123</session-id>";
395         testJsonResponse(new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION,
396                 ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", errorInfo)), Status.BAD_REQUEST,
397                 ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag",
398                 new SimpleErrorInfoVerifier(errorInfo));
399     }
400
401     @Test
402     public void testToJsonResponseWithExceptionCause() throws Exception {
403
404         final Exception cause = new Exception("mock exception cause");
405         testJsonResponse(new RestconfDocumentedException("mock error", cause), Status.INTERNAL_SERVER_ERROR,
406                 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null,
407                 new SimpleErrorInfoVerifier(cause.getMessage()));
408     }
409
410     void testXMLResponse(final RestconfDocumentedException ex, final Status expStatus, final ErrorType expErrorType,
411             final ErrorTag expErrorTag, final String expErrorMessage, final String expErrorAppTag,
412             final ErrorInfoVerifier errorInfoVerifier) throws Exception {
413         stageMockEx(ex);
414
415         final Response resp = target("/operational/foo").request(MediaType.APPLICATION_XML).get();
416
417         final InputStream stream = verifyResponse(resp, MediaType.APPLICATION_XML, expStatus);
418
419         verifyXMLResponseBody(stream, expErrorType, expErrorTag, expErrorMessage, expErrorAppTag, errorInfoVerifier);
420     }
421
422     @Test
423     public void testToXMLResponseWithMessageOnly() throws Exception {
424
425         testXMLResponse(new RestconfDocumentedException("mock error"), Status.INTERNAL_SERVER_ERROR,
426                 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null, null);
427
428      // To test verification code
429         // String xml =
430         // "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">"+
431         // "  <error>" +
432         // "    <error-type>application</error-type>"+
433         // "    <error-tag>operation-failed</error-tag>"+
434         // "    <error-message>An error occurred</error-message>"+
435         // "    <error-info>" +
436         // "      <session-id>123</session-id>" +
437         // "      <address>1.2.3.4</address>" +
438         // "    </error-info>" +
439         // "  </error>" +
440         // "</errors>";
441         //
442         // verifyXMLResponseBody( new java.io.StringBufferInputStream(xml),
443         // ErrorType.APPLICATION,
444         // ErrorTag.OPERATION_FAILED, "An error occurred", null,
445         // com.google.common.collect.ImmutableMap.of( "session-id", "123",
446         // "address", "1.2.3.4" ) );
447     }
448
449     @Test
450     public void testToXMLResponseWithInUseErrorTag() throws Exception {
451
452         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.IN_USE),
453                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.IN_USE, "mock error", null, null);
454     }
455
456     @Test
457     public void testToXMLResponseWithInvalidValueErrorTag() throws Exception {
458
459         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.RPC, ErrorTag.INVALID_VALUE),
460                 Status.BAD_REQUEST, ErrorType.RPC, ErrorTag.INVALID_VALUE, "mock error", null, null);
461     }
462
463     @Test
464     public void testToXMLResponseWithTooBigErrorTag() throws Exception {
465
466         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.TRANSPORT, ErrorTag.TOO_BIG),
467                 Status.REQUEST_ENTITY_TOO_LARGE, ErrorType.TRANSPORT, ErrorTag.TOO_BIG, "mock error", null, null);
468     }
469
470     @Test
471     public void testToXMLResponseWithMissingAttributeErrorTag() throws Exception {
472
473         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE),
474                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE, "mock error", null, null);
475     }
476
477     @Test
478     public void testToXMLResponseWithBadAttributeErrorTag() throws Exception {
479
480         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE),
481                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE, "mock error", null, null);
482     }
483
484     @Test
485     public void testToXMLResponseWithUnknownAttributeErrorTag() throws Exception {
486
487         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE),
488                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE, "mock error", null, null);
489     }
490
491     @Test
492     public void testToXMLResponseWithBadElementErrorTag() throws Exception {
493
494         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT),
495                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT, "mock error", null, null);
496     }
497
498     @Test
499     public void testToXMLResponseWithUnknownElementErrorTag() throws Exception {
500
501         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT),
502                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT, "mock error", null, null);
503     }
504
505     @Test
506     public void testToXMLResponseWithUnknownNamespaceErrorTag() throws Exception {
507
508         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE),
509                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE, "mock error", null, null);
510     }
511
512     @Test
513     public void testToXMLResponseWithMalformedMessageErrorTag() throws Exception {
514
515         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE),
516                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, "mock error", null, null);
517     }
518
519     @Test
520     public void testToXMLResponseWithAccessDeniedErrorTag() throws Exception {
521
522         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.ACCESS_DENIED),
523                 Status.FORBIDDEN, ErrorType.PROTOCOL, ErrorTag.ACCESS_DENIED, "mock error", null, null);
524     }
525
526     @Test
527     public void testToXMLResponseWithLockDeniedErrorTag() throws Exception {
528
529         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.LOCK_DENIED),
530                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.LOCK_DENIED, "mock error", null, null);
531     }
532
533     @Test
534     public void testToXMLResponseWithResourceDeniedErrorTag() throws Exception {
535
536         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.RESOURCE_DENIED),
537                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.RESOURCE_DENIED, "mock error", null, null);
538     }
539
540     @Test
541     public void testToXMLResponseWithRollbackFailedErrorTag() throws Exception {
542
543         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.ROLLBACK_FAILED),
544                 Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.ROLLBACK_FAILED, "mock error", null, null);
545     }
546
547     @Test
548     public void testToXMLResponseWithDataExistsErrorTag() throws Exception {
549
550         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS),
551                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, "mock error", null, null);
552     }
553
554     @Test
555     public void testToXMLResponseWithDataMissingErrorTag() throws Exception {
556
557         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING),
558                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, "mock error", null, null);
559     }
560
561     @Test
562     public void testToXMLResponseWithOperationNotSupportedErrorTag() throws Exception {
563
564         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL,
565                 ErrorTag.OPERATION_NOT_SUPPORTED), Status.NOT_IMPLEMENTED, ErrorType.PROTOCOL,
566                 ErrorTag.OPERATION_NOT_SUPPORTED, "mock error", null, null);
567     }
568
569     @Test
570     public void testToXMLResponseWithOperationFailedErrorTag() throws Exception {
571
572         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED),
573                 Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED, "mock error", null, null);
574     }
575
576     @Test
577     public void testToXMLResponseWithPartialOperationErrorTag() throws Exception {
578
579         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.PARTIAL_OPERATION),
580                 Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.PARTIAL_OPERATION, "mock error", null, null);
581     }
582
583     @Test
584     public void testToXMLResponseWithErrorAppTag() throws Exception {
585
586         testXMLResponse(new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION,
587                 ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag")), Status.BAD_REQUEST, ErrorType.APPLICATION,
588                 ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", null);
589     }
590
591     @Test
592     public void testToXMLResponseWithErrorInfo() throws Exception {
593
594         final String errorInfo = "<address>1.2.3.4</address> <session-id>123</session-id>";
595         testXMLResponse(new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION,
596                 ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", errorInfo)), Status.BAD_REQUEST,
597                 ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag",
598                 new SimpleErrorInfoVerifier(errorInfo));
599     }
600
601     @Test
602     public void testToXMLResponseWithExceptionCause() throws Exception {
603
604         final Exception cause = new Exception("mock exception cause");
605         testXMLResponse(new RestconfDocumentedException("mock error", cause), Status.INTERNAL_SERVER_ERROR,
606                 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null,
607                 new SimpleErrorInfoVerifier(cause.getMessage()));
608     }
609
610     @Test
611     @Ignore // FIXME : find why it return error-type as RPC no APPLICATION
612     public void testToXMLResponseWithMultipleErrors() throws Exception {
613
614         final List<RestconfError> errorList = Arrays.asList(
615                 new RestconfError(ErrorType.APPLICATION, ErrorTag.LOCK_DENIED, "mock error1"),
616                 new RestconfError(ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2"));
617         stageMockEx(new RestconfDocumentedException("mock", null, errorList));
618
619         final Response resp = target("/operational/foo").request(MediaType.APPLICATION_XML).get();
620
621         final InputStream stream = verifyResponse(resp, MediaType.APPLICATION_XML, Status.CONFLICT);
622
623         final Document doc = parseXMLDocument(stream);
624
625         final NodeList children = getXMLErrorList(doc, 2);
626
627         verifyXMLErrorNode(children.item(0), ErrorType.APPLICATION, ErrorTag.LOCK_DENIED, "mock error1", null, null);
628
629         verifyXMLErrorNode(children.item(1), ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2", null, null);
630     }
631
632     @Test
633     public void testToResponseWithAcceptHeader() throws Exception {
634
635         stageMockEx(new RestconfDocumentedException("mock error"));
636
637         final Response resp = target("/operational/foo").request().header("Accept", MediaType.APPLICATION_JSON).get();
638
639         final InputStream stream = verifyResponse(resp, MediaType.APPLICATION_JSON, Status.INTERNAL_SERVER_ERROR);
640
641         verifyJsonResponseBody(stream, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null, null);
642     }
643
644     @Test
645     @Ignore
646     public void testToResponseWithStatusOnly() throws Exception {
647
648         // The StructuredDataToJsonProvider should throw a
649         // RestconfDocumentedException with no data
650
651         when(mockRestConf.readOperationalData(any(String.class), any(UriInfo.class))).thenReturn(
652                 new NormalizedNodeContext(null, null));
653
654         final Response resp = target("/operational/foo").request(MediaType.APPLICATION_JSON).get();
655
656         verifyResponse(resp, MediaType.TEXT_PLAIN, Status.NOT_FOUND);
657     }
658
659     InputStream verifyResponse(final Response resp, final String expMediaType, final Status expStatus) {
660         assertEquals("getMediaType", MediaType.valueOf(expMediaType), resp.getMediaType());
661         assertEquals("getStatus", expStatus.getStatusCode(), resp.getStatus());
662
663         final Object entity = resp.getEntity();
664         assertEquals("Response entity", true, entity instanceof InputStream);
665         final InputStream stream = (InputStream) entity;
666         return stream;
667     }
668
669     void verifyJsonResponseBody(final InputStream stream, final ErrorType expErrorType, final ErrorTag expErrorTag,
670             final String expErrorMessage, final String expErrorAppTag, final ErrorInfoVerifier errorInfoVerifier)
671             throws Exception {
672
673         final JsonArray arrayElement = parseJsonErrorArrayElement(stream);
674
675         assertEquals("\"error\" Json array element length", 1, arrayElement.size());
676
677         verifyJsonErrorNode(arrayElement.get(0), expErrorType, expErrorTag, expErrorMessage, expErrorAppTag,
678                 errorInfoVerifier);
679     }
680
681     @SuppressWarnings("checkstyle:IllegalCatch")
682     private static JsonArray parseJsonErrorArrayElement(final InputStream stream) throws IOException {
683         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
684         stream.transferTo(bos);
685
686         LOG.info("JSON: {}", bos);
687
688         JsonElement rootElement;
689         try {
690             rootElement = JsonParser.parseReader(new InputStreamReader(new ByteArrayInputStream(bos.toByteArray())));
691         } catch (final Exception e) {
692             throw new IllegalArgumentException("Invalid JSON response:\n" + bos.toString(), e);
693         }
694
695         assertTrue("Root element of Json is not an Object", rootElement.isJsonObject());
696
697         final Set<Entry<String, JsonElement>> errorsEntrySet = rootElement.getAsJsonObject().entrySet();
698         assertEquals("Json Object element set count", 1, errorsEntrySet.size());
699
700         final Entry<String, JsonElement> errorsEntry = errorsEntrySet.iterator().next();
701         final JsonElement errorsElement = errorsEntry.getValue();
702         assertEquals("First Json element name", "errors", errorsEntry.getKey());
703         assertTrue("\"errors\" Json element is not an Object", errorsElement.isJsonObject());
704
705         final Set<Entry<String, JsonElement>> errorListEntrySet = errorsElement.getAsJsonObject().entrySet();
706         assertEquals("Root \"errors\" element child count", 1, errorListEntrySet.size());
707
708         final JsonElement errorListElement = errorListEntrySet.iterator().next().getValue();
709         assertEquals("\"errors\" child Json element name", "error", errorListEntrySet.iterator().next().getKey());
710         assertTrue("\"error\" Json element is not an Array", errorListElement.isJsonArray());
711
712         // As a final check, make sure there aren't multiple "error" array
713         // elements. Unfortunately,
714         // the call above to getAsJsonObject().entrySet() will out duplicate
715         // "error" elements. So
716         // we'll use regex on the json string to verify this.
717
718         final Matcher matcher = Pattern.compile("\"error\"[ ]*:[ ]*\\[", Pattern.DOTALL).matcher(bos.toString());
719         assertTrue("Expected 1 \"error\" element", matcher.find());
720         assertFalse("Found multiple \"error\" elements", matcher.find());
721
722         return errorListElement.getAsJsonArray();
723     }
724
725     void verifyJsonErrorNode(final JsonElement errorEntryElement, final ErrorType expErrorType,
726             final ErrorTag expErrorTag, final String expErrorMessage, final String expErrorAppTag,
727             final ErrorInfoVerifier errorInfoVerifier) {
728
729         JsonElement errorInfoElement = null;
730         final Map<String, String> leafMap = new HashMap<>();
731         for (final Entry<String, JsonElement> entry : errorEntryElement.getAsJsonObject().entrySet()) {
732             final String leafName = entry.getKey();
733             final JsonElement leafElement = entry.getValue();
734
735             if ("error-info".equals(leafName)) {
736                 assertNotNull("Found unexpected \"error-info\" element", errorInfoVerifier);
737                 errorInfoElement = leafElement;
738             } else {
739                 assertTrue("\"error\" leaf Json element " + leafName + " is not a Primitive",
740                         leafElement.isJsonPrimitive());
741
742                 leafMap.put(leafName, leafElement.getAsString());
743             }
744         }
745
746         assertEquals("error-type", expErrorType.elementBody(), leafMap.remove("error-type"));
747         assertEquals("error-tag", expErrorTag.elementBody(), leafMap.remove("error-tag"));
748
749         verifyOptionalJsonLeaf(leafMap.remove("error-message"), expErrorMessage, "error-message");
750         verifyOptionalJsonLeaf(leafMap.remove("error-app-tag"), expErrorAppTag, "error-app-tag");
751
752         if (!leafMap.isEmpty()) {
753             fail("Found unexpected Json leaf elements for \"error\" element: " + leafMap);
754         }
755
756         if (errorInfoVerifier != null) {
757             assertNotNull("Missing \"error-info\" element", errorInfoElement);
758             errorInfoVerifier.verifyJson(errorInfoElement);
759         }
760     }
761
762     void verifyOptionalJsonLeaf(final String actualValue, final String expValue, final String tagName) {
763         if (expValue != null) {
764             assertEquals(tagName, expValue, actualValue);
765         } else {
766             assertNull("Found unexpected \"error\" leaf entry for: " + tagName, actualValue);
767         }
768     }
769
770     void verifyXMLResponseBody(final InputStream stream, final ErrorType expErrorType, final ErrorTag expErrorTag,
771             final String expErrorMessage, final String expErrorAppTag, final ErrorInfoVerifier errorInfoVerifier)
772             throws Exception {
773
774         final Document doc = parseXMLDocument(stream);
775
776         final NodeList children = getXMLErrorList(doc, 1);
777
778         verifyXMLErrorNode(children.item(0), expErrorType, expErrorTag, expErrorMessage, expErrorAppTag,
779                 errorInfoVerifier);
780     }
781
782     private static Document parseXMLDocument(final InputStream stream) throws IOException, SAXException {
783         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
784         stream.transferTo(bos);
785
786         LOG.debug("XML: {}", bos);
787
788         return UntrustedXML.newDocumentBuilder().parse(new ByteArrayInputStream(bos.toByteArray()));
789     }
790
791     void verifyXMLErrorNode(final Node errorNode, final ErrorType expErrorType, final ErrorTag expErrorTag,
792             final String expErrorMessage, final String expErrorAppTag, final ErrorInfoVerifier errorInfoVerifier)
793             throws Exception {
794
795         final String errorType = (String) ERROR_TYPE.evaluate(errorNode, XPathConstants.STRING);
796         assertEquals("error-type", expErrorType.elementBody(), errorType);
797
798         final String errorTag = (String) ERROR_TAG.evaluate(errorNode, XPathConstants.STRING);
799         assertEquals("error-tag", expErrorTag.elementBody(), errorTag);
800
801         verifyOptionalXMLLeaf(errorNode, ERROR_MESSAGE, expErrorMessage, "error-message");
802         verifyOptionalXMLLeaf(errorNode, ERROR_APP_TAG, expErrorAppTag, "error-app-tag");
803
804         final Node errorInfoNode = (Node) ERROR_INFO.evaluate(errorNode, XPathConstants.NODE);
805         if (errorInfoVerifier != null) {
806             assertNotNull("Missing \"error-info\" node", errorInfoNode);
807
808             errorInfoVerifier.verifyXML(errorInfoNode);
809         } else {
810             assertNull("Found unexpected \"error-info\" node", errorInfoNode);
811         }
812     }
813
814     void verifyOptionalXMLLeaf(final Node fromNode, final XPathExpression xpath, final String expValue,
815             final String tagName) throws Exception {
816         if (expValue != null) {
817             final String actual = (String) xpath.evaluate(fromNode, XPathConstants.STRING);
818             assertEquals(tagName, expValue, actual);
819         } else {
820             assertNull("Found unexpected \"error\" leaf entry for: " + tagName,
821                     xpath.evaluate(fromNode, XPathConstants.NODE));
822         }
823     }
824
825     NodeList getXMLErrorList(final Node fromNode, final int count) throws Exception {
826         final NodeList errorList = (NodeList) ERROR_LIST.evaluate(fromNode, XPathConstants.NODESET);
827         assertNotNull("Root errors node is empty", errorList);
828         assertEquals("Root errors node child count", count, errorList.getLength());
829         return errorList;
830     }
831 }