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