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