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