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