/* * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.controller.sal.restconf.impl.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.io.ByteStreams; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.ws.rs.core.Application; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathFactory; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.opendaylight.controller.sal.rest.api.Draft02; import org.opendaylight.controller.sal.rest.api.RestconfService; import org.opendaylight.controller.sal.rest.impl.RestconfDocumentedExceptionMapper; import org.opendaylight.controller.sal.rest.impl.StructuredDataToJsonProvider; import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider; import org.opendaylight.controller.sal.restconf.impl.ControllerContext; import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException; import org.opendaylight.controller.sal.restconf.impl.RestconfError; import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag; import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType; import org.opendaylight.controller.sal.restconf.impl.StructuredData; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Unit tests for RestconfDocumentedExceptionMapper. * * @author Thomas Pantelis */ public class RestconfDocumentedExceptionMapperTest extends JerseyTest { interface ErrorInfoVerifier { void verifyXML( Node errorInfoNode ); void verifyJson( JsonElement errorInfoElement ); } static class ComplexErrorInfoVerifier implements ErrorInfoVerifier { Map expErrorInfo; public ComplexErrorInfoVerifier( final Map expErrorInfo ) { this.expErrorInfo = expErrorInfo; } @Override public void verifyXML( final Node errorInfoNode ) { Map mutableExpMap = Maps.newHashMap( expErrorInfo ); NodeList childNodes = errorInfoNode.getChildNodes(); for( int i = 0; i < childNodes.getLength(); i++ ) { Node child = childNodes.item( i ); if( child instanceof Element ) { String expValue = mutableExpMap.remove( child.getNodeName() ); assertNotNull( "Found unexpected \"error-info\" child node: " + child.getNodeName(), expValue ); assertEquals( "Text content for \"error-info\" child node " + child.getNodeName(), expValue, child.getTextContent() ); } } if( !mutableExpMap.isEmpty() ) { fail( "Missing \"error-info\" child nodes: " + mutableExpMap ); } } @Override public void verifyJson( final JsonElement errorInfoElement ) { assertTrue( "\"error-info\" Json element is not an Object", errorInfoElement.isJsonObject() ); Map actualErrorInfo = Maps.newHashMap(); for( Entry entry: errorInfoElement.getAsJsonObject().entrySet() ) { String leafName = entry.getKey(); JsonElement leafElement = entry.getValue(); actualErrorInfo.put( leafName, leafElement.getAsString() ); } Map mutableExpMap = Maps.newHashMap( expErrorInfo ); for( Entry actual: actualErrorInfo.entrySet() ) { String expValue = mutableExpMap.remove( actual.getKey() ); assertNotNull( "Found unexpected \"error-info\" child node: " + actual.getKey(), expValue ); assertEquals( "Text content for \"error-info\" child node " + actual.getKey(), expValue, actual.getValue() ); } if( !mutableExpMap.isEmpty() ) { fail( "Missing \"error-info\" child nodes: " + mutableExpMap ); } } } static class SimpleErrorInfoVerifier implements ErrorInfoVerifier { String expTextContent; public SimpleErrorInfoVerifier( final String expErrorInfo ) { this.expTextContent = expErrorInfo; } void verifyContent( final String actualContent ) { assertNotNull( "Actual \"error-info\" text content is null", actualContent ); assertTrue( "", actualContent.contains( expTextContent ) ); } @Override public void verifyXML( final Node errorInfoNode ) { verifyContent( errorInfoNode.getTextContent() ); } @Override public void verifyJson( final JsonElement errorInfoElement ) { verifyContent( errorInfoElement.getAsString() ); } } static RestconfService mockRestConf = mock( RestconfService.class ); static XPath XPATH = XPathFactory.newInstance().newXPath(); static XPathExpression ERROR_LIST; static XPathExpression ERROR_TYPE; static XPathExpression ERROR_TAG; static XPathExpression ERROR_MESSAGE; static XPathExpression ERROR_APP_TAG; static XPathExpression ERROR_INFO; @BeforeClass public static void init() throws Exception { ControllerContext.getInstance().setGlobalSchema( TestUtils.loadSchemaContext("/modules") ); NamespaceContext nsContext = new NamespaceContext() { @Override public Iterator getPrefixes( final String namespaceURI ) { return null; } @Override public String getPrefix( final String namespaceURI ) { return null; } @Override public String getNamespaceURI( final String prefix ) { return "ietf-restconf".equals( prefix ) ? Draft02.RestConfModule.NAMESPACE : null; } }; XPATH.setNamespaceContext( nsContext ); ERROR_LIST = XPATH.compile( "ietf-restconf:errors/ietf-restconf:error" ); ERROR_TYPE = XPATH.compile( "ietf-restconf:error-type" ); ERROR_TAG = XPATH.compile( "ietf-restconf:error-tag" ); ERROR_MESSAGE = XPATH.compile( "ietf-restconf:error-message" ); ERROR_APP_TAG = XPATH.compile( "ietf-restconf:error-app-tag" ); ERROR_INFO = XPATH.compile( "ietf-restconf:error-info" ); } @Override @Before public void setUp() throws Exception { reset( mockRestConf ); super.setUp(); } @Override protected Application configure() { ResourceConfig resourceConfig = new ResourceConfig(); resourceConfig = resourceConfig.registerInstances( mockRestConf, StructuredDataToXmlProvider.INSTANCE, StructuredDataToJsonProvider.INSTANCE ); resourceConfig.registerClasses( RestconfDocumentedExceptionMapper.class ); return resourceConfig; } void stageMockEx( final RestconfDocumentedException ex ) { reset( mockRestConf ); when( mockRestConf.readOperationalData( any( String.class ), any( UriInfo.class ) ) ).thenThrow( ex ); } void testJsonResponse( final RestconfDocumentedException ex, final Status expStatus, final ErrorType expErrorType, final ErrorTag expErrorTag, final String expErrorMessage, final String expErrorAppTag, final ErrorInfoVerifier errorInfoVerifier ) throws Exception { stageMockEx( ex ); Response resp = target("/operational/foo").request( MediaType.APPLICATION_JSON ).get(); InputStream stream = verifyResponse( resp, MediaType.APPLICATION_JSON, expStatus ); verifyJsonResponseBody( stream, expErrorType, expErrorTag, expErrorMessage, expErrorAppTag, errorInfoVerifier ); } @Test public void testToJsonResponseWithMessageOnly() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error" ), Status.INTERNAL_SERVER_ERROR, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null, null ); // To test verification code // String json = // "{ errors: {" + // " error: [{" + // " error-tag : \"operation-failed\"" + // " ,error-type : \"application\"" + // " ,error-message : \"An error occurred\"" + // " ,error-info : {" + // " session-id: \"123\"" + // " ,address: \"1.2.3.4\"" + // " }" + // " }]" + // " }" + // "}"; // // verifyJsonResponseBody( new java.io.StringBufferInputStream(json ), ErrorType.APPLICATION, // ErrorTag.OPERATION_FAILED, "An error occurred", null, // com.google.common.collect.ImmutableMap.of( "session-id", "123", "address", "1.2.3.4" ) ); } @Test public void testToJsonResponseWithInUseErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.IN_USE ), Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.IN_USE, "mock error", null, null ); } @Test public void testToJsonResponseWithInvalidValueErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.RPC, ErrorTag.INVALID_VALUE ), Status.BAD_REQUEST, ErrorType.RPC, ErrorTag.INVALID_VALUE, "mock error", null, null ); } @Test public void testToJsonResponseWithTooBigErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.TRANSPORT, ErrorTag.TOO_BIG ), Status.REQUEST_ENTITY_TOO_LARGE, ErrorType.TRANSPORT, ErrorTag.TOO_BIG, "mock error", null, null ); } @Test public void testToJsonResponseWithMissingAttributeErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE ), Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE, "mock error", null, null ); } @Test public void testToJsonResponseWithBadAttributeErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE ), Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE, "mock error", null, null ); } @Test public void testToJsonResponseWithUnknownAttributeErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE ), Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE, "mock error", null, null ); } @Test public void testToJsonResponseWithBadElementErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT ), Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT, "mock error", null, null ); } @Test public void testToJsonResponseWithUnknownElementErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT ), Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT, "mock error", null, null ); } @Test public void testToJsonResponseWithUnknownNamespaceErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE ), Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE, "mock error", null, null ); } @Test public void testToJsonResponseWithMalformedMessageErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE ), Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, "mock error", null, null ); } @Test public void testToJsonResponseWithAccessDeniedErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.ACCESS_DENIED ), Status.FORBIDDEN, ErrorType.PROTOCOL, ErrorTag.ACCESS_DENIED, "mock error", null, null ); } @Test public void testToJsonResponseWithLockDeniedErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.LOCK_DENIED ), Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.LOCK_DENIED, "mock error", null, null ); } @Test public void testToJsonResponseWithResourceDeniedErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.RESOURCE_DENIED ), Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.RESOURCE_DENIED, "mock error", null, null ); } @Test public void testToJsonResponseWithRollbackFailedErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.ROLLBACK_FAILED ), Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.ROLLBACK_FAILED, "mock error", null, null ); } @Test public void testToJsonResponseWithDataExistsErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS ), Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, "mock error", null, null ); } @Test public void testToJsonResponseWithDataMissingErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING ), Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, "mock error", null, null ); } @Test public void testToJsonResponseWithOperationNotSupportedErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.OPERATION_NOT_SUPPORTED ), Status.NOT_IMPLEMENTED, ErrorType.PROTOCOL, ErrorTag.OPERATION_NOT_SUPPORTED, "mock error", null, null ); } @Test public void testToJsonResponseWithOperationFailedErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED ), Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED, "mock error", null, null ); } @Test public void testToJsonResponseWithPartialOperationErrorTag() throws Exception { testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.PARTIAL_OPERATION ), Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.PARTIAL_OPERATION, "mock error", null, null ); } @Test public void testToJsonResponseWithErrorAppTag() throws Exception { testJsonResponse( new RestconfDocumentedException( new RestconfError( ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag" ) ), Status.BAD_REQUEST, ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", null ); } @Test public void testToJsonResponseWithMultipleErrors() throws Exception { List errorList = Arrays.asList( new RestconfError( ErrorType.APPLICATION, ErrorTag.LOCK_DENIED, "mock error1" ), new RestconfError( ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2" ) ); stageMockEx( new RestconfDocumentedException( errorList ) ); Response resp = target("/operational/foo").request( MediaType.APPLICATION_JSON ).get(); InputStream stream = verifyResponse( resp, MediaType.APPLICATION_JSON, Status.CONFLICT ); JsonArray arrayElement = parseJsonErrorArrayElement( stream ); assertEquals( "\"error\" Json array element length", 2, arrayElement.size() ); verifyJsonErrorNode( arrayElement.get( 0 ), ErrorType.APPLICATION, ErrorTag.LOCK_DENIED, "mock error1", null, null ); verifyJsonErrorNode( arrayElement.get( 1 ), ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2", null, null ); } @Test public void testToJsonResponseWithErrorInfo() throws Exception { String errorInfo = "
1.2.3.4
123"; testJsonResponse( new RestconfDocumentedException( new RestconfError( ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", errorInfo ) ), Status.BAD_REQUEST, ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", new ComplexErrorInfoVerifier( ImmutableMap.of( "session-id", "123", "address", "1.2.3.4" ) ) ); } @Test public void testToJsonResponseWithExceptionCause() throws Exception { Exception cause = new Exception( "mock exception cause" ); testJsonResponse( new RestconfDocumentedException( "mock error", cause ), Status.INTERNAL_SERVER_ERROR, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null, new SimpleErrorInfoVerifier( cause.getMessage() ) ); } void testXMLResponse( final RestconfDocumentedException ex, final Status expStatus, final ErrorType expErrorType, final ErrorTag expErrorTag, final String expErrorMessage, final String expErrorAppTag, final ErrorInfoVerifier errorInfoVerifier ) throws Exception { stageMockEx( ex ); Response resp = target("/operational/foo").request( MediaType.APPLICATION_XML ).get(); InputStream stream = verifyResponse( resp, MediaType.APPLICATION_XML, expStatus ); verifyXMLResponseBody( stream, expErrorType, expErrorTag, expErrorMessage, expErrorAppTag, errorInfoVerifier ); } @Test public void testToXMLResponseWithMessageOnly() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error" ), Status.INTERNAL_SERVER_ERROR, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null, null ); // To test verification code // String xml = // ""+ // " " + // " application"+ // " operation-failed"+ // " An error occurred"+ // " " + // " 123" + // "
1.2.3.4
" + // "
" + // "
" + // "
"; // // verifyXMLResponseBody( new java.io.StringBufferInputStream(xml), ErrorType.APPLICATION, // ErrorTag.OPERATION_FAILED, "An error occurred", null, // com.google.common.collect.ImmutableMap.of( "session-id", "123", "address", "1.2.3.4" ) ); } @Test public void testToXMLResponseWithInUseErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.IN_USE ), Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.IN_USE, "mock error", null, null ); } @Test public void testToXMLResponseWithInvalidValueErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.RPC, ErrorTag.INVALID_VALUE ), Status.BAD_REQUEST, ErrorType.RPC, ErrorTag.INVALID_VALUE, "mock error", null, null ); } @Test public void testToXMLResponseWithTooBigErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.TRANSPORT, ErrorTag.TOO_BIG ), Status.REQUEST_ENTITY_TOO_LARGE, ErrorType.TRANSPORT, ErrorTag.TOO_BIG, "mock error", null, null ); } @Test public void testToXMLResponseWithMissingAttributeErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE ), Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE, "mock error", null, null ); } @Test public void testToXMLResponseWithBadAttributeErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE ), Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE, "mock error", null, null ); } @Test public void testToXMLResponseWithUnknownAttributeErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE ), Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE, "mock error", null, null ); } @Test public void testToXMLResponseWithBadElementErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT ), Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT, "mock error", null, null ); } @Test public void testToXMLResponseWithUnknownElementErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT ), Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT, "mock error", null, null ); } @Test public void testToXMLResponseWithUnknownNamespaceErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE ), Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE, "mock error", null, null ); } @Test public void testToXMLResponseWithMalformedMessageErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE ), Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, "mock error", null, null ); } @Test public void testToXMLResponseWithAccessDeniedErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.ACCESS_DENIED ), Status.FORBIDDEN, ErrorType.PROTOCOL, ErrorTag.ACCESS_DENIED, "mock error", null, null ); } @Test public void testToXMLResponseWithLockDeniedErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.LOCK_DENIED ), Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.LOCK_DENIED, "mock error", null, null ); } @Test public void testToXMLResponseWithResourceDeniedErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.RESOURCE_DENIED ), Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.RESOURCE_DENIED, "mock error", null, null ); } @Test public void testToXMLResponseWithRollbackFailedErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.ROLLBACK_FAILED ), Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.ROLLBACK_FAILED, "mock error", null, null ); } @Test public void testToXMLResponseWithDataExistsErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS ), Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, "mock error", null, null ); } @Test public void testToXMLResponseWithDataMissingErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING ), Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, "mock error", null, null ); } @Test public void testToXMLResponseWithOperationNotSupportedErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.OPERATION_NOT_SUPPORTED ), Status.NOT_IMPLEMENTED, ErrorType.PROTOCOL, ErrorTag.OPERATION_NOT_SUPPORTED, "mock error", null, null ); } @Test public void testToXMLResponseWithOperationFailedErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED ), Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED, "mock error", null, null ); } @Test public void testToXMLResponseWithPartialOperationErrorTag() throws Exception { testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL, ErrorTag.PARTIAL_OPERATION ), Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.PARTIAL_OPERATION, "mock error", null, null ); } @Test public void testToXMLResponseWithErrorAppTag() throws Exception { testXMLResponse( new RestconfDocumentedException( new RestconfError( ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag" ) ), Status.BAD_REQUEST, ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", null ); } @Test public void testToXMLResponseWithErrorInfo() throws Exception { String errorInfo = "
1.2.3.4
123"; testXMLResponse( new RestconfDocumentedException( new RestconfError( ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", errorInfo ) ), Status.BAD_REQUEST, ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", new ComplexErrorInfoVerifier( ImmutableMap.of( "session-id", "123", "address", "1.2.3.4" ) ) ); } @Test public void testToXMLResponseWithExceptionCause() throws Exception { Exception cause = new Exception( "mock exception cause" ); testXMLResponse( new RestconfDocumentedException( "mock error", cause ), Status.INTERNAL_SERVER_ERROR, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null, new SimpleErrorInfoVerifier( cause.getMessage() ) ); } @Test public void testToXMLResponseWithMultipleErrors() throws Exception { List errorList = Arrays.asList( new RestconfError( ErrorType.APPLICATION, ErrorTag.LOCK_DENIED, "mock error1" ), new RestconfError( ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2" ) ); stageMockEx( new RestconfDocumentedException( errorList ) ); Response resp = target("/operational/foo").request( MediaType.APPLICATION_XML ).get(); InputStream stream = verifyResponse( resp, MediaType.APPLICATION_XML, Status.CONFLICT ); Document doc = parseXMLDocument( stream ); NodeList children = getXMLErrorList( doc, 2 ); verifyXMLErrorNode( children.item( 0 ), ErrorType.APPLICATION, ErrorTag.LOCK_DENIED, "mock error1", null, null ); verifyXMLErrorNode( children.item( 1 ), ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2", null, null ); } @Test public void testToResponseWithAcceptHeader() throws Exception { stageMockEx( new RestconfDocumentedException( "mock error" ) ); Response resp = target("/operational/foo") .request().header( "Accept", MediaType.APPLICATION_JSON ).get(); InputStream stream = verifyResponse( resp, MediaType.APPLICATION_JSON, Status.INTERNAL_SERVER_ERROR ); verifyJsonResponseBody( stream, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null, null ); } @Test public void testToResponseWithStatusOnly() throws Exception { // The StructuredDataToJsonProvider should throw a RestconfDocumentedException with no data when( mockRestConf.readOperationalData( any( String.class ), any( UriInfo.class ) ) ) .thenReturn( new StructuredData( null, null, null ) ); Response resp = target("/operational/foo").request( MediaType.APPLICATION_JSON ).get(); verifyResponse( resp, MediaType.TEXT_PLAIN, Status.NOT_FOUND ); } InputStream verifyResponse( final Response resp, final String expMediaType, final Status expStatus ) { assertEquals( "getMediaType", MediaType.valueOf( expMediaType ), resp.getMediaType() ); assertEquals( "getStatus", expStatus.getStatusCode(), resp.getStatus() ); Object entity = resp.getEntity(); assertEquals( "Response entity", true, entity instanceof InputStream ); InputStream stream = (InputStream)entity; return stream; } void verifyJsonResponseBody( final InputStream stream, final ErrorType expErrorType, final ErrorTag expErrorTag, final String expErrorMessage, final String expErrorAppTag, final ErrorInfoVerifier errorInfoVerifier ) throws Exception { JsonArray arrayElement = parseJsonErrorArrayElement( stream ); assertEquals( "\"error\" Json array element length", 1, arrayElement.size() ); verifyJsonErrorNode( arrayElement.get( 0 ), expErrorType, expErrorTag, expErrorMessage, expErrorAppTag, errorInfoVerifier ); } private JsonArray parseJsonErrorArrayElement( final InputStream stream ) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteStreams.copy( stream, bos ); System.out.println("JSON: "+bos.toString()); JsonParser parser = new JsonParser(); JsonElement rootElement; try { rootElement = parser.parse( new InputStreamReader( new ByteArrayInputStream( bos.toByteArray() ) ) ); } catch( Exception e ) { throw new IllegalArgumentException( "Invalid JSON response:\n" + bos.toString(), e ); } assertTrue( "Root element of Json is not an Object", rootElement.isJsonObject() ); Set> errorsEntrySet = rootElement.getAsJsonObject().entrySet(); assertEquals( "Json Object element set count", 1, errorsEntrySet.size() ); Entry errorsEntry = errorsEntrySet.iterator().next(); JsonElement errorsElement = errorsEntry.getValue(); assertEquals( "First Json element name", "errors", errorsEntry.getKey() ); assertTrue( "\"errors\" Json element is not an Object", errorsElement.isJsonObject() ); Set> errorListEntrySet = errorsElement.getAsJsonObject().entrySet(); assertEquals( "Root \"errors\" element child count", 1, errorListEntrySet.size() ); JsonElement errorListElement = errorListEntrySet.iterator().next().getValue(); assertEquals( "\"errors\" child Json element name", "error", errorListEntrySet.iterator().next().getKey() ); assertTrue( "\"error\" Json element is not an Array", errorListElement.isJsonArray() ); // As a final check, make sure there aren't multiple "error" array elements. Unfortunately, // the call above to getAsJsonObject().entrySet() will out duplicate "error" elements. So // we'll use regex on the json string to verify this. Matcher matcher = Pattern.compile( "\"error\"[ ]*:[ ]*\\[", Pattern.DOTALL ).matcher( bos.toString() ); assertTrue( "Expected 1 \"error\" element", matcher.find() ); assertFalse( "Found multiple \"error\" elements", matcher.find() ); return errorListElement.getAsJsonArray(); } void verifyJsonErrorNode( final JsonElement errorEntryElement, final ErrorType expErrorType, final ErrorTag expErrorTag, final String expErrorMessage, final String expErrorAppTag, final ErrorInfoVerifier errorInfoVerifier ) { JsonElement errorInfoElement = null; Map leafMap = Maps.newHashMap(); for( Entry entry: errorEntryElement.getAsJsonObject().entrySet() ) { String leafName = entry.getKey(); JsonElement leafElement = entry.getValue(); if( "error-info".equals( leafName ) ) { assertNotNull( "Found unexpected \"error-info\" element", errorInfoVerifier ); errorInfoElement = leafElement; } else { assertTrue( "\"error\" leaf Json element " + leafName + " is not a Primitive", leafElement.isJsonPrimitive() ); leafMap.put( leafName, leafElement.getAsString() ); } } assertEquals( "error-type", expErrorType.getErrorTypeTag(), leafMap.remove( "error-type" ) ); assertEquals( "error-tag", expErrorTag.getTagValue(), leafMap.remove( "error-tag" ) ); verifyOptionalJsonLeaf( leafMap.remove( "error-message" ), expErrorMessage, "error-message" ); verifyOptionalJsonLeaf( leafMap.remove( "error-app-tag" ), expErrorAppTag, "error-app-tag" ); if( !leafMap.isEmpty() ) { fail( "Found unexpected Json leaf elements for \"error\" element: " + leafMap ); } if( errorInfoVerifier != null ) { assertNotNull( "Missing \"error-info\" element", errorInfoElement ); errorInfoVerifier.verifyJson( errorInfoElement ); } } void verifyOptionalJsonLeaf( final String actualValue, final String expValue, final String tagName ) { if( expValue != null ) { assertEquals( tagName, expValue, actualValue ); } else { assertNull( "Found unexpected \"error\" leaf entry for: " + tagName, actualValue ); } } void verifyXMLResponseBody( final InputStream stream, final ErrorType expErrorType, final ErrorTag expErrorTag, final String expErrorMessage, final String expErrorAppTag, final ErrorInfoVerifier errorInfoVerifier ) throws Exception { Document doc = parseXMLDocument( stream ); NodeList children = getXMLErrorList( doc, 1 ); verifyXMLErrorNode( children.item( 0 ), expErrorType, expErrorTag, expErrorMessage, expErrorAppTag, errorInfoVerifier ); } private Document parseXMLDocument( final InputStream stream ) throws IOException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); factory.setCoalescing(true); factory.setIgnoringElementContentWhitespace(true); factory.setIgnoringComments(true); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteStreams.copy( stream, bos ); System.out.println("XML: "+bos.toString()); Document doc = null; try { doc = factory.newDocumentBuilder().parse( new ByteArrayInputStream( bos.toByteArray() ) ); } catch( Exception e ) { throw new IllegalArgumentException( "Invalid XML response:\n" + bos.toString(), e ); } return doc; } void verifyXMLErrorNode( final Node errorNode, final ErrorType expErrorType, final ErrorTag expErrorTag, final String expErrorMessage, final String expErrorAppTag, final ErrorInfoVerifier errorInfoVerifier ) throws Exception { String errorType = (String)ERROR_TYPE.evaluate( errorNode, XPathConstants.STRING ); assertEquals( "error-type", expErrorType.getErrorTypeTag(), errorType ); String errorTag = (String)ERROR_TAG.evaluate( errorNode, XPathConstants.STRING ); assertEquals( "error-tag", expErrorTag.getTagValue(), errorTag ); verifyOptionalXMLLeaf( errorNode, ERROR_MESSAGE, expErrorMessage, "error-message" ); verifyOptionalXMLLeaf( errorNode, ERROR_APP_TAG, expErrorAppTag, "error-app-tag" ); Node errorInfoNode = (Node)ERROR_INFO.evaluate( errorNode, XPathConstants.NODE ); if( errorInfoVerifier != null ) { assertNotNull( "Missing \"error-info\" node", errorInfoNode ); errorInfoVerifier.verifyXML( errorInfoNode ); } else { assertNull( "Found unexpected \"error-info\" node", errorInfoNode ); } } void verifyOptionalXMLLeaf( final Node fromNode, final XPathExpression xpath, final String expValue, final String tagName ) throws Exception { if( expValue != null ) { String actual = (String)xpath.evaluate( fromNode, XPathConstants.STRING ); assertEquals( tagName, expValue, actual ); } else { assertNull( "Found unexpected \"error\" leaf entry for: " + tagName, xpath.evaluate( fromNode, XPathConstants.NODE ) ); } } NodeList getXMLErrorList( final Node fromNode, final int count ) throws Exception { NodeList errorList = (NodeList)ERROR_LIST.evaluate( fromNode, XPathConstants.NODESET ); assertNotNull( "Root errors node is empty", errorList ); assertEquals( "Root errors node child count", count, errorList.getLength() ); return errorList; } }