/* * 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 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 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 = "
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 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 = // ""+ // " " + // " 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 { final 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 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 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> errorsEntrySet = rootElement.getAsJsonObject().entrySet(); assertEquals("Json Object element set count", 1, errorsEntrySet.size()); final Entry 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> 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 leafMap = new HashMap<>(); for (final Entry 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; } }