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