diff options
Diffstat (limited to 'netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfDocumentedExceptionMapperTest.java')
-rw-r--r-- | netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfDocumentedExceptionMapperTest.java | 831 |
1 files changed, 831 insertions, 0 deletions
diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfDocumentedExceptionMapperTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfDocumentedExceptionMapperTest.java new file mode 100644 index 0000000..f4ddb35 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfDocumentedExceptionMapperTest.java @@ -0,0 +1,831 @@ +/* + * 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.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; + +import com.google.common.collect.Iterators; +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.HashMap; +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.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.Ignore; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.netconf.sal.rest.api.Draft02; +import org.opendaylight.netconf.sal.rest.api.RestconfService; +import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeJsonBodyWriter; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeXmlBodyWriter; +import org.opendaylight.netconf.sal.rest.impl.RestconfDocumentedExceptionMapper; +import org.opendaylight.netconf.sal.rest.impl.XmlNormalizedNodeBodyReader; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.restconf.common.errors.RestconfError; +import org.opendaylight.yangtools.util.xml.UntrustedXML; +import org.opendaylight.yangtools.yang.common.ErrorTag; +import org.opendaylight.yangtools.yang.common.ErrorType; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * Unit tests for RestconfDocumentedExceptionMapper. + * + * @author Thomas Pantelis + */ +public class RestconfDocumentedExceptionMapperTest extends JerseyTest { + + interface ErrorInfoVerifier { + void verifyXML(Node errorInfoNode); + + void verifyJson(JsonElement errorInfoElement); + } + + static class SimpleErrorInfoVerifier implements ErrorInfoVerifier { + + String expTextContent; + + SimpleErrorInfoVerifier(final String expErrorInfo) { + 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()); + } + } + + private static final Logger LOG = LoggerFactory.getLogger(RestconfDocumentedExceptionMapperTest.class); + private static final String IETF_RESTCONF = "ietf-restconf"; + 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; + + private static EffectiveModelContext schemaContext; + + @BeforeClass + public static void init() throws Exception { + schemaContext = TestUtils.loadSchemaContext("/modules"); + + final NamespaceContext nsContext = new NamespaceContext() { + @Override + public Iterator<String> getPrefixes(final String namespaceURI) { + return Iterators.singletonIterator(IETF_RESTCONF); + } + + @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(); + ControllerContext controllerContext = TestRestconfUtils.newControllerContext(schemaContext); + resourceConfig = resourceConfig.registerInstances(mockRestConf, + new XmlNormalizedNodeBodyReader(controllerContext), new JsonNormalizedNodeBodyReader(controllerContext), + new NormalizedNodeJsonBodyWriter(), new NormalizedNodeXmlBodyWriter(), + new RestconfDocumentedExceptionMapper(controllerContext)); + 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); + + final Response resp = target("/operational/foo").request(MediaType.APPLICATION_JSON).get(); + + final 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 + @Ignore // FIXME : find why it return "error-type" RPC no expected APPLICATION + public void testToJsonResponseWithMultipleErrors() throws Exception { + + final List<RestconfError> errorList = Arrays.asList( + new RestconfError(ErrorType.APPLICATION, ErrorTag.LOCK_DENIED, "mock error1"), + new RestconfError(ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2")); + stageMockEx(new RestconfDocumentedException("mock", null, errorList)); + + final Response resp = target("/operational/foo").request(MediaType.APPLICATION_JSON).get(); + + final InputStream stream = verifyResponse(resp, MediaType.APPLICATION_JSON, Status.CONFLICT); + + final 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 { + + final String errorInfo = "<address>1.2.3.4</address> <session-id>123</session-id>"; + 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 SimpleErrorInfoVerifier(errorInfo)); + } + + @Test + public void testToJsonResponseWithExceptionCause() throws Exception { + + final 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); + + final Response resp = target("/operational/foo").request(MediaType.APPLICATION_XML).get(); + + final 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 = + // "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">"+ + // " <error>" + + // " <error-type>application</error-type>"+ + // " <error-tag>operation-failed</error-tag>"+ + // " <error-message>An error occurred</error-message>"+ + // " <error-info>" + + // " <session-id>123</session-id>" + + // " <address>1.2.3.4</address>" + + // " </error-info>" + + // " </error>" + + // "</errors>"; + // + // 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 { + + final String errorInfo = "<address>1.2.3.4</address> <session-id>123</session-id>"; + 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 SimpleErrorInfoVerifier(errorInfo)); + } + + @Test + public void testToXMLResponseWithExceptionCause() throws Exception { + + final 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 + @Ignore // FIXME : find why it return error-type as RPC no APPLICATION + public void testToXMLResponseWithMultipleErrors() throws Exception { + + final List<RestconfError> errorList = Arrays.asList( + new RestconfError(ErrorType.APPLICATION, ErrorTag.LOCK_DENIED, "mock error1"), + new RestconfError(ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2")); + stageMockEx(new RestconfDocumentedException("mock", null, errorList)); + + final Response resp = target("/operational/foo").request(MediaType.APPLICATION_XML).get(); + + final InputStream stream = verifyResponse(resp, MediaType.APPLICATION_XML, Status.CONFLICT); + + final Document doc = parseXMLDocument(stream); + + final 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")); + + final Response resp = target("/operational/foo").request().header("Accept", MediaType.APPLICATION_JSON).get(); + + final InputStream stream = verifyResponse(resp, MediaType.APPLICATION_JSON, Status.INTERNAL_SERVER_ERROR); + + verifyJsonResponseBody(stream, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null, null); + } + + @Test + @Ignore + 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 NormalizedNodeContext(null, null)); + + final 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()); + + final Object entity = resp.getEntity(); + assertEquals("Response entity", true, entity instanceof InputStream); + final 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 { + + final JsonArray arrayElement = parseJsonErrorArrayElement(stream); + + assertEquals("\"error\" Json array element length", 1, arrayElement.size()); + + verifyJsonErrorNode(arrayElement.get(0), expErrorType, expErrorTag, expErrorMessage, expErrorAppTag, + errorInfoVerifier); + } + + @SuppressWarnings("checkstyle:IllegalCatch") + private static JsonArray parseJsonErrorArrayElement(final InputStream stream) throws IOException { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + stream.transferTo(bos); + + LOG.info("JSON: {}", bos); + + JsonElement rootElement; + try { + rootElement = JsonParser.parseReader(new InputStreamReader(new ByteArrayInputStream(bos.toByteArray()))); + } catch (final Exception e) { + throw new IllegalArgumentException("Invalid JSON response:\n" + bos.toString(), e); + } + + assertTrue("Root element of Json is not an Object", rootElement.isJsonObject()); + + final Set<Entry<String, JsonElement>> errorsEntrySet = rootElement.getAsJsonObject().entrySet(); + assertEquals("Json Object element set count", 1, errorsEntrySet.size()); + + final Entry<String, JsonElement> errorsEntry = errorsEntrySet.iterator().next(); + final JsonElement errorsElement = errorsEntry.getValue(); + assertEquals("First Json element name", "errors", errorsEntry.getKey()); + assertTrue("\"errors\" Json element is not an Object", errorsElement.isJsonObject()); + + final Set<Entry<String, JsonElement>> errorListEntrySet = errorsElement.getAsJsonObject().entrySet(); + assertEquals("Root \"errors\" element child count", 1, errorListEntrySet.size()); + + final 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. + + final 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; + final Map<String, String> leafMap = new HashMap<>(); + for (final Entry<String, JsonElement> entry : errorEntryElement.getAsJsonObject().entrySet()) { + final String leafName = entry.getKey(); + final 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.elementBody(), leafMap.remove("error-type")); + assertEquals("error-tag", expErrorTag.elementBody(), 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 { + + final Document doc = parseXMLDocument(stream); + + final NodeList children = getXMLErrorList(doc, 1); + + verifyXMLErrorNode(children.item(0), expErrorType, expErrorTag, expErrorMessage, expErrorAppTag, + errorInfoVerifier); + } + + private static Document parseXMLDocument(final InputStream stream) throws IOException, SAXException { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + stream.transferTo(bos); + + LOG.debug("XML: {}", bos); + + return UntrustedXML.newDocumentBuilder().parse(new ByteArrayInputStream(bos.toByteArray())); + } + + void verifyXMLErrorNode(final Node errorNode, final ErrorType expErrorType, final ErrorTag expErrorTag, + final String expErrorMessage, final String expErrorAppTag, final ErrorInfoVerifier errorInfoVerifier) + throws Exception { + + final String errorType = (String) ERROR_TYPE.evaluate(errorNode, XPathConstants.STRING); + assertEquals("error-type", expErrorType.elementBody(), errorType); + + final String errorTag = (String) ERROR_TAG.evaluate(errorNode, XPathConstants.STRING); + assertEquals("error-tag", expErrorTag.elementBody(), errorTag); + + verifyOptionalXMLLeaf(errorNode, ERROR_MESSAGE, expErrorMessage, "error-message"); + verifyOptionalXMLLeaf(errorNode, ERROR_APP_TAG, expErrorAppTag, "error-app-tag"); + + final 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) { + final 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 { + final 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; + } +} |