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