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