diff options
Diffstat (limited to 'netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight')
76 files changed, 12221 insertions, 0 deletions
diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/md/sal/rest/common/TestRestconfUtils.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/md/sal/rest/common/TestRestconfUtils.java new file mode 100644 index 0000000..d883797 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/md/sal/rest/common/TestRestconfUtils.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2015 Cisco 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.md.sal.rest.common; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableClassToInstanceMap; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import javax.xml.stream.XMLStreamException; +import javax.xml.transform.dom.DOMSource; +import org.opendaylight.controller.sal.rest.impl.test.providers.TestJsonBodyWriter; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.mdsal.dom.api.DOMMountPointService; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.yangtools.util.xml.UntrustedXML; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult; +import org.opendaylight.yangtools.yang.model.api.ContainerLike; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; +import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +public final class TestRestconfUtils { + + private static final Logger LOG = LoggerFactory.getLogger(TestRestconfUtils.class); + + private TestRestconfUtils() { + throw new UnsupportedOperationException("Test utility class"); + } + + public static ControllerContext newControllerContext(final EffectiveModelContext schemaContext) { + return newControllerContext(schemaContext, null); + } + + public static ControllerContext newControllerContext(final EffectiveModelContext schemaContext, + final DOMMountPoint mountInstance) { + final DOMMountPointService mockMountService = mock(DOMMountPointService.class); + + if (mountInstance != null) { + doReturn(Optional.of(FixedDOMSchemaService.of(() -> schemaContext))).when(mountInstance) + .getService(eq(DOMSchemaService.class)); + doReturn(Optional.ofNullable(mountInstance)).when(mockMountService).getMountPoint( + any(YangInstanceIdentifier.class)); + } + + DOMSchemaService mockSchemaService = mock(DOMSchemaService.class); + doReturn(schemaContext).when(mockSchemaService).getGlobalContext(); + + DOMSchemaService mockDomSchemaService = mock(DOMSchemaService.class); + doReturn(ImmutableClassToInstanceMap.of()).when(mockDomSchemaService).getExtensions(); + + return ControllerContext.newInstance(mockSchemaService, mockMountService, mockDomSchemaService); + } + + @SuppressWarnings("checkstyle:IllegalCatch") + public static EffectiveModelContext loadSchemaContext(final String yangPath, + final EffectiveModelContext schemaContext) { + try { + Preconditions.checkArgument(yangPath != null, "Path can not be null."); + Preconditions.checkArgument(!yangPath.isEmpty(), "Path can not be empty."); + if (schemaContext == null) { + return YangParserTestUtils.parseYangFiles(TestRestconfUtils.loadFiles(yangPath)); + } else { + throw new UnsupportedOperationException("Unable to add new yang sources to existing schema context."); + } + } catch (final Exception e) { + LOG.error("Yang files at path: " + yangPath + " weren't loaded.", e); + } + return schemaContext; + } + + public static NormalizedNodeContext loadNormalizedContextFromJsonFile() { + throw new AbstractMethodError("Not implemented yet"); + } + + @SuppressWarnings("checkstyle:IllegalCatch") + public static NormalizedNodeContext loadNormalizedContextFromXmlFile(final String pathToInputFile, + final String uri, final ControllerContext controllerContext) { + final InstanceIdentifierContext iiContext = controllerContext.toInstanceIdentifier(uri); + final InputStream inputStream = TestJsonBodyWriter.class.getResourceAsStream(pathToInputFile); + try { + final Document doc = UntrustedXML.newDocumentBuilder().parse(inputStream); + final NormalizedNode nn = parse(iiContext, doc); + return new NormalizedNodeContext(iiContext, nn); + } catch (final Exception e) { + LOG.error("Load xml file " + pathToInputFile + " fail.", e); + } + return null; + } + + private static NormalizedNode parse(final InstanceIdentifierContext iiContext, final Document doc) + throws XMLStreamException, IOException, SAXException, URISyntaxException { + final SchemaNode schemaNodeContext = iiContext.getSchemaNode(); + final SchemaInferenceStack stack; + DataSchemaNode schemaNode = null; + if (schemaNodeContext instanceof RpcDefinition) { + final var rpc = (RpcDefinition) schemaNodeContext; + stack = SchemaInferenceStack.of(iiContext.getSchemaContext()); + stack.enterSchemaTree(rpc.getQName()); + if ("input".equalsIgnoreCase(doc.getDocumentElement().getLocalName())) { + schemaNode = rpc.getInput(); + } else if ("output".equalsIgnoreCase(doc.getDocumentElement().getLocalName())) { + schemaNode = rpc.getOutput(); + } else { + throw new IllegalStateException("Unknown Rpc input node"); + } + stack.enterSchemaTree(schemaNode.getQName()); + } else if (schemaNodeContext instanceof DataSchemaNode) { + schemaNode = (DataSchemaNode) schemaNodeContext; + stack = iiContext.inference().toSchemaInferenceStack(); + } else { + throw new IllegalStateException("Unknow SchemaNode"); + } + + final String docRootElm = doc.getDocumentElement().getLocalName(); + final String schemaNodeName = iiContext.getSchemaNode().getQName().getLocalName(); + + if (!schemaNodeName.equalsIgnoreCase(docRootElm)) { + for (final DataSchemaNode child : ((DataNodeContainer) schemaNode).getChildNodes()) { + if (child.getQName().getLocalName().equalsIgnoreCase(docRootElm)) { + schemaNode = child; + stack.enterSchemaTree(child.getQName()); + break; + } + } + } + + final NormalizedNodeResult resultHolder = new NormalizedNodeResult(); + final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder); + final XmlParserStream xmlParser = XmlParserStream.create(writer, stack.toInference()); + + if (schemaNode instanceof ContainerLike || schemaNode instanceof ListSchemaNode) { + xmlParser.traverse(new DOMSource(doc.getDocumentElement())); + return resultHolder.getResult(); + } + // FIXME : add another DataSchemaNode extensions e.g. LeafSchemaNode + return null; + } + + public static Collection<File> loadFiles(final String resourceDirectory) throws FileNotFoundException { + final String path = TestRestconfUtils.class.getResource(resourceDirectory).getPath(); + final File testDir = new File(path); + final String[] fileList = testDir.list(); + final List<File> testFiles = new ArrayList<>(); + if (fileList == null) { + throw new FileNotFoundException(resourceDirectory); + } + for (final String fileName : fileList) { + if (new File(testDir, fileName).isDirectory() == false) { + testFiles.add(new File(testDir, fileName)); + } + } + return testFiles; + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/InstanceIdentifierTypeLeafTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/InstanceIdentifierTypeLeafTest.java new file mode 100644 index 0000000..3c46201 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/InstanceIdentifierTypeLeafTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016 Cisco 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.rest.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; + +public class InstanceIdentifierTypeLeafTest { + + @Test + public void stringToInstanceIdentifierTest() throws Exception { + final EffectiveModelContext schemaContext = + YangParserTestUtils.parseYangFiles(TestRestconfUtils.loadFiles("/instanceidentifier")); + ControllerContext controllerContext = TestRestconfUtils.newControllerContext(schemaContext); + final InstanceIdentifierContext instanceIdentifier = + controllerContext.toInstanceIdentifier( + "/iid-value-module:cont-iid/iid-list/%2Fiid-value-module%3Acont-iid%2Fiid-value-module%3A" + + "values-iid%5Biid-value-module:value-iid='value'%5D"); + final YangInstanceIdentifier yiD = instanceIdentifier.getInstanceIdentifier(); + assertNotNull(yiD); + final PathArgument lastPathArgument = yiD.getLastPathArgument(); + assertTrue(lastPathArgument.getNodeType().getNamespace().toString().equals("iid:value:module")); + assertTrue(lastPathArgument.getNodeType().getLocalName().equals("iid-list")); + + final NodeIdentifierWithPredicates list = (NodeIdentifierWithPredicates) lastPathArgument; + final YangInstanceIdentifier value = (YangInstanceIdentifier) list.getValue( + QName.create(lastPathArgument.getNodeType(), "iid-leaf")); + final PathArgument lastPathArgumentOfValue = value.getLastPathArgument(); + assertTrue(lastPathArgumentOfValue.getNodeType().getNamespace().toString().equals("iid:value:module")); + assertTrue(lastPathArgumentOfValue.getNodeType().getLocalName().equals("values-iid")); + + final NodeIdentifierWithPredicates valueList = (NodeIdentifierWithPredicates) lastPathArgumentOfValue; + final String valueIid = (String) valueList.getValue( + QName.create(lastPathArgumentOfValue.getNodeType(), "value-iid")); + assertEquals("value", valueIid); + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/AbstractBodyReaderTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/AbstractBodyReaderTest.java new file mode 100644 index 0000000..5bbd161 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/AbstractBodyReaderTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2015 Cisco 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.rest.impl.test.providers; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.net.URI; +import java.util.List; +import java.util.Optional; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.UriInfo; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.netconf.sal.rest.api.RestconfConstants; +import org.opendaylight.netconf.sal.rest.impl.AbstractIdentifierAwareJaxRsProvider; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.restconf.common.patch.PatchContext; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; + +public abstract class AbstractBodyReaderTest { + private static Field uriField; + private static Field requestField; + + static { + try { + uriField = AbstractIdentifierAwareJaxRsProvider.class.getDeclaredField("uriInfo"); + uriField.setAccessible(true); + requestField = AbstractIdentifierAwareJaxRsProvider.class.getDeclaredField("request"); + requestField.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + protected final ControllerContext controllerContext; + protected final MediaType mediaType; + + protected AbstractBodyReaderTest(final EffectiveModelContext schemaContext, final DOMMountPoint mountInstance) { + mediaType = getMediaType(); + + controllerContext = TestRestconfUtils.newControllerContext(schemaContext, mountInstance); + } + + protected abstract MediaType getMediaType(); + + protected static EffectiveModelContext schemaContextLoader(final String yangPath, + final EffectiveModelContext schemaContext) { + return TestRestconfUtils.loadSchemaContext(yangPath, schemaContext); + } + + protected static <T extends AbstractIdentifierAwareJaxRsProvider> void mockBodyReader( + final String identifier, final T normalizedNodeProvider, final boolean isPost) throws Exception { + final UriInfo uriInfoMock = mock(UriInfo.class); + final MultivaluedMap<String, String> pathParm = new MultivaluedHashMap<>(1); + + if (!identifier.isEmpty()) { + pathParm.put(RestconfConstants.IDENTIFIER, List.of(identifier)); + } + + when(uriInfoMock.getPathParameters()).thenReturn(pathParm); + when(uriInfoMock.getPathParameters(false)).thenReturn(pathParm); + when(uriInfoMock.getPathParameters(true)).thenReturn(pathParm); + when(uriInfoMock.getAbsolutePath()).thenReturn(URI.create("restconf")); + uriField.set(normalizedNodeProvider, uriInfoMock); + + final Request request = mock(Request.class); + if (isPost) { + when(request.getMethod()).thenReturn("POST"); + } else { + when(request.getMethod()).thenReturn("PUT"); + } + + requestField.set(normalizedNodeProvider, request); + } + + protected static void checkMountPointNormalizedNodeContext(final NormalizedNodeContext nnContext) { + checkNormalizedNodeContext(nnContext); + assertNotNull(nnContext.getInstanceIdentifierContext().getMountPoint()); + } + + protected static void checkNormalizedNodeContext(final NormalizedNodeContext nnContext) { + assertNotNull(nnContext.getData()); + assertNotNull(nnContext.getInstanceIdentifierContext().getInstanceIdentifier()); + assertNotNull(nnContext.getInstanceIdentifierContext().getSchemaContext()); + assertNotNull(nnContext.getInstanceIdentifierContext().getSchemaNode()); + } + + protected static void checkNormalizedNodeContextRpc(final NormalizedNodeContext nnContext) { + assertNotNull(nnContext.getData()); + assertNull(nnContext.getInstanceIdentifierContext().getInstanceIdentifier()); + assertNotNull(nnContext.getInstanceIdentifierContext().getSchemaContext()); + assertNotNull(nnContext.getInstanceIdentifierContext().getSchemaNode()); + } + + protected static void checkPatchContext(final PatchContext patchContext) { + assertNotNull(patchContext.getData()); + assertNotNull(patchContext.getInstanceIdentifierContext().getInstanceIdentifier()); + assertNotNull(patchContext.getInstanceIdentifierContext().getSchemaContext()); + assertNotNull(patchContext.getInstanceIdentifierContext().getSchemaNode()); + } + + protected static void checkPatchContextMountPoint(final PatchContext patchContext) { + checkPatchContext(patchContext); + assertNotNull(patchContext.getInstanceIdentifierContext().getMountPoint()); + } + + protected static EffectiveModelContext modelContext(final DOMMountPoint mountPoint) { + return mountPoint.getService(DOMSchemaService.class) + .flatMap(svc -> Optional.ofNullable(svc.getGlobalContext())) + .orElse(null); + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonBodyReader.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonBodyReader.java new file mode 100644 index 0000000..fe50c68 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonBodyReader.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2015 Cisco 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.rest.impl.test.providers; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.InputStream; +import java.util.Collection; +import java.util.Optional; +import java.util.Set; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.common.XMLNamespace; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; + +public class TestJsonBodyReader extends AbstractBodyReaderTest { + + private final JsonNormalizedNodeBodyReader jsonBodyReader; + private static EffectiveModelContext schemaContext; + + private static final QNameModule INSTANCE_IDENTIFIER_MODULE_QNAME = QNameModule.create( + XMLNamespace.of("instance:identifier:module"), Revision.of("2014-01-17")); + + public TestJsonBodyReader() { + super(schemaContext, null); + jsonBodyReader = new JsonNormalizedNodeBodyReader(controllerContext); + } + + @Override + protected MediaType getMediaType() { + return new MediaType(MediaType.APPLICATION_XML, null); + } + + @BeforeClass + public static void initialization() + throws Exception { + final Collection<File> testFiles = TestRestconfUtils.loadFiles("/instanceidentifier/yang"); + testFiles.addAll(TestRestconfUtils.loadFiles("/invoke-rpc")); + schemaContext = YangParserTestUtils.parseYangFiles(testFiles); + } + + @Test + public void moduleDataTest() throws Exception { + final DataSchemaNode dataSchemaNode = + schemaContext.getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final YangInstanceIdentifier dataII = YangInstanceIdentifier.of(dataSchemaNode.getQName()); + final String uri = "instance-identifier-module:cont"; + mockBodyReader(uri, jsonBodyReader, false); + final InputStream inputStream = TestJsonBodyReader.class + .getResourceAsStream("/instanceidentifier/json/jsondata.json"); + final NormalizedNodeContext returnValue = jsonBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNode, returnValue, dataII); + } + + @Test + public void moduleSubContainerDataPutTest() throws Exception { + final DataSchemaNode dataSchemaNode = + schemaContext.getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final QName cont1QName = QName.create(dataSchemaNode.getQName(), "cont1"); + final YangInstanceIdentifier dataII = YangInstanceIdentifier.of(dataSchemaNode.getQName()).node(cont1QName); + final DataSchemaNode dataSchemaNodeOnPath = ((DataNodeContainer) dataSchemaNode).getDataChildByName(cont1QName); + final String uri = "instance-identifier-module:cont/cont1"; + mockBodyReader(uri, jsonBodyReader, false); + final InputStream inputStream = TestJsonBodyReader.class + .getResourceAsStream("/instanceidentifier/json/json_sub_container.json"); + final NormalizedNodeContext returnValue = jsonBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNodeOnPath, returnValue, dataII); + } + + @Test + public void moduleSubContainerDataPostTest() throws Exception { + final DataSchemaNode dataSchemaNode = + schemaContext.getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final QName cont1QName = QName.create(dataSchemaNode.getQName(), "cont1"); + final YangInstanceIdentifier dataII = YangInstanceIdentifier.of(dataSchemaNode.getQName()).node(cont1QName); + final String uri = "instance-identifier-module:cont"; + mockBodyReader(uri, jsonBodyReader, true); + final InputStream inputStream = TestJsonBodyReader.class + .getResourceAsStream("/instanceidentifier/json/json_sub_container.json"); + final NormalizedNodeContext returnValue = jsonBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNode, returnValue, dataII); + } + + @Test + public void moduleSubContainerAugmentDataPostTest() throws Exception { + final DataSchemaNode dataSchemaNode = + schemaContext.getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final Module augmentModule = schemaContext.findModules(XMLNamespace.of("augment:module")).iterator().next(); + final QName contAugmentQName = QName.create(augmentModule.getQNameModule(), "cont-augment"); + final AugmentationIdentifier augII = new AugmentationIdentifier(Set.of(contAugmentQName)); + final YangInstanceIdentifier dataII = YangInstanceIdentifier.of(dataSchemaNode.getQName()) + .node(augII).node(contAugmentQName); + final String uri = "instance-identifier-module:cont"; + mockBodyReader(uri, jsonBodyReader, true); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/json/json_augment_container.json"); + final NormalizedNodeContext returnValue = jsonBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNode, returnValue, dataII); + } + + //FIXME: Uncomment this when JsonParserStream works correctly with case augmentation with choice + //@Test + public void moduleSubContainerChoiceAugmentDataPostTest() throws Exception { + final DataSchemaNode dataSchemaNode = + schemaContext.getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final Module augmentModule = schemaContext.findModules(XMLNamespace.of("augment:module")).iterator().next(); + final QName augmentChoice1QName = QName.create(augmentModule.getQNameModule(), "augment-choice1"); + final QName augmentChoice2QName = QName.create(augmentChoice1QName, "augment-choice2"); + final QName containerQName = QName.create(augmentChoice1QName, "case-choice-case-container1"); + final AugmentationIdentifier augChoice1II = new AugmentationIdentifier(Set.of(augmentChoice1QName)); + final AugmentationIdentifier augChoice2II = new AugmentationIdentifier(Set.of(augmentChoice2QName)); + final YangInstanceIdentifier dataII = YangInstanceIdentifier.of(dataSchemaNode.getQName()) + .node(augChoice1II).node(augmentChoice1QName).node(augChoice2II).node(augmentChoice2QName) + .node(containerQName); + final String uri = "instance-identifier-module:cont"; + mockBodyReader(uri, jsonBodyReader, true); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/json/json_augment_choice_container.json"); + final NormalizedNodeContext returnValue = jsonBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNode, returnValue, dataII); + } + + @Test + public void rpcModuleInputTest() throws Exception { + final String uri = "invoke-rpc-module:rpc-test"; + mockBodyReader(uri, jsonBodyReader, true); + final InputStream inputStream = TestJsonBodyReader.class.getResourceAsStream("/invoke-rpc/json/rpc-input.json"); + final NormalizedNodeContext returnValue = jsonBodyReader.readFrom(null, null, null, mediaType, null, + inputStream); + checkNormalizedNodeContextRpc(returnValue); + final ContainerNode inputNode = (ContainerNode) returnValue.getData(); + final YangInstanceIdentifier yangCont = YangInstanceIdentifier.of( + QName.create(inputNode.getIdentifier().getNodeType(), "cont")); + final Optional<DataContainerChild> contDataNode = inputNode.findChildByArg(yangCont.getLastPathArgument()); + assertTrue(contDataNode.isPresent()); + assertTrue(contDataNode.get() instanceof ContainerNode); + final YangInstanceIdentifier yangleaf = YangInstanceIdentifier.of( + QName.create(inputNode.getIdentifier().getNodeType(), "lf")); + final Optional<DataContainerChild> leafDataNode = ((ContainerNode) contDataNode.get()) + .findChildByArg(yangleaf.getLastPathArgument()); + assertTrue(leafDataNode.isPresent()); + assertTrue("lf-test".equalsIgnoreCase(leafDataNode.get().body().toString())); + } + + private static void checkExpectValueNormalizeNodeContext(final DataSchemaNode dataSchemaNode, + final NormalizedNodeContext nnContext, final YangInstanceIdentifier dataNodeIdent) { + assertEquals(dataSchemaNode, nnContext.getInstanceIdentifierContext().getSchemaNode()); + assertEquals(dataNodeIdent, nnContext.getInstanceIdentifierContext().getInstanceIdentifier()); + assertNotNull(NormalizedNodes.findNode(nnContext.getData(), dataNodeIdent)); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonBodyReaderMountPoint.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonBodyReaderMountPoint.java new file mode 100644 index 0000000..e8776c8 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonBodyReaderMountPoint.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2015 Cisco 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.rest.impl.test.providers; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import java.io.File; +import java.io.InputStream; +import java.util.Collection; +import java.util.Optional; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.common.XMLNamespace; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; + +public class TestJsonBodyReaderMountPoint extends AbstractBodyReaderTest { + + private final JsonNormalizedNodeBodyReader jsonBodyReader; + private static EffectiveModelContext schemaContext; + + private static final QNameModule INSTANCE_IDENTIFIER_MODULE_QNAME = QNameModule.create( + XMLNamespace.of("instance:identifier:module"), Revision.of("2014-01-17")); + + public TestJsonBodyReaderMountPoint() throws NoSuchFieldException, SecurityException { + super(schemaContext, mock(DOMMountPoint.class)); + jsonBodyReader = new JsonNormalizedNodeBodyReader(controllerContext); + } + + @Override + protected MediaType getMediaType() { + return new MediaType(MediaType.APPLICATION_XML, null); + } + + @BeforeClass + public static void initialization() throws Exception { + final Collection<File> testFiles = TestRestconfUtils.loadFiles("/instanceidentifier/yang"); + testFiles.addAll(TestRestconfUtils.loadFiles("/invoke-rpc")); + schemaContext = YangParserTestUtils.parseYangFiles(testFiles); + } + + @Test + public void moduleDataTest() throws Exception { + final DataSchemaNode dataSchemaNode = schemaContext + .getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final String uri = "instance-identifier-module:cont/yang-ext:mount/instance-identifier-module:cont"; + mockBodyReader(uri, jsonBodyReader, false); + final InputStream inputStream = TestJsonBodyReaderMountPoint.class + .getResourceAsStream("/instanceidentifier/json/jsondata.json"); + final NormalizedNodeContext returnValue = jsonBodyReader.readFrom(null, + null, null, mediaType, null, inputStream); + checkMountPointNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNode, returnValue); + } + + @Test + public void moduleSubContainerDataPutTest() throws Exception { + final DataSchemaNode dataSchemaNode = schemaContext + .getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final String uri = "instance-identifier-module:cont/yang-ext:mount/instance-identifier-module:cont/cont1"; + mockBodyReader(uri, jsonBodyReader, false); + final InputStream inputStream = TestJsonBodyReaderMountPoint.class + .getResourceAsStream("/instanceidentifier/json/json_sub_container.json"); + final NormalizedNodeContext returnValue = jsonBodyReader.readFrom(null, + null, null, mediaType, null, inputStream); + checkMountPointNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNode, returnValue, + QName.create(dataSchemaNode.getQName(), "cont1")); + } + + @Test + public void moduleSubContainerDataPostTest() throws Exception { + final DataSchemaNode dataSchemaNode = schemaContext + .getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final String uri = "instance-identifier-module:cont/yang-ext:mount/instance-identifier-module:cont"; + mockBodyReader(uri, jsonBodyReader, true); + final InputStream inputStream = TestJsonBodyReaderMountPoint.class + .getResourceAsStream("/instanceidentifier/json/json_sub_container.json"); + final NormalizedNodeContext returnValue = jsonBodyReader.readFrom(null, + null, null, mediaType, null, inputStream); + checkMountPointNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNode, returnValue); + } + + @Test + public void rpcModuleInputTest() throws Exception { + final String uri = "instance-identifier-module:cont/yang-ext:mount/invoke-rpc-module:rpc-test"; + mockBodyReader(uri, jsonBodyReader, true); + final InputStream inputStream = TestJsonBodyReaderMountPoint.class.getResourceAsStream( + "/invoke-rpc/json/rpc-input.json"); + final NormalizedNodeContext returnValue = jsonBodyReader.readFrom(null, null, null, mediaType, null, + inputStream); + checkNormalizedNodeContextRpc(returnValue); + final ContainerNode inputNode = (ContainerNode) returnValue.getData(); + final YangInstanceIdentifier yangCont = YangInstanceIdentifier.of( + QName.create(inputNode.getIdentifier().getNodeType(), "cont")); + final Optional<DataContainerChild> contDataNode = inputNode.findChildByArg(yangCont.getLastPathArgument()); + assertTrue(contDataNode.isPresent()); + assertTrue(contDataNode.get() instanceof ContainerNode); + final YangInstanceIdentifier yangleaf = YangInstanceIdentifier.of( + QName.create(inputNode.getIdentifier().getNodeType(), "lf")); + final Optional<DataContainerChild> leafDataNode = + ((ContainerNode) contDataNode.get()).findChildByArg(yangleaf.getLastPathArgument()); + assertTrue(leafDataNode.isPresent()); + assertTrue("lf-test".equalsIgnoreCase(leafDataNode.get().body().toString())); + } + + private void checkExpectValueNormalizeNodeContext(final DataSchemaNode dataSchemaNode, + final NormalizedNodeContext nnContext) { + checkExpectValueNormalizeNodeContext(dataSchemaNode, nnContext, null); + } + + protected void checkExpectValueNormalizeNodeContext(final DataSchemaNode dataSchemaNode, + final NormalizedNodeContext nnContext, final QName qualifiedName) { + YangInstanceIdentifier dataNodeIdent = YangInstanceIdentifier.of(dataSchemaNode.getQName()); + final DOMMountPoint mountPoint = nnContext.getInstanceIdentifierContext().getMountPoint(); + final DataSchemaNode mountDataSchemaNode = modelContext(mountPoint).getDataChildByName( + dataSchemaNode.getQName()); + assertNotNull(mountDataSchemaNode); + if (qualifiedName != null && dataSchemaNode instanceof DataNodeContainer) { + final DataSchemaNode child = ((DataNodeContainer) dataSchemaNode).getDataChildByName(qualifiedName); + dataNodeIdent = YangInstanceIdentifier.builder(dataNodeIdent).node(child.getQName()).build(); + assertTrue(nnContext.getInstanceIdentifierContext().getSchemaNode().equals(child)); + } else { + assertTrue(mountDataSchemaNode.equals(dataSchemaNode)); + } + assertNotNull(NormalizedNodes.findNode(nnContext.getData(), + dataNodeIdent)); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonBodyWriter.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonBodyWriter.java new file mode 100644 index 0000000..121a7d6 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonBodyWriter.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2015 Cisco 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.rest.impl.test.providers; + +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +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.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; + +public class TestJsonBodyWriter extends AbstractBodyReaderTest { + + private final JsonNormalizedNodeBodyReader jsonBodyReader; + private final NormalizedNodeJsonBodyWriter jsonBodyWriter; + private static EffectiveModelContext schemaContext; + + public TestJsonBodyWriter() { + super(schemaContext, null); + this.jsonBodyWriter = new NormalizedNodeJsonBodyWriter(); + this.jsonBodyReader = new JsonNormalizedNodeBodyReader(controllerContext); + } + + @Override + protected MediaType getMediaType() { + return new MediaType(MediaType.APPLICATION_XML, null); + } + + @BeforeClass + public static void initialization() throws Exception { + final Collection<File> testFiles = TestRestconfUtils.loadFiles("/instanceidentifier/yang"); + testFiles.addAll(TestRestconfUtils.loadFiles("/invoke-rpc")); + schemaContext = YangParserTestUtils.parseYangFiles(testFiles); + } + + @Test + public void rpcModuleInputTest() throws Exception { + final String uri = "invoke-rpc-module:rpc-test"; + mockBodyReader(uri, this.jsonBodyReader, true); + final InputStream inputStream = TestJsonBodyWriter.class + .getResourceAsStream("/invoke-rpc/json/rpc-output.json"); + final NormalizedNodeContext returnValue = this.jsonBodyReader.readFrom(null, + null, null, this.mediaType, null, inputStream); + final OutputStream output = new ByteArrayOutputStream(); + this.jsonBodyWriter.writeTo(returnValue, null, null, null, this.mediaType, null, + output); + assertTrue(output.toString().contains("lf-test")); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonPatchBodyReader.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonPatchBodyReader.java new file mode 100644 index 0000000..f2a6c41 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonPatchBodyReader.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2015 Cisco 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.rest.impl.test.providers; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.io.InputStream; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.netconf.sal.rest.impl.JsonToPatchBodyReader; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.restconf.common.patch.PatchContext; +import org.opendaylight.yangtools.yang.common.ErrorTag; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; + +public class TestJsonPatchBodyReader extends AbstractBodyReaderTest { + private static EffectiveModelContext schemaContext; + + private final JsonToPatchBodyReader jsonToPatchBodyReader; + + public TestJsonPatchBodyReader() { + super(schemaContext, null); + jsonToPatchBodyReader = new JsonToPatchBodyReader(controllerContext); + } + + @BeforeClass + public static void initialization() { + schemaContext = schemaContextLoader("/instanceidentifier/yang", schemaContext); + } + + @Override + protected MediaType getMediaType() { + return new MediaType(APPLICATION_JSON, null); + } + + @Test + public void modulePatchDataTest() throws Exception { + final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class.getResourceAsStream( + "/instanceidentifier/json/jsonPATCHdata.json"); + + final PatchContext returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContext(returnValue); + } + + /** + * Test of successful Patch consisting of create and delete Patch operations. + */ + @Test + public void modulePatchCreateAndDeleteTest() throws Exception { + final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class.getResourceAsStream( + "/instanceidentifier/json/jsonPATCHdataCreateAndDelete.json"); + + final PatchContext returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContext(returnValue); + } + + /** + * Test trying to use Patch create operation which requires value without value. Test should fail with + * {@link RestconfDocumentedException} with error code 400. + */ + @Test + public void modulePatchValueMissingNegativeTest() throws Exception { + final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class.getResourceAsStream( + "/instanceidentifier/json/jsonPATCHdataValueMissing.json"); + + final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class, + () -> jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream)); + assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag()); + } + + /** + * Test trying to use value with Patch delete operation which does not support value. Test should fail with + * {@link RestconfDocumentedException} with error code 400. + */ + @Test + public void modulePatchValueNotSupportedNegativeTest() throws Exception { + final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class.getResourceAsStream( + "/instanceidentifier/json/jsonPATCHdataValueNotSupported.json"); + + final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class, + () -> jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream)); + assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag()); + } + + /** + * Test using Patch when target is completely specified in request URI and thus target leaf contains only '/' sign. + */ + @Test + public void modulePatchCompleteTargetInURITest() throws Exception { + final String uri = "instance-identifier-patch-module:patch-cont"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class.getResourceAsStream( + "/instanceidentifier/json/jsonPATCHdataCompleteTargetInURI.json"); + + final PatchContext returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContext(returnValue); + } + + /** + * Test of Yang Patch merge operation on list. Test consists of two edit operations - replace and merge. + */ + @Test + public void modulePatchMergeOperationOnListTest() throws Exception { + final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class + .getResourceAsStream("/instanceidentifier/json/jsonPATCHMergeOperationOnList.json"); + + final PatchContext returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContext(returnValue); + } + + /** + * Test of Yang Patch merge operation on container. Test consists of two edit operations - create and merge. + */ + @Test + public void modulePatchMergeOperationOnContainerTest() throws Exception { + final String uri = "instance-identifier-patch-module:patch-cont"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class.getResourceAsStream( + "/instanceidentifier/json/jsonPATCHMergeOperationOnContainer.json"); + + final PatchContext returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContext(returnValue); + } + + /** + * Test reading simple leaf value. + */ + @Test + public void modulePatchSimpleLeafValueTest() throws Exception { + final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class + .getResourceAsStream("/instanceidentifier/json/jsonPATCHSimpleLeafValue.json"); + + final PatchContext returnValue = jsonToPatchBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContext(returnValue); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonPatchBodyReaderMountPoint.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonPatchBodyReaderMountPoint.java new file mode 100644 index 0000000..4ad1a65 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonPatchBodyReaderMountPoint.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2016 Cisco 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.rest.impl.test.providers; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; + +import java.io.InputStream; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.netconf.sal.rest.impl.JsonToPatchBodyReader; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.restconf.common.patch.PatchContext; +import org.opendaylight.yangtools.yang.common.ErrorTag; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; + +public class TestJsonPatchBodyReaderMountPoint extends AbstractBodyReaderTest { + private final JsonToPatchBodyReader jsonToPatchBodyReader; + private static EffectiveModelContext schemaContext; + private static final String MOUNT_POINT = "instance-identifier-module:cont/yang-ext:mount"; + + public TestJsonPatchBodyReaderMountPoint() { + super(schemaContext, mock(DOMMountPoint.class)); + jsonToPatchBodyReader = new JsonToPatchBodyReader(controllerContext); + } + + @Override + protected MediaType getMediaType() { + return new MediaType(APPLICATION_JSON, null); + } + + @BeforeClass + public static void initialization() { + schemaContext = schemaContextLoader("/instanceidentifier/yang", schemaContext); + } + + @Test + public void modulePatchDataTest() throws Exception { + final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class + .getResourceAsStream("/instanceidentifier/json/jsonPATCHdata.json"); + + final PatchContext returnValue = jsonToPatchBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContextMountPoint(returnValue); + } + + /** + * Test of successful Patch consisting of create and delete Patch operations. + */ + @Test + public void modulePatchCreateAndDeleteTest() throws Exception { + final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class + .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataCreateAndDelete.json"); + + final PatchContext returnValue = jsonToPatchBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContextMountPoint(returnValue); + } + + /** + * Test trying to use Patch create operation which requires value without value. Test should fail with + * {@link RestconfDocumentedException} with error code 400. + */ + @Test + public void modulePatchValueMissingNegativeTest() throws Exception { + final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class.getResourceAsStream( + "/instanceidentifier/json/jsonPATCHdataValueMissing.json"); + + final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class, + () -> jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream)); + assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag()); + } + + /** + * Test trying to use value with Patch delete operation which does not support value. Test should fail with + * {@link RestconfDocumentedException} with error code 400. + */ + @Test + public void modulePatchValueNotSupportedNegativeTest() throws Exception { + final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class + .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataValueNotSupported.json"); + + final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class, + () -> jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream)); + assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag()); + } + + /** + * Test using Patch when target is completely specified in request URI and thus target leaf contains only '/' sign. + */ + @Test + public void modulePatchCompleteTargetInURITest() throws Exception { + final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class + .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataCompleteTargetInURI.json"); + + final PatchContext returnValue = jsonToPatchBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContextMountPoint(returnValue); + } + + /** + * Test of Yang Patch merge operation on list. Test consists of two edit operations - replace and merge. + */ + @Test + public void modulePatchMergeOperationOnListTest() throws Exception { + final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class + .getResourceAsStream("/instanceidentifier/json/jsonPATCHMergeOperationOnList.json"); + + final PatchContext returnValue = jsonToPatchBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContextMountPoint(returnValue); + } + + /** + * Test of Yang Patch merge operation on container. Test consists of two edit operations - create and merge. + */ + @Test + public void modulePatchMergeOperationOnContainerTest() throws Exception { + final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class + .getResourceAsStream("/instanceidentifier/json/jsonPATCHMergeOperationOnContainer.json"); + + final PatchContext returnValue = jsonToPatchBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContextMountPoint(returnValue); + } + + /** + * Test reading simple leaf value. + */ + @Test + public void modulePatchSimpleLeafValueTest() throws Exception { + final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, jsonToPatchBodyReader, false); + + final InputStream inputStream = TestJsonBodyReader.class + .getResourceAsStream("/instanceidentifier/json/jsonPATCHSimpleLeafValue.json"); + + final PatchContext returnValue = jsonToPatchBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContext(returnValue); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlBodyReader.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlBodyReader.java new file mode 100644 index 0000000..7ff353e --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlBodyReader.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2015 Cisco 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.rest.impl.test.providers; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.InputStream; +import java.util.Collection; +import java.util.Optional; +import java.util.Set; +import javax.ws.rs.core.MediaType; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.rest.impl.XmlNormalizedNodeBodyReader; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.restconf.common.errors.RestconfError; +import org.opendaylight.yangtools.yang.common.ErrorTag; +import org.opendaylight.yangtools.yang.common.ErrorType; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.common.XMLNamespace; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; + +public class TestXmlBodyReader extends AbstractBodyReaderTest { + + private final XmlNormalizedNodeBodyReader xmlBodyReader; + private static EffectiveModelContext schemaContext; + private static final QNameModule INSTANCE_IDENTIFIER_MODULE_QNAME = QNameModule.create( + XMLNamespace.of("instance:identifier:module"), Revision.of("2014-01-17")); + + public TestXmlBodyReader() { + super(schemaContext, null); + xmlBodyReader = new XmlNormalizedNodeBodyReader(controllerContext); + } + + @Override + protected MediaType getMediaType() { + return new MediaType(MediaType.APPLICATION_XML, null); + } + + @BeforeClass + public static void initialization() throws Exception { + final Collection<File> testFiles = TestRestconfUtils.loadFiles("/instanceidentifier/yang"); + testFiles.addAll(TestRestconfUtils.loadFiles("/invoke-rpc")); + testFiles.addAll(TestRestconfUtils.loadFiles("/foo-xml-test/yang")); + schemaContext = YangParserTestUtils.parseYangFiles(testFiles); + } + + @Test + public void putXmlTest() throws Exception { + runXmlTest(false, "foo:top-level-list/key-value"); + } + + @Test + public void postXmlTest() throws Exception { + runXmlTest(true, ""); + } + + private void runXmlTest(final boolean isPost, final String path) throws Exception { + mockBodyReader(path, xmlBodyReader, isPost); + final InputStream inputStream = TestXmlBodyReader.class.getResourceAsStream("/foo-xml-test/foo.xml"); + final NormalizedNodeContext nnc = xmlBodyReader.readFrom(null, null, null, mediaType, null, inputStream); + assertNotNull(nnc); + + assertTrue(nnc.getData() instanceof MapEntryNode); + final MapEntryNode data = (MapEntryNode) nnc.getData(); + assertEquals(2, data.size()); + for (final DataContainerChild child : data.body()) { + switch (child.getIdentifier().getNodeType().getLocalName()) { + case "key-leaf": + assertEquals("key-value", child.body()); + break; + + case "ordinary-leaf": + assertEquals("leaf-value", child.body()); + break; + default: + fail(); + } + } + } + + @Test + public void moduleDataTest() throws Exception { + final DataSchemaNode dataSchemaNode = + schemaContext.getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final YangInstanceIdentifier dataII = YangInstanceIdentifier.of(dataSchemaNode.getQName()); + final String uri = "instance-identifier-module:cont"; + mockBodyReader(uri, xmlBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/xml/xmldata.xml"); + final NormalizedNodeContext returnValue = xmlBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNode, returnValue, dataII); + } + + @Test + public void moduleSubContainerDataPutTest() throws Exception { + final DataSchemaNode dataSchemaNode = + schemaContext.getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final QName cont1QName = QName.create(dataSchemaNode.getQName(), "cont1"); + final YangInstanceIdentifier dataII = YangInstanceIdentifier.of(dataSchemaNode.getQName()).node(cont1QName); + final DataSchemaNode dataSchemaNodeOnPath = ((DataNodeContainer) dataSchemaNode).getDataChildByName(cont1QName); + final String uri = "instance-identifier-module:cont/cont1"; + mockBodyReader(uri, xmlBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/xml/xml_sub_container.xml"); + final NormalizedNodeContext returnValue = xmlBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNodeOnPath, returnValue, dataII); + } + + @Test + public void moduleSubContainerDataPostTest() throws Exception { + final DataSchemaNode dataSchemaNode = + schemaContext.getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final QName cont1QName = QName.create(dataSchemaNode.getQName(), "cont1"); + final YangInstanceIdentifier dataII = YangInstanceIdentifier.of(dataSchemaNode.getQName()).node(cont1QName); + final String uri = "instance-identifier-module:cont"; + mockBodyReader(uri, xmlBodyReader, true); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/xml/xml_sub_container.xml"); + final NormalizedNodeContext returnValue = xmlBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNode, returnValue, dataII); + } + + @Test + public void moduleSubContainerAugmentDataPostTest() throws Exception { + final DataSchemaNode dataSchemaNode = + schemaContext.getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final Module augmentModule = schemaContext.findModules(XMLNamespace.of("augment:module")).iterator().next(); + final QName contAugmentQName = QName.create(augmentModule.getQNameModule(), "cont-augment"); + final AugmentationIdentifier augII = new AugmentationIdentifier(Set.of(contAugmentQName)); + final YangInstanceIdentifier dataII = YangInstanceIdentifier.of(dataSchemaNode.getQName()) + .node(augII).node(contAugmentQName); + final String uri = "instance-identifier-module:cont"; + mockBodyReader(uri, xmlBodyReader, true); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/xml/xml_augment_container.xml"); + final NormalizedNodeContext returnValue = xmlBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNode, returnValue, dataII); + } + + @Test + public void moduleSubContainerChoiceAugmentDataPostTest() throws Exception { + final DataSchemaNode dataSchemaNode = + schemaContext.getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final Module augmentModule = schemaContext.findModules(XMLNamespace.of("augment:module")).iterator().next(); + final QName augmentChoice1QName = QName.create(augmentModule.getQNameModule(), "augment-choice1"); + final QName augmentChoice2QName = QName.create(augmentChoice1QName, "augment-choice2"); + final YangInstanceIdentifier dataII = YangInstanceIdentifier.of(dataSchemaNode.getQName()) + .node(new AugmentationIdentifier(Set.of(augmentChoice1QName))) + .node(augmentChoice1QName) + // FIXME: DataSchemaTreeNode intepretation seems to have a bug + //.node(new AugmentationIdentifier(Set.of(augmentChoice2QName))) + .node(augmentChoice2QName) + .node(QName.create(augmentChoice1QName, "case-choice-case-container1")); + final String uri = "instance-identifier-module:cont"; + mockBodyReader(uri, xmlBodyReader, true); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/xml/xml_augment_choice_container.xml"); + final NormalizedNodeContext returnValue = xmlBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNode, returnValue, dataII); + } + + @Test + public void rpcModuleInputTest() throws Exception { + final String uri = "invoke-rpc-module:rpc-test"; + mockBodyReader(uri, xmlBodyReader, true); + final InputStream inputStream = TestXmlBodyReader.class.getResourceAsStream("/invoke-rpc/xml/rpc-input.xml"); + final NormalizedNodeContext returnValue = xmlBodyReader.readFrom(null, null, null, mediaType, null, + inputStream); + checkNormalizedNodeContextRpc(returnValue); + final ContainerNode contNode = (ContainerNode) returnValue.getData(); + final Optional<DataContainerChild> contDataNodePotential = contNode.findChildByArg(new NodeIdentifier( + QName.create(contNode.getIdentifier().getNodeType(), "cont"))); + assertTrue(contDataNodePotential.isPresent()); + final ContainerNode contDataNode = (ContainerNode) contDataNodePotential.get(); + final Optional<DataContainerChild> leafDataNode = contDataNode.findChildByArg(new NodeIdentifier( + QName.create(contDataNode.getIdentifier().getNodeType(), "lf"))); + assertTrue(leafDataNode.isPresent()); + assertTrue("lf-test".equalsIgnoreCase(leafDataNode.get().body().toString())); + } + + private static void checkExpectValueNormalizeNodeContext(final DataSchemaNode dataSchemaNode, + final NormalizedNodeContext nnContext, final YangInstanceIdentifier dataNodeIdent) { + assertEquals(dataSchemaNode, nnContext.getInstanceIdentifierContext().getSchemaNode()); + assertEquals(dataNodeIdent, nnContext.getInstanceIdentifierContext().getInstanceIdentifier()); + assertNotNull(NormalizedNodes.findNode(nnContext.getData(), dataNodeIdent)); + } + + /** + * Test when container with the same name is placed in two modules (foo-module and bar-module). Namespace must be + * used to distinguish between them to find correct one. Check if container was found not only according to its name + * but also by correct namespace used in payload. + */ + @Test + public void findFooContainerUsingNamespaceTest() throws Exception { + mockBodyReader("", xmlBodyReader, true); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/xml/xmlDataFindFooContainer.xml"); + final NormalizedNodeContext returnValue = xmlBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + + // check return value + checkNormalizedNodeContext(returnValue); + // check if container was found both according to its name and namespace + assertEquals("Not correct container found, name was ignored", + "foo-bar-container", returnValue.getData().getIdentifier().getNodeType().getLocalName()); + assertEquals("Not correct container found, namespace was ignored", + "foo:module", returnValue.getData().getIdentifier().getNodeType().getNamespace().toString()); + } + + /** + * Test when container with the same name is placed in two modules (foo-module and bar-module). Namespace must be + * used to distinguish between them to find correct one. Check if container was found not only according to its name + * but also by correct namespace used in payload. + */ + @Test + public void findBarContainerUsingNamespaceTest() throws Exception { + mockBodyReader("", xmlBodyReader, true); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/xml/xmlDataFindBarContainer.xml"); + final NormalizedNodeContext returnValue = xmlBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + + // check return value + checkNormalizedNodeContext(returnValue); + // check if container was found both according to its name and namespace + assertEquals("Not correct container found, name was ignored", + "foo-bar-container", returnValue.getData().getIdentifier().getNodeType().getLocalName()); + assertEquals("Not correct container found, namespace was ignored", + "bar:module", returnValue.getData().getIdentifier().getNodeType().getNamespace().toString()); + } + + /** + * Test PUT operation when message root element is not the same as the last element in request URI. + * PUT operation message should always start with schema node from URI otherwise exception should be + * thrown. + */ + @Test + public void wrongRootElementTest() throws Exception { + mockBodyReader("instance-identifier-module:cont", xmlBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class.getResourceAsStream( + "/instanceidentifier/xml/bug7933.xml"); + try { + xmlBodyReader.readFrom(null, null, null, mediaType, null, inputStream); + Assert.fail("Test should fail due to malformed PUT operation message"); + } catch (final RestconfDocumentedException exception) { + final RestconfError restconfError = exception.getErrors().get(0); + assertEquals(ErrorType.PROTOCOL, restconfError.getErrorType()); + assertEquals(ErrorTag.MALFORMED_MESSAGE, restconfError.getErrorTag()); + } + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlBodyReaderMountPoint.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlBodyReaderMountPoint.java new file mode 100644 index 0000000..fe257db --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlBodyReaderMountPoint.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2015 Cisco 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.rest.impl.test.providers; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import java.io.File; +import java.io.InputStream; +import java.util.Collection; +import java.util.Optional; +import javax.ws.rs.core.MediaType; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.rest.impl.XmlNormalizedNodeBodyReader; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.restconf.common.errors.RestconfError; +import org.opendaylight.yangtools.yang.common.ErrorTag; +import org.opendaylight.yangtools.yang.common.ErrorType; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.common.XMLNamespace; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; + +public class TestXmlBodyReaderMountPoint extends AbstractBodyReaderTest { + private final XmlNormalizedNodeBodyReader xmlBodyReader; + private static EffectiveModelContext schemaContext; + + private static final QNameModule INSTANCE_IDENTIFIER_MODULE_QNAME = QNameModule.create( + XMLNamespace.of("instance:identifier:module"), Revision.of("2014-01-17")); + + public TestXmlBodyReaderMountPoint() { + super(schemaContext, mock(DOMMountPoint.class)); + xmlBodyReader = new XmlNormalizedNodeBodyReader(controllerContext); + } + + @Override + protected MediaType getMediaType() { + return new MediaType(MediaType.APPLICATION_XML, null); + } + + @BeforeClass + public static void initialization() throws Exception { + final Collection<File> testFiles = TestRestconfUtils.loadFiles("/instanceidentifier/yang"); + testFiles.addAll(TestRestconfUtils.loadFiles("/invoke-rpc")); + schemaContext = YangParserTestUtils.parseYangFiles(testFiles); + } + + @Test + public void moduleDataTest() throws Exception { + final DataSchemaNode dataSchemaNode = schemaContext + .getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final String uri = "instance-identifier-module:cont/yang-ext:mount/instance-identifier-module:cont"; + mockBodyReader(uri, xmlBodyReader, false); + final InputStream inputStream = TestXmlBodyReaderMountPoint.class + .getResourceAsStream("/instanceidentifier/xml/xmldata.xml"); + final NormalizedNodeContext returnValue = xmlBodyReader.readFrom(null, + null, null, mediaType, null, inputStream); + checkMountPointNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNode, returnValue); + } + + @Test + public void moduleSubContainerDataPutTest() throws Exception { + final DataSchemaNode dataSchemaNode = schemaContext + .getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final String uri = "instance-identifier-module:cont/yang-ext:mount/instance-identifier-module:cont/cont1"; + mockBodyReader(uri, xmlBodyReader, false); + final InputStream inputStream = TestXmlBodyReaderMountPoint.class + .getResourceAsStream("/instanceidentifier/xml/xml_sub_container.xml"); + final NormalizedNodeContext returnValue = xmlBodyReader.readFrom(null, + null, null, mediaType, null, inputStream); + checkMountPointNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNode, returnValue, + QName.create(dataSchemaNode.getQName(), "cont1")); + } + + @Test + public void moduleSubContainerDataPostTest() throws Exception { + final DataSchemaNode dataSchemaNode = schemaContext + .getDataChildByName(QName.create(INSTANCE_IDENTIFIER_MODULE_QNAME, "cont")); + final String uri = "instance-identifier-module:cont/yang-ext:mount/instance-identifier-module:cont"; + mockBodyReader(uri, xmlBodyReader, true); + final InputStream inputStream = TestXmlBodyReaderMountPoint.class + .getResourceAsStream("/instanceidentifier/xml/xml_sub_container.xml"); + final NormalizedNodeContext returnValue = xmlBodyReader.readFrom(null, + null, null, mediaType, null, inputStream); + checkMountPointNormalizedNodeContext(returnValue); + checkExpectValueNormalizeNodeContext(dataSchemaNode, returnValue); + } + + @Test + public void rpcModuleInputTest() throws Exception { + final String uri = "instance-identifier-module:cont/yang-ext:mount/invoke-rpc-module:rpc-test"; + mockBodyReader(uri, xmlBodyReader, true); + final InputStream inputStream = TestXmlBodyReaderMountPoint.class + .getResourceAsStream("/invoke-rpc/xml/rpc-input.xml"); + final NormalizedNodeContext returnValue = xmlBodyReader.readFrom(null, + null, null, mediaType, null, inputStream); + checkNormalizedNodeContextRpc(returnValue); + final ContainerNode contNode = (ContainerNode) returnValue.getData(); + final YangInstanceIdentifier yangCont = YangInstanceIdentifier.of( + QName.create(contNode.getIdentifier().getNodeType(), "cont")); + final Optional<DataContainerChild> contDataNodePotential = + contNode.findChildByArg(yangCont.getLastPathArgument()); + assertTrue(contDataNodePotential.isPresent()); + final ContainerNode contDataNode = (ContainerNode) contDataNodePotential.get(); + final YangInstanceIdentifier yangLeaf = YangInstanceIdentifier.of( + QName.create(contDataNode.getIdentifier().getNodeType(), "lf")); + final Optional<DataContainerChild> leafDataNode = contDataNode.findChildByArg(yangLeaf.getLastPathArgument()); + assertTrue(leafDataNode.isPresent()); + assertTrue("lf-test".equalsIgnoreCase(leafDataNode.get().body().toString())); + } + + private void checkExpectValueNormalizeNodeContext( + final DataSchemaNode dataSchemaNode, + final NormalizedNodeContext nnContext) { + checkExpectValueNormalizeNodeContext(dataSchemaNode, nnContext, null); + } + + protected void checkExpectValueNormalizeNodeContext( + final DataSchemaNode dataSchemaNode, final NormalizedNodeContext nnContext, final QName qualifiedName) { + YangInstanceIdentifier dataNodeIdent = YangInstanceIdentifier.of(dataSchemaNode.getQName()); + final DOMMountPoint mountPoint = nnContext.getInstanceIdentifierContext().getMountPoint(); + final DataSchemaNode mountDataSchemaNode = + modelContext(mountPoint).getDataChildByName(dataSchemaNode.getQName()); + assertNotNull(mountDataSchemaNode); + if (qualifiedName != null && dataSchemaNode instanceof DataNodeContainer) { + final DataSchemaNode child = ((DataNodeContainer) dataSchemaNode).getDataChildByName(qualifiedName); + dataNodeIdent = YangInstanceIdentifier.builder(dataNodeIdent).node(child.getQName()).build(); + assertTrue(nnContext.getInstanceIdentifierContext().getSchemaNode().equals(child)); + } else { + assertTrue(mountDataSchemaNode.equals(dataSchemaNode)); + } + assertNotNull(NormalizedNodes.findNode(nnContext.getData(), dataNodeIdent)); + } + + /** + * Test when container with the same name is placed in two modules (foo-module and bar-module). Namespace must be + * used to distinguish between them to find correct one. Check if container was found not only according to its name + * but also by correct namespace used in payload. + */ + @Test + public void findFooContainerUsingNamespaceTest() throws Exception { + mockBodyReader("instance-identifier-module:cont/yang-ext:mount", xmlBodyReader, true); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/xml/xmlDataFindFooContainer.xml"); + final NormalizedNodeContext returnValue = xmlBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + + // check return value + checkMountPointNormalizedNodeContext(returnValue); + // check if container was found both according to its name and namespace + assertEquals("Not correct container found, name was ignored", + "foo-bar-container", returnValue.getData().getIdentifier().getNodeType().getLocalName()); + assertEquals("Not correct container found, namespace was ignored", + "foo:module", returnValue.getData().getIdentifier().getNodeType().getNamespace().toString()); + } + + /** + * Test when container with the same name is placed in two modules (foo-module and bar-module). Namespace must be + * used to distinguish between them to find correct one. Check if container was found not only according to its name + * but also by correct namespace used in payload. + */ + @Test + public void findBarContainerUsingNamespaceTest() throws Exception { + mockBodyReader("instance-identifier-module:cont/yang-ext:mount", xmlBodyReader, true); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/xml/xmlDataFindBarContainer.xml"); + final NormalizedNodeContext returnValue = xmlBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + + // check return value + checkMountPointNormalizedNodeContext(returnValue); + // check if container was found both according to its name and namespace + assertEquals("Not correct container found, name was ignored", + "foo-bar-container", returnValue.getData().getIdentifier().getNodeType().getLocalName()); + assertEquals("Not correct container found, namespace was ignored", + "bar:module", returnValue.getData().getIdentifier().getNodeType().getNamespace().toString()); + } + + /** + * Test PUT operation when message root element is not the same as the last element in request URI. + * PUT operation message should always start with schema node from URI otherwise exception should be + * thrown. + */ + @Test + public void wrongRootElementTest() throws Exception { + mockBodyReader("instance-identifier-module:cont/yang-ext:mount", xmlBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class.getResourceAsStream( + "/instanceidentifier/xml/bug7933.xml"); + try { + xmlBodyReader.readFrom(null, null, null, mediaType, null, inputStream); + Assert.fail("Test should fail due to malformed PUT operation message"); + } catch (final RestconfDocumentedException exception) { + final RestconfError restconfError = exception.getErrors().get(0); + assertEquals(ErrorType.PROTOCOL, restconfError.getErrorType()); + assertEquals(ErrorTag.MALFORMED_MESSAGE, restconfError.getErrorTag()); + } + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlBodyWriter.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlBodyWriter.java new file mode 100644 index 0000000..169d806 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlBodyWriter.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2015 Cisco 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.rest.impl.test.providers; + +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.OutputStream; +import java.util.Collection; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeXmlBodyWriter; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; + +public class TestXmlBodyWriter extends AbstractBodyReaderTest { + + private final NormalizedNodeXmlBodyWriter xmlBodyWriter; + private static EffectiveModelContext schemaContext; + + public TestXmlBodyWriter() { + super(schemaContext, null); + this.xmlBodyWriter = new NormalizedNodeXmlBodyWriter(); + } + + @Override + protected MediaType getMediaType() { + return new MediaType(MediaType.APPLICATION_XML, null); + } + + @BeforeClass + public static void initialization() throws Exception { + final Collection<File> testFiles = TestRestconfUtils.loadFiles("/instanceidentifier/yang"); + testFiles.addAll(TestRestconfUtils.loadFiles("/invoke-rpc")); + schemaContext = YangParserTestUtils.parseYangFiles(testFiles); + } + + @Test + public void rpcModuleInputTest() throws Exception { + final String uri = "invoke-rpc-module:rpc-test"; + final String pathToInputFile = "/invoke-rpc/xml/rpc-output.xml"; + final NormalizedNodeContext nnContext = TestRestconfUtils + .loadNormalizedContextFromXmlFile(pathToInputFile, uri, controllerContext); + final OutputStream output = new ByteArrayOutputStream(); + this.xmlBodyWriter.writeTo(nnContext, null, null, null, this.mediaType, null, output); + assertTrue(output.toString().contains("lf-test")); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlPatchBodyReader.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlPatchBodyReader.java new file mode 100644 index 0000000..8b37ba4 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlPatchBodyReader.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2015 Cisco 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.rest.impl.test.providers; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.io.InputStream; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.netconf.sal.rest.impl.XmlToPatchBodyReader; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.restconf.common.patch.PatchContext; +import org.opendaylight.yangtools.yang.common.ErrorTag; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; + +public class TestXmlPatchBodyReader extends AbstractBodyReaderTest { + + private final XmlToPatchBodyReader xmlToPatchBodyReader; + private static EffectiveModelContext schemaContext; + + public TestXmlPatchBodyReader() { + super(schemaContext, null); + xmlToPatchBodyReader = new XmlToPatchBodyReader(controllerContext); + } + + @Override + protected MediaType getMediaType() { + return new MediaType(MediaType.APPLICATION_XML, null); + } + + @BeforeClass + public static void initialization() throws NoSuchFieldException, SecurityException { + schemaContext = schemaContextLoader("/instanceidentifier/yang", schemaContext); + } + + @Test + public void moduleDataTest() throws Exception { + final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, xmlToPatchBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class.getResourceAsStream( + "/instanceidentifier/xml/xmlPATCHdata.xml"); + final PatchContext returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContext(returnValue); + } + + /** + * Test trying to use Patch create operation which requires value without value. Error code 400 should be returned. + */ + @Test + public void moduleDataValueMissingNegativeTest() throws Exception { + final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, xmlToPatchBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class.getResourceAsStream( + "/instanceidentifier/xml/xmlPATCHdataValueMissing.xml"); + final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class, + () -> xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream)); + assertEquals(1, ex.getErrors().size()); + assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag()); + } + + /** + * Test trying to use value with Patch delete operation which does not support value. Error code 400 should be + * returned. + */ + @Test + public void moduleDataNotValueNotSupportedNegativeTest() throws Exception { + final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, xmlToPatchBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class.getResourceAsStream( + "/instanceidentifier/xml/xmlPATCHdataValueNotSupported.xml"); + + final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class, + () -> xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream)); + assertEquals(1, ex.getErrors().size()); + assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag()); + } + + /** + * Test of Yang Patch with absolute target path. + */ + @Test + public void moduleDataAbsoluteTargetPathTest() throws Exception { + final String uri = ""; + mockBodyReader(uri, xmlToPatchBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataAbsoluteTargetPath.xml"); + final PatchContext returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContext(returnValue); + } + + /** + * Test using Patch when target is completely specified in request URI and thus target leaf contains only '/' sign. + */ + @Test + public void modulePatchCompleteTargetInURITest() throws Exception { + final String uri = "instance-identifier-patch-module:patch-cont"; + mockBodyReader(uri, xmlToPatchBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataCompleteTargetInURI.xml"); + final PatchContext returnValue = xmlToPatchBodyReader + .readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContext(returnValue); + } + + /** + * Test of Yang Patch merge operation on list. Test consists of two edit operations - replace and merge. + */ + @Test + public void moduleDataMergeOperationOnListTest() throws Exception { + final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, xmlToPatchBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataMergeOperationOnList.xml"); + final PatchContext returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContext(returnValue); + } + + /** + * Test of Yang Patch merge operation on container. Test consists of two edit operations - create and merge. + */ + @Test + public void moduleDataMergeOperationOnContainerTest() throws Exception { + final String uri = "instance-identifier-patch-module:patch-cont"; + mockBodyReader(uri, xmlToPatchBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataMergeOperationOnContainer.xml"); + final PatchContext returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContext(returnValue); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlPatchBodyReaderMountPoint.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlPatchBodyReaderMountPoint.java new file mode 100644 index 0000000..614f36e --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlPatchBodyReaderMountPoint.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2016 Cisco 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.rest.impl.test.providers; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; + +import java.io.InputStream; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.netconf.sal.rest.impl.XmlToPatchBodyReader; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.restconf.common.patch.PatchContext; +import org.opendaylight.yangtools.yang.common.ErrorTag; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; + +public class TestXmlPatchBodyReaderMountPoint extends AbstractBodyReaderTest { + + private final XmlToPatchBodyReader xmlToPatchBodyReader; + private static EffectiveModelContext schemaContext; + private static final String MOUNT_POINT = "instance-identifier-module:cont/yang-ext:mount"; + + public TestXmlPatchBodyReaderMountPoint() { + super(schemaContext, mock(DOMMountPoint.class)); + xmlToPatchBodyReader = new XmlToPatchBodyReader(controllerContext); + } + + @BeforeClass + public static void initialization() throws NoSuchFieldException, SecurityException { + schemaContext = schemaContextLoader("/instanceidentifier/yang", schemaContext); + } + + @Override + protected MediaType getMediaType() { + return new MediaType(MediaType.APPLICATION_XML, null); + } + + @Test + public void moduleDataTest() throws Exception { + final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, xmlToPatchBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class.getResourceAsStream( + "/instanceidentifier/xml/xmlPATCHdata.xml"); + checkPatchContextMountPoint(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream)); + } + + /** + * Test trying to use Patch create operation which requires value without value. Error code 400 should be returned. + */ + @Test + public void moduleDataValueMissingNegativeTest() throws Exception { + final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, xmlToPatchBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class.getResourceAsStream( + "/instanceidentifier/xml/xmlPATCHdataValueMissing.xml"); + final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class, + () -> xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream)); + assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag()); + } + + /** + * Test trying to use value with Patch delete operation which does not support value. Error code 400 should be + * returned. + */ + @Test + public void moduleDataNotValueNotSupportedNegativeTest() throws Exception { + final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, xmlToPatchBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class + .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataValueNotSupported.xml"); + final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class, + () -> xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream)); + assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag()); + } + + /** + * Test of Yang Patch with absolute target path. + */ + @Test + public void moduleDataAbsoluteTargetPathTest() throws Exception { + final String uri = MOUNT_POINT; + mockBodyReader(uri, xmlToPatchBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class.getResourceAsStream( + "/instanceidentifier/xml/xmlPATCHdataAbsoluteTargetPath.xml"); + final PatchContext returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContextMountPoint(returnValue); + } + + /** + * Test using Patch when target is completely specified in request URI and thus target leaf contains only '/' sign. + */ + @Test + public void modulePatchCompleteTargetInURITest() throws Exception { + final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont"; + mockBodyReader(uri, xmlToPatchBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class.getResourceAsStream( + "/instanceidentifier/xml/xmlPATCHdataCompleteTargetInURI.xml"); + final PatchContext returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream); + checkPatchContextMountPoint(returnValue); + } + + /** + * Test of Yang Patch merge operation on list. Test consists of two edit operations - replace and merge. + */ + @Test + public void moduleDataMergeOperationOnListTest() throws Exception { + final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1"; + mockBodyReader(uri, xmlToPatchBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class.getResourceAsStream( + "/instanceidentifier/xml/xmlPATCHdataMergeOperationOnList.xml"); + checkPatchContextMountPoint(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream)); + } + + /** + * Test of Yang Patch merge operation on container. Test consists of two edit operations - create and merge. + */ + @Test + public void moduleDataMergeOperationOnContainerTest() throws Exception { + final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont"; + mockBodyReader(uri, xmlToPatchBodyReader, false); + final InputStream inputStream = TestXmlBodyReader.class.getResourceAsStream( + "/instanceidentifier/xml/xmlPATCHdataMergeOperationOnContainer.xml"); + checkPatchContextMountPoint(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream)); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/json/test/CnSnToJsonBasicDataTypesTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/json/test/CnSnToJsonBasicDataTypesTest.java new file mode 100644 index 0000000..e263873 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/json/test/CnSnToJsonBasicDataTypesTest.java @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2014 Cisco 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.cnsn.to.json.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.fail; + +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; +import org.junit.BeforeClass; +import org.opendaylight.controller.sal.restconf.impl.test.YangAndXmlAndDataSchemaLoader; + +public class CnSnToJsonBasicDataTypesTest extends YangAndXmlAndDataSchemaLoader { + + abstract static class LeafVerifier { + + Object expectedValue; + JsonToken expectedToken; + + LeafVerifier(final Object expectedValue, final JsonToken expectedToken) { + this.expectedValue = expectedValue; + this.expectedToken = expectedToken; + } + + abstract Object getActualValue(JsonReader reader) throws IOException; + + void verify(final JsonReader reader, final String keyName) throws IOException { + assertEquals("Json value for key " + keyName, this.expectedValue, getActualValue(reader)); + } + + JsonToken expectedTokenType() { + return this.expectedToken; + } + } + + static class BooleanVerifier extends LeafVerifier { + + BooleanVerifier(final boolean expected) { + super(expected, JsonToken.BOOLEAN); + } + + @Override + Object getActualValue(final JsonReader reader) throws IOException { + return reader.nextBoolean(); + } + } + + static class NumberVerifier extends LeafVerifier { + + NumberVerifier(final Number expected) { + super(expected, JsonToken.NUMBER); + } + + @Override + Object getActualValue(final JsonReader reader) throws IOException { + if (this.expectedValue instanceof Double) { + return reader.nextDouble(); + } else if (this.expectedValue instanceof Long) { + return reader.nextLong(); + } else if (this.expectedValue instanceof Integer) { + return reader.nextInt(); + } + + return null; + } + } + + static class StringVerifier extends LeafVerifier { + + StringVerifier(final String expected) { + super(expected, JsonToken.STRING); + } + + @Override + Object getActualValue(final JsonReader reader) throws IOException { + return reader.nextString(); + } + } + + static class EmptyVerifier extends LeafVerifier { + + EmptyVerifier() { + super(null, null); + } + + @Override + Object getActualValue(final JsonReader reader) throws IOException { + reader.beginArray(); + reader.nextNull(); + reader.endArray(); + return null; + } + + } + + static class ComplexAnyXmlVerifier extends LeafVerifier { + + ComplexAnyXmlVerifier() { + super(null, JsonToken.BEGIN_OBJECT); + } + + @Override + void verify(final JsonReader reader, final String keyName) throws IOException { + + reader.beginObject(); + final String innerKey = reader.nextName(); + assertEquals("Json reader child key for " + keyName, "data", innerKey); + assertEquals("Json token type for key " + innerKey, JsonToken.BEGIN_OBJECT, reader.peek()); + + reader.beginObject(); + verifyLeaf(reader, innerKey, "leaf1", "leaf1-value"); + verifyLeaf(reader, innerKey, "leaf2", "leaf2-value"); + + String nextName = reader.nextName(); + assertEquals("Json reader child key for " + innerKey, "leaf-list", nextName); + reader.beginArray(); + assertEquals("Json value for key " + nextName, "leaf-list-value1", reader.nextString()); + assertEquals("Json value for key " + nextName, "leaf-list-value2", reader.nextString()); + reader.endArray(); + + nextName = reader.nextName(); + assertEquals("Json reader child key for " + innerKey, "list", nextName); + reader.beginArray(); + verifyNestedLists(reader, 1); + verifyNestedLists(reader, 3); + reader.endArray(); + + reader.endObject(); + reader.endObject(); + } + + void verifyNestedLists(final JsonReader reader, int leafNum) throws IOException { + reader.beginObject(); + + final String nextName = reader.nextName(); + assertEquals("Json reader next name", "nested-list", nextName); + + reader.beginArray(); + + reader.beginObject(); + verifyLeaf(reader, "nested-list", "nested-leaf", "nested-value" + leafNum++); + reader.endObject(); + + reader.beginObject(); + verifyLeaf(reader, "nested-list", "nested-leaf", "nested-value" + leafNum); + reader.endObject(); + + reader.endArray(); + reader.endObject(); + } + + void verifyLeaf(final JsonReader reader, final String parent, final String name, + final String value) throws IOException { + final String nextName = reader.nextName(); + assertEquals("Json reader child key for " + parent, name, nextName); + assertEquals("Json token type for key " + parent, JsonToken.STRING, reader.peek()); + assertEquals("Json value for key " + nextName, value, reader.nextString()); + } + + @Override + Object getActualValue(final JsonReader reader) throws IOException { + return null; + } + } + + @BeforeClass + public static void initialize() throws FileNotFoundException { + dataLoad("/cnsn-to-json/simple-data-types"); + } + + private static void verifyJsonOutput(final String jsonOutput) { + final StringReader strReader = new StringReader(jsonOutput); + final JsonReader jReader = new JsonReader(strReader); + + String exception = null; + try { + jsonReadCont(jReader); + } catch (final IOException e) { + exception = e.getMessage(); + } + + assertNull("Error during reading Json output: " + exception, exception); + } + + private static void jsonReadCont(final JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + assertNotNull("cont1 is missing.", jsonReader.hasNext()); + + // Cont dataFromJson = new Cont(jReader.nextName()); + jsonReader.nextName(); + jsonReadContElements(jsonReader); + + assertFalse("cont shouldn't have other element.", jsonReader.hasNext()); + jsonReader.endObject(); + // return dataFromJson; + } + + private static void jsonReadContElements(final JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + + final Map<String, LeafVerifier> expectedMap = new HashMap<>(); + expectedMap.put("lfnint8Min", new NumberVerifier(-128)); + expectedMap.put("lfnint8Max", new NumberVerifier(127)); + expectedMap.put("lfnint16Min", new NumberVerifier(-32768)); + expectedMap.put("lfnint16Max", new NumberVerifier(32767)); + expectedMap.put("lfnint32Min", new NumberVerifier(-2147483648)); + expectedMap.put("lfnint32Max", new NumberVerifier(2147483647L)); + expectedMap.put("lfnint64Min", new NumberVerifier(-9223372036854775808L)); + expectedMap.put("lfnint64Max", new NumberVerifier(9223372036854775807L)); + expectedMap.put("lfnuint8Max", new NumberVerifier(255)); + expectedMap.put("lfnuint16Max", new NumberVerifier(65535)); + expectedMap.put("lfnuint32Max", new NumberVerifier(4294967295L)); + expectedMap.put("lfstr", new StringVerifier("lfstr")); + expectedMap.put("lfstr1", new StringVerifier("")); + expectedMap.put("lfbool1", new BooleanVerifier(true)); + expectedMap.put("lfbool2", new BooleanVerifier(false)); + expectedMap.put("lfbool3", new BooleanVerifier(false)); + expectedMap.put("lfdecimal1", new NumberVerifier(43.32)); + expectedMap.put("lfdecimal2", new NumberVerifier(-0.43)); + expectedMap.put("lfdecimal3", new NumberVerifier(43d)); + expectedMap.put("lfdecimal4", new NumberVerifier(43E3)); + expectedMap.put("lfdecimal6", new NumberVerifier(33.12345)); + expectedMap.put("lfenum", new StringVerifier("enum3")); + expectedMap.put("lfbits", new StringVerifier("bit3 bit2")); + expectedMap.put("lfbinary", new StringVerifier("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")); + expectedMap.put("lfunion1", new StringVerifier("324")); + expectedMap.put("lfunion2", new StringVerifier("33.3")); + expectedMap.put("lfunion3", new StringVerifier("55")); + expectedMap.put("lfunion4", new StringVerifier("true")); + expectedMap.put("lfunion5", new StringVerifier("true")); + expectedMap.put("lfunion6", new StringVerifier("10")); + expectedMap.put("lfunion7", new StringVerifier("")); + expectedMap.put("lfunion8", new StringVerifier("")); + expectedMap.put("lfunion9", new StringVerifier("")); + expectedMap.put("lfunion10", new StringVerifier("bt1")); + expectedMap.put("lfunion11", new StringVerifier("33")); + expectedMap.put("lfunion12", new StringVerifier("false")); + expectedMap.put("lfunion13", new StringVerifier("b1")); + expectedMap.put("lfunion14", new StringVerifier("zero")); + expectedMap.put("lfempty", new EmptyVerifier()); + expectedMap.put("identityref1", new StringVerifier("simple-data-types:iden")); + expectedMap.put("complex-any", new ComplexAnyXmlVerifier()); + expectedMap.put("simple-any", new StringVerifier("simple")); + expectedMap.put("empty-any", new StringVerifier("")); + + while (jsonReader.hasNext()) { + final String keyName = jsonReader.nextName(); + final JsonToken peek = jsonReader.peek(); + + final LeafVerifier verifier = expectedMap.remove(keyName); + assertNotNull("Found unexpected leaf: " + keyName, verifier); + + final JsonToken expToken = verifier.expectedTokenType(); + if (expToken != null) { + assertEquals("Json token type for key " + keyName, expToken, peek); + } + + verifier.verify(jsonReader, keyName); + } + + if (!expectedMap.isEmpty()) { + fail("Missing leaf nodes in Json output: " + expectedMap.keySet()); + } + + jsonReader.endObject(); + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/json/test/CnSnToJsonIdentityrefTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/json/test/CnSnToJsonIdentityrefTest.java new file mode 100644 index 0000000..bf262d7 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/json/test/CnSnToJsonIdentityrefTest.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014 Cisco 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.cnsn.to.json.test; + +import java.io.FileNotFoundException; +import org.junit.BeforeClass; +import org.opendaylight.controller.sal.restconf.impl.test.YangAndXmlAndDataSchemaLoader; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class CnSnToJsonIdentityrefTest extends YangAndXmlAndDataSchemaLoader { + + @BeforeClass + public static void initialization() throws FileNotFoundException, ReactorException { + dataLoad("/cnsn-to-json/identityref", 2, "identityref-module", "cont"); + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/json/test/CnSnToJsonWithDataFromSeveralModulesTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/json/test/CnSnToJsonWithDataFromSeveralModulesTest.java new file mode 100644 index 0000000..1b44d75 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/cnsn/to/json/test/CnSnToJsonWithDataFromSeveralModulesTest.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2014 Cisco 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.cnsn.to.json.test; + +import java.io.FileNotFoundException; +import org.junit.BeforeClass; +import org.opendaylight.controller.sal.restconf.impl.test.YangAndXmlAndDataSchemaLoader; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class CnSnToJsonWithDataFromSeveralModulesTest extends YangAndXmlAndDataSchemaLoader { + + @BeforeClass + public static void initialize() throws FileNotFoundException, ReactorException { + dataLoad("/xml-to-cnsn/data-of-several-modules/yang", 2, "module1", "cont_m1"); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/input/to/cnsn/test/RestPutListDataTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/input/to/cnsn/test/RestPutListDataTest.java new file mode 100644 index 0000000..71406ca --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/input/to/cnsn/test/RestPutListDataTest.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2014 Cisco 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.input.to.cnsn.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +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.when; + +import com.google.common.util.concurrent.FluentFuture; +import java.io.FileNotFoundException; +import java.util.List; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.controller.sal.restconf.impl.test.TestUtils; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.PutResult; +import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.restconf.common.errors.RestconfError; +import org.opendaylight.yangtools.yang.common.ErrorTag; +import org.opendaylight.yangtools.yang.common.ErrorType; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.api.schema.builder.NormalizedNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.valid.DataValidationException; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack; + +public class RestPutListDataTest { + private static EffectiveModelContext schemaContextTestModule; + + private static BrokerFacade brokerFacade; + private static RestconfImpl restconfImpl; + + private static final String TEST_MODULE_NS_STRING = "test:module"; + private static final String TEST_MODULE_REVISION = "2014-01-09"; + + @BeforeClass + public static void staticSetup() throws FileNotFoundException { + schemaContextTestModule = TestUtils.loadSchemaContext("/full-versions/test-module"); + } + + @Before + public void initialize() throws FileNotFoundException { + final ControllerContext controllerContext = TestRestconfUtils.newControllerContext(schemaContextTestModule); + brokerFacade = mock(BrokerFacade.class); + restconfImpl = RestconfImpl.newInstance(brokerFacade, controllerContext); + final PutResult result = mock(PutResult.class); + when(brokerFacade.commitConfigurationDataPut(any(EffectiveModelContext.class), + any(YangInstanceIdentifier.class), any(NormalizedNode.class), Mockito.anyString(), Mockito.anyString())) + .thenReturn(result); + when(result.getFutureOfPutData()).thenReturn(mock(FluentFuture.class)); + when(result.getStatus()).thenReturn(Status.OK); + } + + /** + * Tests whether no exception is raised if number and values of keys in URI + * and payload are equal. + */ + @Test + @Ignore + public void testValidKeys() { + putListDataTest("key1value", "15", "key1value", (short) 15); + } + + /** + * Tests whether an exception is raised if key values in URI and payload are + * different. + * + * <p> + * The exception should be raised from validation method + * {@code RestconfImpl#validateListEqualityOfListInDataAndUri} + */ + @Test + @Ignore // RestconfDocumentedExceptionMapper needs update + public void testUriAndPayloadKeysDifferent() { + try { + putListDataTest("key1value", "15", "key1value", (short) 16); + fail("RestconfDocumentedException expected"); + } catch (final RestconfDocumentedException e) { + verifyException(e, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE); + } + + try { + putListDataTest("key1value", "15", "key1value1", (short) 16); + fail("RestconfDocumentedException expected"); + } catch (final RestconfDocumentedException e) { + verifyException(e, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE); + } + } + + /** + * Tests whether an exception is raised if URI contains less key values then + * payload. + * + * <p> + * The exception is raised during {@code InstanceIdentifier} instance is + * built from URI + */ + @Test + @Ignore + public void testMissingKeysInUri() { + try { + putListDataTest("key1value", null, "key1value", (short) 15); + fail("RestconfDocumentedException expected"); + } catch (final RestconfDocumentedException e) { + verifyException(e, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING); + } + } + + /** + * Tests whether an exception is raised if URI contains more key values then + * payload. + * + * <p> + * The exception should be raised from validation method + * {@code RestconfImpl#validateListEqualityOfListInDataAndUri} + */ + @Test + public void testMissingKeysInPayload() { + try { + putListDataTest("key1value", "15", "key1value", null); + fail("RestconfDocumentedException expected"); + } catch (final DataValidationException e) { + // FIXME: thing about different approach for testing the Exception states + // RestconfDocumentedException is not rise in new API because you get + // DataValidationException from putListDataTest before you call the real rest service +// verifyException(e, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING); + } + } + + private static void verifyException(final RestconfDocumentedException restDocumentedException, + final ErrorType errorType, final ErrorTag errorTag) { + final List<RestconfError> errors = restDocumentedException.getErrors(); + assertEquals("getErrors() size", 1, errors.size()); + assertEquals("RestconfError getErrorType()", errorType, errors.get(0).getErrorType()); + assertEquals("RestconfError getErrorTag()", errorTag, errors.get(0).getErrorTag()); + } + + public void putListDataTest(final String uriKey1, final String uriKey2, final String payloadKey1, + final Short payloadKey2) { + final QName lstWithCompositeKey = + QName.create(TEST_MODULE_NS_STRING, TEST_MODULE_REVISION, "lst-with-composite-key"); + final QName key1 = QName.create(TEST_MODULE_NS_STRING, TEST_MODULE_REVISION, "key1"); + final QName key2 = QName.create(TEST_MODULE_NS_STRING, TEST_MODULE_REVISION, "key2"); + + final DataSchemaNode testNodeSchemaNode = schemaContextTestModule.getDataChildByName(lstWithCompositeKey); + assertTrue(testNodeSchemaNode != null); + assertTrue(testNodeSchemaNode instanceof ListSchemaNode); + final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> testNodeContainer = + SchemaAwareBuilders.mapEntryBuilder((ListSchemaNode) testNodeSchemaNode); + + var testChildren = ControllerContext.findInstanceDataChildrenByName( + (ListSchemaNode) testNodeSchemaNode, key1.getLocalName()); + assertTrue(testChildren != null); + final DataSchemaNode testLeafKey1SchemaNode = testChildren.get(0).child; + assertTrue(testLeafKey1SchemaNode != null); + assertTrue(testLeafKey1SchemaNode instanceof LeafSchemaNode); + final NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>> leafKey1 = + SchemaAwareBuilders.leafBuilder((LeafSchemaNode) testLeafKey1SchemaNode); + leafKey1.withValue(payloadKey1); + testNodeContainer.withChild(leafKey1.build()); + + if (payloadKey2 != null) { + testChildren = ControllerContext.findInstanceDataChildrenByName( + (ListSchemaNode) testNodeSchemaNode, key2.getLocalName()); + assertTrue(testChildren != null); + final DataSchemaNode testLeafKey2SchemaNode = testChildren.get(0).child; + assertNotNull(testLeafKey2SchemaNode); + assertTrue(testLeafKey2SchemaNode instanceof LeafSchemaNode); + final NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>> leafKey2 = + SchemaAwareBuilders.leafBuilder((LeafSchemaNode) testLeafKey2SchemaNode); + leafKey2.withValue(payloadKey2); + testNodeContainer.withChild(leafKey2.build()); + } + + final NormalizedNodeContext testCompositeContext = new NormalizedNodeContext( + InstanceIdentifierContext.ofStack( + SchemaInferenceStack.ofDataTreePath(schemaContextTestModule, lstWithCompositeKey)), + testNodeContainer.build()); + + final UriInfo uriInfo = Mockito.mock(UriInfo.class); + restconfImpl.updateConfigurationData(toUri(uriKey1, uriKey2), testCompositeContext, uriInfo); + } + + public void putListDataWithWrapperTest(final String uriKey1, final String uriKey2, final String payloadKey1, + final Short payloadKey2) { + putListDataTest(uriKey1, uriKey2, payloadKey1, payloadKey2); + } + + private static String toUri(final String uriKey1, final String uriKey2) { + final StringBuilder uriBuilder = new StringBuilder("/test-module:lst-with-composite-key/"); + uriBuilder.append(uriKey1); + if (uriKey2 != null) { + uriBuilder.append("/"); + uriBuilder.append(uriKey2); + } + return uriBuilder.toString(); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/nn/test/JsonIdentityrefToNnTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/nn/test/JsonIdentityrefToNnTest.java new file mode 100644 index 0000000..f331c47 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/nn/test/JsonIdentityrefToNnTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2014 Cisco 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.json.to.nn.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.InputStream; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.sal.rest.impl.test.providers.AbstractBodyReaderTest; +import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; + +public class JsonIdentityrefToNnTest extends AbstractBodyReaderTest { + + private final JsonNormalizedNodeBodyReader jsonBodyReader; + private static EffectiveModelContext schemaContext; + + public JsonIdentityrefToNnTest() { + super(schemaContext, null); + this.jsonBodyReader = new JsonNormalizedNodeBodyReader(controllerContext); + } + + @BeforeClass + public static void initialize() { + schemaContext = schemaContextLoader("/json-to-nn/identityref", schemaContext); + } + + @Test + public void jsonIdentityrefToNn() throws Exception { + + final String uri = "identityref-module:cont"; + mockBodyReader(uri, this.jsonBodyReader, false); + final InputStream inputStream = this.getClass().getResourceAsStream( + "/json-to-nn/identityref/json/data.json"); + + final NormalizedNodeContext normalizedNodeContext = this.jsonBodyReader.readFrom( + null, null, null, this.mediaType, null, inputStream); + + assertEquals("cont", normalizedNodeContext.getData().getIdentifier().getNodeType().getLocalName()); + + final String dataTree = NormalizedNodes.toStringTree(normalizedNodeContext.getData()); + + assertTrue(dataTree.contains("cont1")); + assertTrue(dataTree + .contains("lf11 (identity:module?revision=2013-12-02)iden")); + assertTrue(dataTree + .contains("lf12 (identityref:module?revision=2013-12-02)iden_local")); + assertTrue(dataTree + .contains("lf13 (identityref:module?revision=2013-12-02)iden_local")); + assertTrue(dataTree + .contains("lf14 (identity:module?revision=2013-12-02)iden")); + } + + @Override + protected MediaType getMediaType() { + return null; + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/nn/test/JsonLeafrefToNnTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/nn/test/JsonLeafrefToNnTest.java new file mode 100644 index 0000000..19bba7b --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/nn/test/JsonLeafrefToNnTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2014 Cisco 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.json.to.nn.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.InputStream; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.sal.rest.impl.test.providers.AbstractBodyReaderTest; +import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; + +public class JsonLeafrefToNnTest extends AbstractBodyReaderTest { + + private final JsonNormalizedNodeBodyReader jsonBodyReader; + private static EffectiveModelContext schemaContext; + + public JsonLeafrefToNnTest() { + super(schemaContext, null); + this.jsonBodyReader = new JsonNormalizedNodeBodyReader(controllerContext); + } + + @BeforeClass + public static void initialize() { + schemaContext = schemaContextLoader("/json-to-nn/leafref", schemaContext); + } + + @Test + public void jsonIdentityrefToNormalizeNode() throws Exception { + + final String uri = "leafref-module:cont"; + mockBodyReader(uri, this.jsonBodyReader, false); + final InputStream inputStream = this.getClass().getResourceAsStream( + "/json-to-nn/leafref/json/data.json"); + + final NormalizedNodeContext normalizedNodeContext = this.jsonBodyReader.readFrom( + null, null, null, this.mediaType, null, inputStream); + + assertEquals("cont", normalizedNodeContext.getData().getIdentifier().getNodeType().getLocalName()); + final String dataTree = NormalizedNodes.toStringTree(normalizedNodeContext.getData()); + assertTrue(dataTree.contains("lf2 121")); + } + + @Override + protected MediaType getMediaType() { + return null; + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/nn/test/JsonToNnTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/nn/test/JsonToNnTest.java new file mode 100644 index 0000000..51d1e64 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/nn/test/JsonToNnTest.java @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2014 Cisco 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.json.to.nn.test; + +import static org.junit.Assert.assertEquals; +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 java.io.File; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.Collection; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.controller.sal.rest.impl.test.providers.AbstractBodyReaderTest; +import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JsonToNnTest extends AbstractBodyReaderTest { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractBodyReaderTest.class); + + private final JsonNormalizedNodeBodyReader jsonBodyReader; + private static EffectiveModelContext schemaContext; + + public JsonToNnTest() { + super(schemaContext, null); + this.jsonBodyReader = new JsonNormalizedNodeBodyReader(controllerContext); + } + + @BeforeClass + public static void initialize() throws FileNotFoundException { + final Collection<File> testFiles = TestRestconfUtils.loadFiles("/json-to-nn/simple-list-yang/1"); + testFiles.addAll(TestRestconfUtils.loadFiles("/json-to-nn/simple-list-yang/3")); + testFiles.addAll(TestRestconfUtils.loadFiles("/json-to-nn/simple-list-yang/4")); + testFiles.addAll(TestRestconfUtils.loadFiles("/json-to-nn/simple-container-yang")); + testFiles.addAll(TestRestconfUtils.loadFiles("/common/augment/yang")); + schemaContext = YangParserTestUtils.parseYangFiles(testFiles); + } + + @Test + public void simpleListTest() throws Exception { + simpleTest("/json-to-nn/simple-list.json", + "lst", "simple-list-yang1"); + } + + @Test + public void simpleContainerTest() throws Exception { + simpleTest("/json-to-nn/simple-container.json", + "cont", "simple-container-yang"); + } + + @Test + public void multipleItemsInLeafListTest() throws Exception { + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + "/json-to-nn/multiple-leaflist-items.json", + "simple-list-yang1:lst"); + assertNotNull(normalizedNodeContext); + + final String dataTree = NormalizedNodes.toStringTree(normalizedNodeContext + .getData()); + assertTrue(dataTree.contains("45")); + assertTrue(dataTree.contains("55")); + assertTrue(dataTree.contains("66")); + } + + @Test + public void multipleItemsInListTest() throws Exception { + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + "/json-to-nn/multiple-items-in-list.json", + "multiple-items-yang:lst"); + assertNotNull(normalizedNodeContext); + + assertEquals("lst", normalizedNodeContext.getData().getIdentifier().getNodeType().getLocalName()); + + verityMultipleItemsInList(normalizedNodeContext); + } + + @Test + public void nullArrayToSimpleNodeWithNullValueTest() throws Exception { + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + "/json-to-nn/array-with-null.json", "array-with-null-yang:cont"); + assertNotNull(normalizedNodeContext); + + assertEquals("cont", normalizedNodeContext.getData().getIdentifier().getNodeType().getLocalName()); + + final String dataTree = NormalizedNodes.toStringTree(normalizedNodeContext.getData()); + assertTrue(dataTree.contains("lf")); + assertTrue(dataTree.contains("empty")); + } + + @Test + public void incorrectTopLevelElementsTest() throws Exception { + mockBodyReader("simple-list-yang1:lst", this.jsonBodyReader, false); + + InputStream inputStream = this.getClass().getResourceAsStream( + "/json-to-nn/wrong-top-level1.json"); + + int countExceptions = 0; + RestconfDocumentedException exception = null; + + try { + this.jsonBodyReader.readFrom(null, null, null, this.mediaType, null, + inputStream); + } catch (final RestconfDocumentedException e) { + exception = e; + countExceptions++; + } + assertNotNull(exception); + assertEquals( + "Error parsing input: Schema node with name wrong was not found under " + + "(urn:ietf:params:xml:ns:netconf:base:1.0)data.", + exception.getErrors().get(0).getErrorMessage()); + + inputStream = this.getClass().getResourceAsStream( + "/json-to-nn/wrong-top-level2.json"); + exception = null; + try { + this.jsonBodyReader.readFrom(null, null, null, this.mediaType, null, + inputStream); + } catch (final RestconfDocumentedException e) { + exception = e; + countExceptions++; + } + assertNotNull(exception); + assertEquals( + "Error parsing input: Schema node with name lst1 was not found under " + + "(urn:ietf:params:xml:ns:netconf:base:1.0)data.", + exception.getErrors().get(0).getErrorMessage()); + + inputStream = this.getClass().getResourceAsStream( + "/json-to-nn/wrong-top-level3.json"); + exception = null; + try { + this.jsonBodyReader.readFrom(null, null, null, this.mediaType, null, + inputStream); + } catch (final RestconfDocumentedException e) { + exception = e; + countExceptions++; + } + assertNotNull(exception); + assertEquals( + "Error parsing input: Schema node with name lf was not found under " + + "(urn:ietf:params:xml:ns:netconf:base:1.0)data.", + exception.getErrors().get(0).getErrorMessage()); + assertEquals(3, countExceptions); + } + + @Test + public void emptyDataReadTest() throws Exception { + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + "/json-to-nn/empty-data.json", "array-with-null-yang:cont"); + assertNotNull(normalizedNodeContext); + + assertEquals("cont", normalizedNodeContext.getData().getIdentifier().getNodeType().getLocalName()); + + final String dataTree = NormalizedNodes.toStringTree(normalizedNodeContext.getData()); + + assertTrue(dataTree.contains("lflst1")); + + assertTrue(dataTree.contains("lflst2 45")); + + RestconfDocumentedException exception = null; + mockBodyReader("array-with-null-yang:cont", this.jsonBodyReader, false); + final InputStream inputStream = this.getClass().getResourceAsStream("/json-to-nn/empty-data.json1"); + + try { + this.jsonBodyReader.readFrom(null, null, null, this.mediaType, null,inputStream); + } catch (final RestconfDocumentedException e) { + exception = e; + } + assertNotNull(exception); + assertEquals("Error parsing input: null", exception.getErrors().get(0).getErrorMessage()); + } + + @Test + public void testJsonBlankInput() throws Exception { + final NormalizedNodeContext normalizedNodeContext = prepareNNC("", "array-with-null-yang:cont"); + assertNull(normalizedNodeContext); + } + + @Test + public void notSupplyNamespaceIfAlreadySupplied()throws Exception { + final String uri = "simple-list-yang1" + ":" + "lst"; + + final NormalizedNodeContext normalizedNodeContext = prepareNNC("/json-to-nn/simple-list.json", uri); + assertNotNull(normalizedNodeContext); + + verifyNormaluizedNodeContext(normalizedNodeContext, "lst"); + + mockBodyReader("simple-list-yang2:lst", this.jsonBodyReader, false); + final InputStream inputStream = this.getClass().getResourceAsStream("/json-to-nn/simple-list.json"); + + try { + this.jsonBodyReader.readFrom(null, null, null, this.mediaType, null, inputStream); + fail("NormalizedNodeContext should not be create because of different namespace"); + } catch (final RestconfDocumentedException e) { + LOG.warn("Read from InputStream failed. Message: {}. Status: {}", e.getMessage(), e.getStatus()); + } + + verifyNormaluizedNodeContext(normalizedNodeContext, "lst"); + } + + @Test + public void dataAugmentedTest() throws Exception { + NormalizedNodeContext normalizedNodeContext = prepareNNC("/common/augment/json/dataa.json", "main:cont"); + + assertNotNull(normalizedNodeContext); + assertEquals("cont", normalizedNodeContext.getData().getIdentifier().getNodeType().getLocalName()); + + String dataTree = NormalizedNodes.toStringTree(normalizedNodeContext + .getData()); + assertTrue(dataTree.contains("cont1")); + assertTrue(dataTree.contains("lf11 lf11 value from a")); + + normalizedNodeContext = prepareNNC("/common/augment/json/datab.json", "main:cont"); + + assertNotNull(normalizedNodeContext); + assertEquals("cont", normalizedNodeContext.getData().getIdentifier().getNodeType().getLocalName()); + dataTree = NormalizedNodes.toStringTree(normalizedNodeContext.getData()); + assertTrue(dataTree.contains("cont1")); + assertTrue(dataTree.contains("lf11 lf11 value from b")); + } + + private void simpleTest(final String jsonPath, final String topLevelElementName, + final String moduleName) throws Exception { + final String uri = moduleName + ":" + topLevelElementName; + + final NormalizedNodeContext normalizedNodeContext = prepareNNC(jsonPath, uri); + assertNotNull(normalizedNodeContext); + + verifyNormaluizedNodeContext(normalizedNodeContext, topLevelElementName); + } + + private NormalizedNodeContext prepareNNC(final String jsonPath, final String uri) throws Exception { + try { + mockBodyReader(uri, this.jsonBodyReader, false); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + LOG.warn("Operation failed due to: {}", e.getMessage()); + } + final InputStream inputStream = this.getClass().getResourceAsStream(jsonPath); + + NormalizedNodeContext normalizedNodeContext = null; + + try { + normalizedNodeContext = this.jsonBodyReader.readFrom(null, null, null, this.mediaType, null, inputStream); + } catch (WebApplicationException e) { + // TODO Auto-generated catch block + } + + return normalizedNodeContext; + } + + private static void verifyNormaluizedNodeContext(final NormalizedNodeContext normalizedNodeContext, + final String topLevelElementName) { + assertEquals(topLevelElementName, normalizedNodeContext.getData().getIdentifier().getNodeType().getLocalName()); + + final String dataTree = NormalizedNodes.toStringTree(normalizedNodeContext.getData()); + assertTrue(dataTree.contains("cont1")); + assertTrue(dataTree.contains("lst1")); + assertTrue(dataTree.contains("lflst1")); + assertTrue(dataTree.contains("lflst1_1")); + assertTrue(dataTree.contains("lflst1_2")); + assertTrue(dataTree.contains("lf1")); + } + + private static void verityMultipleItemsInList(final NormalizedNodeContext normalizedNodeContext) { + final String dataTree = NormalizedNodes.toStringTree(normalizedNodeContext.getData()); + assertTrue(dataTree.contains("lf11")); + assertTrue(dataTree.contains("lf11_1")); + assertTrue(dataTree.contains("lflst11")); + assertTrue(dataTree.contains("45")); + assertTrue(dataTree.contains("cont11")); + assertTrue(dataTree.contains("lst11")); + } + + @Test + public void unsupportedDataFormatTest() throws Exception { + mockBodyReader("simple-list-yang1:lst", this.jsonBodyReader, false); + + final InputStream inputStream = this.getClass().getResourceAsStream("/json-to-nn/unsupported-json-format.json"); + + RestconfDocumentedException exception = null; + + try { + this.jsonBodyReader.readFrom(null, null, null, this.mediaType, null, inputStream); + } catch (final RestconfDocumentedException e) { + exception = e; + } + LOG.info(exception.getErrors().get(0).getErrorMessage()); + + assertTrue(exception.getErrors().get(0).getErrorMessage().contains("is not a simple type")); + } + + @Test + public void invalidUriCharacterInValue() throws Exception { + mockBodyReader("array-with-null-yang:cont", this.jsonBodyReader, false); + + final InputStream inputStream = this.getClass().getResourceAsStream( + "/json-to-nn/invalid-uri-character-in-value.json"); + + final NormalizedNodeContext normalizedNodeContext = this.jsonBodyReader.readFrom( + null, null, null, this.mediaType, null, inputStream); + assertNotNull(normalizedNodeContext); + + assertEquals("cont", normalizedNodeContext.getData().getIdentifier().getNodeType().getLocalName()); + + final String dataTree = NormalizedNodes.toStringTree(normalizedNodeContext.getData()); + assertTrue(dataTree.contains("lf1 module<Name:value lf1")); + assertTrue(dataTree.contains("lf2 module>Name:value lf2")); + } + + @Override + protected MediaType getMediaType() { + return null; + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/json/test/NnJsonChoiceCaseTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/json/test/NnJsonChoiceCaseTest.java new file mode 100644 index 0000000..8fd5db8 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/json/test/NnJsonChoiceCaseTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2015 Cisco 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.nn.to.json.test; + +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.controller.sal.rest.impl.test.providers.AbstractBodyReaderTest; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeJsonBodyWriter; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; + +public class NnJsonChoiceCaseTest extends AbstractBodyReaderTest { + + private static EffectiveModelContext schemaContext; + private final NormalizedNodeJsonBodyWriter jsonBodyWriter; + + public NnJsonChoiceCaseTest() { + super(schemaContext, null); + jsonBodyWriter = new NormalizedNodeJsonBodyWriter(); + } + + @BeforeClass + public static void initialization() { + schemaContext = schemaContextLoader("/nn-to-json/choice", schemaContext); + } + + /** + * Test when some data are in one case node and other in another. This isn't + * correct. Next Json validator should return error because nodes has to be + * from one case below concrete choice. + */ + @Test(expected = NullPointerException.class) + public void nodeSchemasOnVariousChoiceCasePathTest() throws Exception { + getJson("/nn-to-json/choice/xml/data_various_path_err.xml"); + } + + /** + * Test when some data are in one case node and other in another. + * Additionally data are loadef from various choices. This isn't correct. + * Next Json validator should return error because nodes has to be from one + * case below concrete choice. + */ + @Test(expected = NullPointerException.class) + public void nodeSchemasOnVariousChoiceCasePathAndMultipleChoicesTest() + throws Exception { + getJson("/nn-to-json/choice/xml/data_more_choices_same_level_various_paths_err.xml"); + } + + /** + * Test when second level data are red first, then first and at the end + * third level. Level represents pass through couple choice-case + */ + + @Test + public void nodeSchemasWithRandomOrderAccordingLevel() throws Exception { + final String json = getJson("/nn-to-json/choice/xml/data_random_level.xml"); + + assertTrue(json.contains("cont")); + assertTrue(json.contains("\"lf1\":\"lf1 val\"")); + assertTrue(json.contains("\"lf1aaa\":\"lf1aaa val\"")); + assertTrue(json.contains("\"lf1aa\":\"lf1aa val\"")); + assertTrue(json.contains("\"lf1a\":121")); + } + + /** + * Test when element from no first case is used. + */ + @Test + public void nodeSchemasNotInFirstCase() throws Exception { + final String json = getJson("/nn-to-json/choice/xml/data_no_first_case.xml"); + + assertTrue(json.contains("cont")); + assertTrue(json.contains("\"lf1\":\"lf1 val\"")); + assertTrue(json.contains("\"lf1ab\":\"lf1ab val\"")); + assertTrue(json.contains("\"lf1a\":121")); + } + + /** + * Test when element in case is list. + */ + @Test + public void nodeSchemaAsList() throws Exception { + final String json = getJson("/nn-to-json/choice/xml/data_list.xml"); + + assertTrue(json.contains("cont")); + assertTrue(json.contains("\"lst1b\":[")); + assertTrue(json.contains("{\"lf11b\":\"lf11b_1 val\"}")); + assertTrue(json.contains("{\"lf11b\":\"lf11b_2 val\"}")); + } + + /** + * Test when element in case is container. + */ + @Test + public void nodeSchemaAsContainer() throws Exception { + final String json = getJson("/nn-to-json/choice/xml/data_container.xml"); + + assertTrue(json.contains("cont")); + assertTrue(json.contains("\"cont1c\":{")); + assertTrue(json.contains("\"lf11c\":\"lf11c val\"")); + } + + /** + * Test when element in case is leaflist. + */ + @Test + public void nodeSchemaAsLeafList() throws Exception { + final String json = getJson("/nn-to-json/choice/xml/data_leaflist.xml"); + + assertTrue(json.contains("cont")); + assertTrue(json.contains("\"lflst1d\":[")); + assertTrue(json.contains("\"lflst1d_1 val\"")); + assertTrue(json.contains("\"lflst1d_2 val\"")); + } + + @Test + public void nodeSchemasInMultipleChoicesTest() throws Exception { + final String json = getJson("/nn-to-json/choice/xml/data_more_choices_same_level.xml"); + + assertTrue(json.contains("cont")); + assertTrue(json.contains("\"lf2b\":\"lf2b value\"")); + assertTrue(json.contains("\"cont1c\":{")); + assertTrue(json.contains("\"lf11c\":\"lf11c val\"")); + } + + /** + * Test whether is possible to find data schema for node which is specified + * as dirrect subnode of choice (case without CASE key word). + */ + @Test + public void nodeSchemasInCaseNotDefinedWithCaseKeyword() throws Exception { + final String json = getJson("/nn-to-json/choice/xml/data_case_defined_without_case.xml"); + + assertTrue(json.contains("cont")); + assertTrue(json.contains("\"lf2b\":\"lf2b val\"")); + assertTrue(json.contains("\"e1\":45")); + } + + /** + * Test of multiple use of choices. + */ + @Test + public void nodeSchemasInThreeChoicesAtSameLevel() throws Exception { + final String json = getJson("/nn-to-json/choice/xml/data_three_choices_same_level.xml"); + + assertTrue(json.contains("cont")); + assertTrue(json.contains("lf2b\":\"lf2b value")); + assertTrue(json.contains("lst4a\":[{")); + assertTrue(json.contains("{\"lf4ab\":33}")); + assertTrue(json.contains("{\"lf4ab\":37}")); + assertTrue(json.contains("\"lf1aaa\":\"lf1aaa value\"")); + } + + private String getJson(final String xmlPath) throws Exception { + final String uri = "choice-case-test:cont"; + final NormalizedNodeContext testNN = TestRestconfUtils + .loadNormalizedContextFromXmlFile(xmlPath, uri, controllerContext); + + final OutputStream output = new ByteArrayOutputStream(); + jsonBodyWriter.writeTo(testNN, null, null, null, mediaType, null, + output); + + return output.toString(); + } + + @Override + protected MediaType getMediaType() { + return null; + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/json/test/NnToJsonLeafrefType.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/json/test/NnToJsonLeafrefType.java new file mode 100644 index 0000000..4ea6130 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/json/test/NnToJsonLeafrefType.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2014 Cisco 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.nn.to.json.test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.controller.sal.rest.impl.test.providers.AbstractBodyReaderTest; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeJsonBodyWriter; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; + +public class NnToJsonLeafrefType extends AbstractBodyReaderTest { + + private static EffectiveModelContext schemaContext; + private final NormalizedNodeJsonBodyWriter jsonBodyWriter; + + public NnToJsonLeafrefType() { + super(schemaContext, null); + jsonBodyWriter = new NormalizedNodeJsonBodyWriter(); + } + + @BeforeClass + public static void initialization() { + schemaContext = schemaContextLoader("/nn-to-json/leafref", schemaContext); + } + + @Test + public void leafrefAbsolutePathToExistingLeafTest() throws Exception { + final String json = toJson("/nn-to-json/leafref/xml/data_absolut_ref_to_existing_leaf.xml"); + validateJson(".*\"lf3\":\\p{Blank}*\"true\".*", json); + } + + @Test + public void leafrefRelativePathToExistingLeafTest() throws Exception { + final String json = toJson("/nn-to-json/leafref/xml/data_relativ_ref_to_existing_leaf.xml"); + validateJson(".*\"lf2\":\\p{Blank}*\"121\".*", json); + } + + @Test(expected = NullPointerException.class) + public void leafrefToNonExistingLeafTest() throws Exception { + toJson("/nn-to-json/leafref/xml/data_ref_to_non_existing_leaf.xml"); + } + + @Test + public void leafrefToNotLeafTest() throws Exception { + final String json = toJson("/nn-to-json/leafref/xml/data_ref_to_not_leaf.xml"); + validateJson( + ".*\"cont-augment-module\\p{Blank}*:\\p{Blank}*lf6\":\\p{Blank}*\"44\".*", + json); + } + + @Test + public void leafrefFromLeafListToLeafTest() throws Exception { + final String json = toJson("/nn-to-json/leafref/xml/data_relativ_ref_from_leaflist_to_existing_leaf.xml"); + validateJson(".*\"cont-augment-module\\p{Blank}*:\\p{Blank}*lflst1\":\\p{Blank}*.*\"34[5|6|7]\",*\"34[5|6|7]\"," + + "*\"34[5|6|7]\".*", json); + } + + @Test + public void leafrefFromLeafrefToLeafrefTest() throws Exception { + final String json = toJson("/nn-to-json/leafref/xml/data_from_leafref_to_leafref.xml"); + validateJson( + ".*\"cont-augment-module\\p{Blank}*:\\p{Blank}*lf7\":\\p{Blank}*\"200\".*", + json); + } + + private static void validateJson(final String regex, final String value) { + assertNotNull(value); + final Pattern ptrn = Pattern.compile(regex, Pattern.DOTALL); + final Matcher mtch = ptrn.matcher(value); + assertTrue(mtch.matches()); + } + + private String toJson(final String xmlDataPath) throws Exception { + final String uri = "main-module:cont"; + final String pathToInputFile = xmlDataPath; + + final NormalizedNodeContext testNN = TestRestconfUtils + .loadNormalizedContextFromXmlFile(pathToInputFile, uri, controllerContext); + + final OutputStream output = new ByteArrayOutputStream(); + jsonBodyWriter.writeTo(testNN, null, null, null, mediaType, null, + output); + final String jsonOutput = output.toString(); + + return jsonOutput; + } + + @Override + protected MediaType getMediaType() { + return new MediaType(MediaType.APPLICATION_XML, null); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/json/test/NnToJsonWithAugmentTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/json/test/NnToJsonWithAugmentTest.java new file mode 100644 index 0000000..d97b246 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/json/test/NnToJsonWithAugmentTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2014 Cisco 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.nn.to.json.test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.controller.sal.rest.impl.test.providers.AbstractBodyReaderTest; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeJsonBodyWriter; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; + +@Deprecated +public class NnToJsonWithAugmentTest extends AbstractBodyReaderTest { + + private static EffectiveModelContext schemaContext; + private final NormalizedNodeJsonBodyWriter xmlBodyWriter; + + public NnToJsonWithAugmentTest() { + super(schemaContext, null); + xmlBodyWriter = new NormalizedNodeJsonBodyWriter(); + } + + @BeforeClass + public static void initialize() { + schemaContext = schemaContextLoader("/nn-to-json/augmentation", schemaContext); + } + + @Test + public void augmentedElementsToJson() throws WebApplicationException, + IOException { + final String uri = "yang:cont"; + final String pathToInputFile = "/nn-to-json/augmentation/xml/data.xml"; + + final NormalizedNodeContext testNN = TestRestconfUtils + .loadNormalizedContextFromXmlFile(pathToInputFile, uri, controllerContext); + + final OutputStream output = new ByteArrayOutputStream(); + xmlBodyWriter + .writeTo(testNN, null, null, null, mediaType, null, output); + final String jsonOutput = output.toString(); + + assertNotNull(jsonOutput); + assertTrue(jsonOutput.contains("\"cont1\"" + ":" + '{')); + assertTrue(jsonOutput.contains("\"lf11\"" + ":" + "\"lf11\"")); + assertTrue(jsonOutput.contains("\"lst1\"" + ":" + '[')); + assertTrue(jsonOutput.contains("\"lf11\"" + ":" + "\"lf1_1\"")); + assertTrue(jsonOutput.contains("\"lf11\"" + ":" + "\"lf1_2\"")); + assertTrue(jsonOutput.contains("\"lflst1\"" + ":" + "[")); + assertTrue(jsonOutput.contains("\"lf2\"" + ":" + "\"lf2\"")); + } + + @Override + protected MediaType getMediaType() { + return new MediaType(MediaType.APPLICATION_XML, null); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/xml/test/NnInstanceIdentifierToXmlTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/xml/test/NnInstanceIdentifierToXmlTest.java new file mode 100644 index 0000000..2ab6476 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/xml/test/NnInstanceIdentifierToXmlTest.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2014 Cisco 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.nn.to.xml.test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.net.URISyntaxException; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.sal.rest.impl.test.providers.AbstractBodyReaderTest; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeXmlBodyWriter; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.SystemLeafSetNode; +import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode; +import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder; +import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.api.schema.builder.ListNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack; + +public class NnInstanceIdentifierToXmlTest extends AbstractBodyReaderTest { + private static EffectiveModelContext schemaContext; + + private final NormalizedNodeXmlBodyWriter xmlBodyWriter = new NormalizedNodeXmlBodyWriter(); + + public NnInstanceIdentifierToXmlTest() { + super(schemaContext, null); + } + + @BeforeClass + public static void initialization() throws URISyntaxException { + schemaContext = schemaContextLoader("/instanceidentifier/yang", schemaContext); + } + + @Test + public void nnAsYangInstanceIdentifierAugmentLeafList() throws Exception { + final NormalizedNodeContext normalizedNodeContext = prepareNNCLeafList(); + + final OutputStream output = new ByteArrayOutputStream(); + + xmlBodyWriter.writeTo(normalizedNodeContext, null, null, null, mediaType, null, output); + + assertNotNull(output); + + final String outputJson = output.toString(); + + assertTrue(outputJson.contains("<cont xmlns=")); + assertTrue(outputJson.contains( + '"' + "instance:identifier:module" + '"')); + assertTrue(outputJson.contains(">")); + + assertTrue(outputJson.contains("<cont1>")); + + assertTrue(outputJson.contains("<lf11 xmlns=")); + assertTrue(outputJson.contains( + '"' + "augment:module:leaf:list" + '"')); + assertTrue(outputJson.contains(">")); + assertTrue(outputJson.contains("/instanceidentifier/")); + assertTrue(outputJson.contains("</lf11>")); + + assertTrue(outputJson.contains("<lflst11 xmlns=")); + assertTrue(outputJson.contains( + '"' + "augment:module:leaf:list" + '"')); + assertTrue(outputJson.contains(">")); + assertTrue(outputJson.contains("lflst11 value")); + assertTrue(outputJson.contains("</lflst11>")); + + assertTrue(outputJson.contains("</cont1>")); + assertTrue(outputJson.contains("</cont>")); + } + + private static NormalizedNodeContext prepareNNCLeafList() throws URISyntaxException { + final QName cont = QName.create("instance:identifier:module", "2014-01-17", + "cont"); + final QName cont1 = QName.create("instance:identifier:module", "2014-01-17", + "cont1"); + final QName lflst11 = QName.create("augment:module:leaf:list", "2014-01-17", + "lflst11"); + final QName lf11 = QName.create("augment:module:leaf:list", "2014-01-17", + "lf11"); + + final DataSchemaNode schemaCont = schemaContext.getDataChildByName(cont); + + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> dataCont = SchemaAwareBuilders + .containerBuilder((ContainerSchemaNode) schemaCont); + + final DataSchemaNode schemaCont1 = ((ContainerSchemaNode) schemaCont).getDataChildByName(cont1); + + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> dataCont1 = SchemaAwareBuilders + .containerBuilder((ContainerSchemaNode) schemaCont1); + + final var instanceLfLst11 = ControllerContext.findInstanceDataChildrenByName( + (DataNodeContainer) schemaCont1, lflst11.getLocalName()); + + final DataSchemaNode lfLst11Schema = instanceLfLst11.get(0).child; + final ListNodeBuilder<Object, SystemLeafSetNode<Object>> lfLst11Data = SchemaAwareBuilders + .leafSetBuilder((LeafListSchemaNode) lfLst11Schema); + + lfLst11Data.withChild(SchemaAwareBuilders.leafSetEntryBuilder((LeafListSchemaNode) lfLst11Schema) + .withValue("lflst11 value").build()); + dataCont1.withChild(lfLst11Data.build()); + + final var instanceLf11 = ControllerContext.findInstanceDataChildrenByName( + (DataNodeContainer) schemaCont1, lf11.getLocalName()); + final DataSchemaNode lf11Schema = instanceLf11.get(0).child; + + dataCont1.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) lf11Schema) + .withValue("/instanceidentifier/").build()); + dataCont.withChild(dataCont1.build()); + + return new NormalizedNodeContext( + InstanceIdentifierContext.ofStack(SchemaInferenceStack.ofDataTreePath(schemaContext, cont)), + dataCont.build()); + } + + @Test + public void nnAsYangInstanceIdentifierAugment() throws Exception { + + final NormalizedNodeContext normalizedNodeContext = preparNNC(); + final OutputStream output = new ByteArrayOutputStream(); + + xmlBodyWriter.writeTo(normalizedNodeContext, null, null, null, + mediaType, null, output); + + assertNotNull(output); + + final String outputJson = output.toString(); + + assertTrue(outputJson.contains("<cont xmlns=")); + assertTrue(outputJson.contains( + '"' + "instance:identifier:module" + '"')); + assertTrue(outputJson.contains(">")); + + assertTrue(outputJson.contains("<cont1>")); + + assertTrue(outputJson.contains("<lst11 xmlns=")); + assertTrue(outputJson.contains('"' + "augment:module" + '"')); + assertTrue(outputJson.contains(">")); + + assertTrue(outputJson.contains( + "<keyvalue111>keyvalue111</keyvalue111>")); + assertTrue(outputJson.contains( + "<keyvalue112>keyvalue112</keyvalue112>")); + + assertTrue(outputJson.contains("<lf111 xmlns=")); + assertTrue(outputJson.contains( + '"' + "augment:augment:module" + '"')); + assertTrue(outputJson.contains(">/cont/cont1/lf12</lf111>")); + + assertTrue(outputJson.contains("<lf112 xmlns=")); + assertTrue(outputJson.contains( + '"' + "augment:augment:module" + '"')); + assertTrue(outputJson.contains(">lf12 value</lf112>")); + + assertTrue(outputJson.contains("</lst11></cont1></cont>")); + } + + private static NormalizedNodeContext preparNNC() { + final QName cont = QName.create("instance:identifier:module", "2014-01-17", + "cont"); + final QName cont1 = QName.create("instance:identifier:module", "2014-01-17", + "cont1"); + final QName lst11 = QName.create("augment:module", "2014-01-17", "lst11"); + final QName lf11 = QName.create("augment:augment:module", "2014-01-17", + "lf111"); + final QName lf12 = QName.create("augment:augment:module", "2014-01-17", + "lf112"); + final QName keyvalue111 = QName.create("augment:module", "2014-01-17", + "keyvalue111"); + final QName keyvalue112 = QName.create("augment:module", "2014-01-17", + "keyvalue112"); + + final DataSchemaNode schemaCont = schemaContext.getDataChildByName(cont); + + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> dataCont = SchemaAwareBuilders + .containerBuilder((ContainerSchemaNode) schemaCont); + + final DataSchemaNode schemaCont1 = ((ContainerSchemaNode) schemaCont) + .getDataChildByName(cont1); + + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> dataCont1 = SchemaAwareBuilders + .containerBuilder((ContainerSchemaNode) schemaCont1); + + final var instanceLst11 = ControllerContext.findInstanceDataChildrenByName( + (DataNodeContainer) schemaCont1, lst11.getLocalName()); + final DataSchemaNode lst11Schema = instanceLst11.get(0).child; + + final CollectionNodeBuilder<MapEntryNode, SystemMapNode> dataLst11 = SchemaAwareBuilders + .mapBuilder((ListSchemaNode) lst11Schema); + + final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> dataLst11Vaule = SchemaAwareBuilders + .mapEntryBuilder((ListSchemaNode) lst11Schema); + + dataLst11Vaule.withChild(buildLeaf(lst11Schema, keyvalue111, dataLst11, "keyvalue111")); + + dataLst11Vaule.withChild(buildLeaf(lst11Schema, keyvalue112, dataLst11, "keyvalue112")); + + dataLst11Vaule.withChild(buildLeaf(lst11Schema, lf11, dataLst11, "/cont/cont1/lf12")); + + dataLst11Vaule.withChild(buildLeaf(lst11Schema, lf12, dataLst11, "lf12 value")); + + dataLst11.withChild(dataLst11Vaule.build()); + + dataCont1.withChild(dataLst11.build()); + dataCont.withChild(dataCont1.build()); + + return new NormalizedNodeContext( + InstanceIdentifierContext.ofStack(SchemaInferenceStack.ofDataTreePath(schemaContext, cont)), + dataCont.build()); + } + + private static DataContainerChild buildLeaf(final DataSchemaNode lst11Schema, final QName qname, + final CollectionNodeBuilder<MapEntryNode, SystemMapNode> dataLst11, final Object value) { + + final var instanceLf = ControllerContext.findInstanceDataChildrenByName( + (DataNodeContainer) lst11Schema, qname.getLocalName()); + final DataSchemaNode schemaLf = instanceLf.get(0).child; + + return SchemaAwareBuilders.leafBuilder((LeafSchemaNode) schemaLf).withValue(value).build(); + } + + @Override + protected MediaType getMediaType() { + return null; + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/xml/test/NnToXmlTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/xml/test/NnToXmlTest.java new file mode 100644 index 0000000..b1385d9 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/xml/test/NnToXmlTest.java @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2014 Cisco 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.nn.to.xml.test; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import com.google.common.base.Throwables; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mockito; +import org.opendaylight.controller.sal.rest.impl.test.providers.AbstractBodyReaderTest; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeXmlBodyWriter; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.Uint32; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec; +import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition; +import org.opendaylight.yangtools.yang.model.ri.type.BaseTypes; +import org.opendaylight.yangtools.yang.model.ri.type.BitsTypeBuilder; +import org.opendaylight.yangtools.yang.model.ri.type.EnumerationTypeBuilder; +import org.opendaylight.yangtools.yang.model.ri.type.UnionTypeBuilder; +import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack; + +public class NnToXmlTest extends AbstractBodyReaderTest { + private static EffectiveModelContext schemaContext; + + private final NormalizedNodeXmlBodyWriter xmlBodyWriter; + + public NnToXmlTest() { + super(schemaContext, null); + xmlBodyWriter = new NormalizedNodeXmlBodyWriter(); + } + + @BeforeClass + public static void initialization() { + schemaContext = schemaContextLoader("/nn-to-xml/yang", schemaContext); + } + + @Test + public void nnAsYangIdentityrefToXMLTest() throws Exception { + final NormalizedNodeContext normalizedNodeContext = prepareIdrefData(null, true); + nnToXml(normalizedNodeContext, "<lf11 xmlns:x=\"referenced:module\">x:iden</lf11>"); + } + + @Test + public void nnAsYangIdentityrefWithQNamePrefixToXMLTest() throws Exception { + final NormalizedNodeContext normalizedNodeContext = prepareIdrefData("prefix", true); + nnToXml(normalizedNodeContext, "<lf11 xmlns", "=\"referenced:module\">", ":iden</lf11>"); + } + + @Test + public void nnAsYangIdentityrefWithPrefixToXMLTest() throws Exception { + final NormalizedNodeContext normalizedNodeContext = prepareIdrefData("prefix", false); + nnToXml(normalizedNodeContext, "<lf11>no qname value</lf11>"); + } + + @Test + public void nnAsYangLeafrefWithPrefixToXMLTest() throws Exception { + nnToXml(prepareLeafrefData(), "<lfBoolean>true</lfBoolean>", "<lfLfref>true</lfLfref>"); + } + + /** + * Negative test when leaf of type leafref references to not-existing leaf. + * {@code VerifyException} is expected. + */ + @Test + public void nnAsYangLeafrefWithPrefixToXMLNegativeTest() throws Exception { + final NormalizedNodeContext normalizedNodeContext = prepareLeafrefNegativeData(); + + final IOException ex = assertThrows(IOException.class, () -> nnToXml(normalizedNodeContext, + "<not-existing>value</not-existing>", "<lfLfrefNegative>value</lfLfrefnegative>")); + final Throwable rootCause = Throwables.getRootCause(ex); + assertThat(rootCause, instanceOf(IllegalArgumentException.class)); + assertEquals("Data tree child (basic:module?revision=2013-12-02)not-existing not present in schema parent " + + "(basic:module?revision=2013-12-02)cont", rootCause.getMessage()); + } + + @Test + public void nnAsYangStringToXmlTest() throws Exception { + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(BaseTypes.stringType()).deserialize("lfStr value"), "lfStr"); + nnToXml(normalizedNodeContext, "<lfStr>lfStr value</lfStr>"); + } + + @Test + public void nnAsYangInt8ToXmlTest() throws Exception { + final String elName = "lfInt8"; + + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(BaseTypes.int8Type()).deserialize("14"), elName); + nnToXml(normalizedNodeContext, "<" + elName + ">14</" + elName + ">"); + } + + @Test + public void nnAsYangInt16ToXmlTest() throws Exception { + final String elName = "lfInt16"; + + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(BaseTypes.int16Type()).deserialize("3000"), elName); + nnToXml(normalizedNodeContext, "<" + elName + ">3000</" + elName + ">"); + } + + @Test + public void nnAsYangInt32ToXmlTest() throws Exception { + final String elName = "lfInt32"; + + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(BaseTypes.int32Type()).deserialize("201234"), elName); + nnToXml(normalizedNodeContext, "<" + elName + ">201234</" + elName + ">"); + } + + @Test + public void nnAsYangInt64ToXmlTest() throws Exception { + final String elName = "lfInt64"; + + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(BaseTypes.int64Type()).deserialize("5123456789"), elName); + nnToXml(normalizedNodeContext, "<" + elName + ">5123456789</" + elName + ">"); + } + + @Test + public void nnAsYangUint8ToXmlTest() throws Exception { + final String elName = "lfUint8"; + + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(BaseTypes.uint8Type()).deserialize("200"), elName); + nnToXml(normalizedNodeContext, "<" + elName + ">200</" + elName + ">"); + } + + @Test + public void snAsYangUint16ToXmlTest() throws Exception { + final String elName = "lfUint16"; + + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(BaseTypes.uint16Type()).deserialize("4000"), elName); + nnToXml(normalizedNodeContext, "<" + elName + ">4000</" + elName + ">"); + } + + @Test + public void nnAsYangUint32ToXmlTest() throws Exception { + final String elName = "lfUint32"; + + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(BaseTypes.uint32Type()).deserialize("4123456789"), elName); + nnToXml(normalizedNodeContext, "<" + elName + ">4123456789</" + elName + ">"); + } + + @Test + public void snAsYangUint64ToXmlTest() throws Exception { + final String elName = "lfUint64"; + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(BaseTypes.uint64Type()).deserialize("5123456789"), elName); + nnToXml(normalizedNodeContext, "<" + elName + ">5123456789</" + elName + ">"); + } + + @Test + public void nnAsYangBinaryToXmlTest() throws Exception { + final String elName = "lfBinary"; + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(BaseTypes.binaryType()) + .deserialize("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567"), + elName); + nnToXml(normalizedNodeContext, + "<" + elName + ">ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567</" + elName + ">"); + } + + @Test + public void nnAsYangBitsToXmlTest() throws Exception { + final BitsTypeDefinition.Bit mockBit1 = Mockito.mock(BitsTypeDefinition.Bit.class); + Mockito.when(mockBit1.getName()).thenReturn("one"); + Mockito.when(mockBit1.getPosition()).thenReturn(Uint32.ONE); + final BitsTypeDefinition.Bit mockBit2 = Mockito.mock(BitsTypeDefinition.Bit.class); + Mockito.when(mockBit2.getName()).thenReturn("two"); + Mockito.when(mockBit2.getPosition()).thenReturn(Uint32.TWO); + final BitsTypeBuilder bitsTypeBuilder = BaseTypes.bitsTypeBuilder(QName.create("foo", "foo")); + bitsTypeBuilder.addBit(mockBit1); + bitsTypeBuilder.addBit(mockBit2); + + final String elName = "lfBits"; + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(bitsTypeBuilder.build()).deserialize("one two"), elName); + nnToXml(normalizedNodeContext, "<" + elName + ">one two</" + elName + ">"); + } + + @Test + public void nnAsYangEnumerationToXmlTest() throws Exception { + final EnumTypeDefinition.EnumPair mockEnum = Mockito.mock(EnumTypeDefinition.EnumPair.class); + Mockito.when(mockEnum.getName()).thenReturn("enum2"); + + final EnumerationTypeBuilder enumerationTypeBuilder = BaseTypes + .enumerationTypeBuilder(QName.create("foo", "foo")); + enumerationTypeBuilder.addEnum(mockEnum); + + final String elName = "lfEnumeration"; + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(enumerationTypeBuilder.build()).deserialize("enum2"), elName); + nnToXml(normalizedNodeContext, "<" + elName + ">enum2</" + elName + ">"); + } + + @Test + public void nnAsYangEmptyToXmlTest() throws Exception { + final String elName = "lfEmpty"; + final NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(BaseTypes.emptyType()).deserialize(""), elName); + nnToXml(normalizedNodeContext, "<" + elName + "/>"); + } + + @Test + public void nnAsYangBooleanToXmlTest() throws Exception { + final String elName = "lfBoolean"; + NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(BaseTypes.booleanType()).deserialize("false"), elName); + nnToXml(normalizedNodeContext, "<" + elName + ">false</" + elName + ">"); + + normalizedNodeContext = prepareNNC(TypeDefinitionAwareCodec.from(BaseTypes.booleanType()).deserialize("true"), + elName); + nnToXml(normalizedNodeContext, "<" + elName + ">true</" + elName + ">"); + } + + @Test + public void nnAsYangUnionToXmlTest() throws Exception { + final BitsTypeDefinition.Bit mockBit1 = Mockito.mock(BitsTypeDefinition.Bit.class); + Mockito.when(mockBit1.getName()).thenReturn("first"); + Mockito.when(mockBit1.getPosition()).thenReturn(Uint32.ONE); + final BitsTypeDefinition.Bit mockBit2 = Mockito.mock(BitsTypeDefinition.Bit.class); + Mockito.when(mockBit2.getName()).thenReturn("second"); + Mockito.when(mockBit2.getPosition()).thenReturn(Uint32.TWO); + + final BitsTypeBuilder bitsTypeBuilder = BaseTypes.bitsTypeBuilder(QName.create("foo", "foo")); + bitsTypeBuilder.addBit(mockBit1); + bitsTypeBuilder.addBit(mockBit2); + + final UnionTypeBuilder unionTypeBuilder = BaseTypes.unionTypeBuilder(QName.create("foo", "foo")); + unionTypeBuilder.addType(BaseTypes.int8Type()); + unionTypeBuilder.addType(bitsTypeBuilder.build()); + unionTypeBuilder.addType(BaseTypes.booleanType()); + unionTypeBuilder.addType(BaseTypes.stringType()); + + final String elName = "lfUnion"; + + // test int8 + final String int8 = "15"; + NormalizedNodeContext normalizedNodeContext = prepareNNC( + TypeDefinitionAwareCodec.from(unionTypeBuilder.build()).deserialize(int8), elName); + nnToXml(normalizedNodeContext, "<" + elName + ">15</" + elName + ">"); + + // test bits + final String bits = "first second"; + normalizedNodeContext = prepareNNC(TypeDefinitionAwareCodec.from(unionTypeBuilder.build()).deserialize(bits), + elName); + nnToXml(normalizedNodeContext, "<" + elName + ">[first, second]</" + elName + ">"); + + // test boolean + final String bool = "true"; + normalizedNodeContext = prepareNNC(TypeDefinitionAwareCodec.from(unionTypeBuilder.build()).deserialize(bool), + elName); + nnToXml(normalizedNodeContext, "<" + elName + ">true</" + elName + ">"); + + // test string + final String s = "Hi!"; + normalizedNodeContext = prepareNNC(TypeDefinitionAwareCodec.from(unionTypeBuilder.build()).deserialize(s), + elName); + nnToXml(normalizedNodeContext, "<" + elName + ">Hi!</" + elName + ">"); + } + + private static NormalizedNodeContext prepareNNC(final Object object, final String name) { + final QName cont = QName.create("basic:module", "2013-12-02", "cont"); + final QName lf = QName.create("basic:module", "2013-12-02", name); + + final DataSchemaNode contSchema = schemaContext.getDataChildByName(cont); + + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> contData = SchemaAwareBuilders + .containerBuilder((ContainerSchemaNode) contSchema); + + final var instanceLf = ControllerContext + .findInstanceDataChildrenByName((DataNodeContainer) contSchema, lf.getLocalName()); + final DataSchemaNode schemaLf = instanceLf.get(0).child; + + contData.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) schemaLf).withValue(object).build()); + + return new NormalizedNodeContext( + InstanceIdentifierContext.ofStack(SchemaInferenceStack.ofDataTreePath(schemaContext, cont)), + contData.build()); + } + + private void nnToXml(final NormalizedNodeContext normalizedNodeContext, final String... xmlRepresentation) + throws Exception { + final OutputStream output = new ByteArrayOutputStream(); + xmlBodyWriter.writeTo(normalizedNodeContext, null, null, null, mediaType, null, output); + + for (String element : xmlRepresentation) { + assertTrue(output.toString().contains(element)); + } + } + + private static NormalizedNodeContext prepareLeafrefData() { + final QName cont = QName.create("basic:module", "2013-12-02", "cont"); + final QName lfBoolean = QName.create("basic:module", "2013-12-02", "lfBoolean"); + final QName lfLfref = QName.create("basic:module", "2013-12-02", "lfLfref"); + + final DataSchemaNode contSchema = schemaContext.getDataChildByName(cont); + + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> contData = SchemaAwareBuilders + .containerBuilder((ContainerSchemaNode) contSchema); + + var instanceLf = ControllerContext + .findInstanceDataChildrenByName((DataNodeContainer) contSchema, lfBoolean.getLocalName()); + DataSchemaNode schemaLf = instanceLf.get(0).child; + + contData.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) schemaLf).withValue(Boolean.TRUE).build()); + + instanceLf = ControllerContext.findInstanceDataChildrenByName((DataNodeContainer) contSchema, + lfLfref.getLocalName()); + schemaLf = instanceLf.get(0).child; + + contData.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) schemaLf).withValue("true").build()); + + return new NormalizedNodeContext( + InstanceIdentifierContext.ofStack(SchemaInferenceStack.ofDataTreePath(schemaContext, cont)), + contData.build()); + } + + private static NormalizedNodeContext prepareLeafrefNegativeData() { + final QName cont = QName.create("basic:module", "2013-12-02", "cont"); + final QName lfLfref = QName.create("basic:module", "2013-12-02", "lfLfrefNegative"); + + final DataSchemaNode contSchema = schemaContext.getDataChildByName(cont); + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> contData = SchemaAwareBuilders + .containerBuilder((ContainerSchemaNode) contSchema); + + final var instanceLf = ControllerContext.findInstanceDataChildrenByName((DataNodeContainer) + contSchema, lfLfref.getLocalName()); + final DataSchemaNode schemaLf = instanceLf.get(0).child; + + contData.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) schemaLf).withValue("value").build()); + + return new NormalizedNodeContext( + InstanceIdentifierContext.ofStack(SchemaInferenceStack.ofDataTreePath(schemaContext, cont)), + contData.build()); + } + + private static NormalizedNodeContext prepareIdrefData(final String prefix, final boolean valueAsQName) { + final QName cont = QName.create("basic:module", "2013-12-02", "cont"); + final QName cont1 = QName.create("basic:module", "2013-12-02", "cont1"); + final QName lf11 = QName.create("basic:module", "2013-12-02", "lf11"); + + final DataSchemaNode contSchema = schemaContext.getDataChildByName(cont); + + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> contData = SchemaAwareBuilders + .containerBuilder((ContainerSchemaNode) contSchema); + + final DataSchemaNode cont1Schema = ((ContainerSchemaNode) contSchema).getDataChildByName(cont1); + + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> cont1Data = SchemaAwareBuilders + .containerBuilder((ContainerSchemaNode) cont1Schema); + + Object value = null; + if (valueAsQName) { + value = QName.create("referenced:module", "2013-12-02", "iden"); + } else { + value = "no qname value"; + } + + final var instanceLf = ControllerContext + .findInstanceDataChildrenByName((DataNodeContainer) cont1Schema, lf11.getLocalName()); + final DataSchemaNode schemaLf = instanceLf.get(0).child; + + cont1Data.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) schemaLf).withValue(value).build()); + + contData.withChild(cont1Data.build()); + + final NormalizedNodeContext testNormalizedNodeContext = new NormalizedNodeContext( + InstanceIdentifierContext.ofStack(SchemaInferenceStack.ofDataTreePath(schemaContext, cont)), + contData.build()); + return testNormalizedNodeContext; + } + + @Override + protected MediaType getMediaType() { + return null; + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/xml/test/NnToXmlWithChoiceTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/xml/test/NnToXmlWithChoiceTest.java new file mode 100644 index 0000000..9d2954d --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/xml/test/NnToXmlWithChoiceTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2014 Cisco 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.nn.to.xml.test; + +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.sal.rest.impl.test.providers.AbstractBodyReaderTest; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeXmlBodyWriter; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders; +import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack; + +public class NnToXmlWithChoiceTest extends AbstractBodyReaderTest { + + private final NormalizedNodeXmlBodyWriter xmlBodyWriter; + private static EffectiveModelContext schemaContext; + + public NnToXmlWithChoiceTest() { + super(schemaContext, null); + xmlBodyWriter = new NormalizedNodeXmlBodyWriter(); + } + + @BeforeClass + public static void initialization() { + schemaContext = schemaContextLoader("/nn-to-xml/choice", schemaContext); + } + + @Test + public void cnSnToXmlWithYangChoice() throws Exception { + NormalizedNodeContext normalizedNodeContext = prepareNNC("lf1", + "String data1"); + OutputStream output = new ByteArrayOutputStream(); + xmlBodyWriter.writeTo(normalizedNodeContext, null, null, null, + mediaType, null, output); + assertTrue(output.toString().contains("<lf1>String data1</lf1>")); + + normalizedNodeContext = prepareNNC("lf2", "String data2"); + output = new ByteArrayOutputStream(); + + xmlBodyWriter.writeTo(normalizedNodeContext, null, null, null, + mediaType, null, output); + assertTrue(output.toString().contains("<lf2>String data2</lf2>")); + } + + private static NormalizedNodeContext prepareNNC(final String name, final Object value) { + + final QName contQname = QName.create("module:with:choice", "2013-12-18", + "cont"); + final QName lf = QName.create("module:with:choice", "2013-12-18", name); + final QName choA = QName.create("module:with:choice", "2013-12-18", "choA"); + + final DataSchemaNode contSchemaNode = schemaContext + .getDataChildByName(contQname); + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> dataContainerNodeAttrBuilder = SchemaAwareBuilders + .containerBuilder((ContainerSchemaNode) contSchemaNode); + + final DataSchemaNode choiceSchemaNode = ((ContainerSchemaNode) contSchemaNode) + .getDataChildByName(choA); + assertTrue(choiceSchemaNode instanceof ChoiceSchemaNode); + + final DataContainerNodeBuilder<NodeIdentifier, ChoiceNode> dataChoice = SchemaAwareBuilders + .choiceBuilder((ChoiceSchemaNode) choiceSchemaNode); + + final var instanceLf = ControllerContext + .findInstanceDataChildrenByName( + (DataNodeContainer) contSchemaNode, lf.getLocalName()); + final DataSchemaNode schemaLf = instanceLf.get(0).child; + + dataChoice.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) schemaLf) + .withValue(value).build()); + + dataContainerNodeAttrBuilder.withChild(dataChoice.build()); + + final NormalizedNodeContext testNormalizedNodeContext = new NormalizedNodeContext( + InstanceIdentifierContext.ofStack(SchemaInferenceStack.ofDataTreePath(schemaContext, contQname)), + dataContainerNodeAttrBuilder.build()); + + return testNormalizedNodeContext; + } + + @Override + protected MediaType getMediaType() { + return null; + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/xml/test/NnToXmlWithDataFromSeveralModulesTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/xml/test/NnToXmlWithDataFromSeveralModulesTest.java new file mode 100644 index 0000000..a9cf7a6 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/nn/to/xml/test/NnToXmlWithDataFromSeveralModulesTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2014 Cisco 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.nn.to.xml.test; + +import static org.junit.Assert.assertTrue; + +import com.google.common.base.Preconditions; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URISyntaxException; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.sal.rest.impl.test.providers.AbstractBodyReaderTest; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeXmlBodyWriter; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; + +public class NnToXmlWithDataFromSeveralModulesTest extends + AbstractBodyReaderTest { + + private final NormalizedNodeXmlBodyWriter xmlBodyWriter; + private static EffectiveModelContext schemaContext; + + public NnToXmlWithDataFromSeveralModulesTest() { + super(schemaContext, null); + xmlBodyWriter = new NormalizedNodeXmlBodyWriter(); + } + + @BeforeClass + public static void initialize() { + schemaContext = schemaContextLoader("/nn-to-xml/data-of-several-modules/yang", schemaContext); + } + + @Test + public void dataFromSeveralModulesToXmlTest() + throws WebApplicationException, IOException, URISyntaxException { + final NormalizedNodeContext normalizedNodeContext = prepareNormalizedNodeContext(); + final OutputStream output = new ByteArrayOutputStream(); + xmlBodyWriter.writeTo(normalizedNodeContext, null, null, null, + mediaType, null, output); + + final String outputString = output.toString(); + // data + assertTrue(outputString + .contains( + "<data xmlns=" + '"' + + "urn:ietf:params:xml:ns:netconf:base:1.0" + + '"' + '>')); + // cont m2 + assertTrue(outputString.contains( + "<cont_m2 xmlns=" + '"' + "module:two" + '"' + '>')); + assertTrue(outputString.contains("<lf1_m2>lf1 m2 value</lf1_m2>")); + assertTrue(outputString.contains("<contB_m2/>")); + assertTrue(outputString.contains("</cont_m2>")); + + // cont m1 + assertTrue(outputString.contains( + "<cont_m1 xmlns=" + '"' + "module:one" + '"' + '>')); + assertTrue(outputString.contains("<contB_m1/>")); + assertTrue(outputString.contains("<lf1_m1>lf1 m1 value</lf1_m1>")); + assertTrue(outputString.contains("</cont_m1>")); + + // end + assertTrue(output.toString().contains("</data>")); + } + + @Override + protected MediaType getMediaType() { + // TODO Auto-generated method stub + return null; + } + + private static NormalizedNodeContext prepareNormalizedNodeContext() { + final String rev = "2014-01-17"; + + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> dataContSchemaContNode = SchemaAwareBuilders + .containerBuilder(schemaContext); + + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> modul1 = buildContBuilderMod1( + "module:one", rev, "cont_m1", "contB_m1", "lf1_m1", + "lf1 m1 value"); + dataContSchemaContNode.withChild(modul1.build()); + + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> modul2 = buildContBuilderMod1( + "module:two", rev, "cont_m2", "contB_m2", "lf1_m2", + "lf1 m2 value"); + dataContSchemaContNode.withChild(modul2.build()); + + final NormalizedNodeContext testNormalizedNodeContext = new NormalizedNodeContext( + InstanceIdentifierContext.ofLocalRoot(schemaContext), + dataContSchemaContNode.build()); + + return testNormalizedNodeContext; + } + + private static DataContainerNodeBuilder<NodeIdentifier, ContainerNode> buildContBuilderMod1( + final String uri, final String rev, final String cont, final String contB, final String lf1, + final String lf1Value) { + final QName contQname = QName.create(uri, rev, cont); + final QName contBQname = QName.create(uri, rev, contB); + final QName lf1Qname = QName.create(contQname, lf1); + + final DataSchemaNode contSchemaNode = schemaContext + .getDataChildByName(contQname); + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> dataContainerNodeAttrBuilder = SchemaAwareBuilders + .containerBuilder((ContainerSchemaNode) contSchemaNode); + + Preconditions.checkState(contSchemaNode instanceof ContainerSchemaNode); + final var instanceLf1_m1 = ControllerContext.findInstanceDataChildrenByName( + (DataNodeContainer) contSchemaNode, lf1Qname.getLocalName()); + final DataSchemaNode schemaLf1_m1 = instanceLf1_m1.get(0).child; + + dataContainerNodeAttrBuilder.withChild(SchemaAwareBuilders + .leafBuilder((LeafSchemaNode) schemaLf1_m1) + .withValue(lf1Value).build()); + + final DataSchemaNode contBSchemaNode = ((ContainerSchemaNode) contSchemaNode) + .getDataChildByName(contBQname); + + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> dataContainerB = SchemaAwareBuilders + .containerBuilder((ContainerSchemaNode) contBSchemaNode); + + return dataContainerNodeAttrBuilder.withChild(dataContainerB.build()); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/Bierman02RestConfWiringTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/Bierman02RestConfWiringTest.java new file mode 100644 index 0000000..990453f --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/Bierman02RestConfWiringTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019 Red Hat, 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 com.google.inject.AbstractModule; +import java.io.IOException; +import java.net.URISyntaxException; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.opendaylight.aaa.filterchain.configuration.CustomFilterAdapterConfiguration; +import org.opendaylight.aaa.web.WebServer; +import org.opendaylight.aaa.web.testutils.TestWebClient; +import org.opendaylight.aaa.web.testutils.WebTestModule; +import org.opendaylight.controller.sal.restconf.impl.test.incubate.InMemoryMdsalModule; +import org.opendaylight.infrautils.inject.guice.testutils.AnnotationsModule; +import org.opendaylight.infrautils.inject.guice.testutils.GuiceRule; +import org.opendaylight.netconf.sal.restconf.api.RestConfConfig; +import org.opendaylight.netconf.sal.restconf.impl.Bierman02RestConfWiring; + +/** + * Tests if the {@link Bierman02RestConfWiring} works. + * + * @author Michael Vorburger.ch + */ +public class Bierman02RestConfWiringTest { + + public static class TestModule extends AbstractModule { + @Override + protected void configure() { + bind(Bierman02RestConfWiring.class).asEagerSingleton(); + bind(RestConfConfig.class).toInstance(() -> 9090); + bind(CustomFilterAdapterConfiguration.class).toInstance(listener -> { }); + } + } + + public @Rule GuiceRule guice = new GuiceRule(TestModule.class, + InMemoryMdsalModule.class, WebTestModule.class, AnnotationsModule.class); + + @Inject WebServer webServer; + @Inject TestWebClient webClient; + + @Test + public void testWiring() throws IOException, InterruptedException, URISyntaxException { + assertEquals(200, webClient.request("GET", "/restconf/modules/").statusCode()); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java new file mode 100644 index 0000000..8443712 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2014, 2015 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.assertSame; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateBooleanFluentFuture; +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFailedFluentFuture; +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture; +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateTrueFluentFuture; + +import com.google.common.collect.ImmutableClassToInstanceMap; +import com.google.common.util.concurrent.FluentFuture; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.List; +import java.util.Optional; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.common.api.CommitInfo; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.common.api.ReadFailedException; +import org.opendaylight.mdsal.dom.api.DOMDataBroker; +import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService; +import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; +import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction; +import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; +import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.mdsal.dom.api.DOMNotificationService; +import org.opendaylight.mdsal.dom.api.DOMRpcResult; +import org.opendaylight.mdsal.dom.api.DOMRpcService; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.mdsal.dom.api.DOMTransactionChain; +import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.PutResult; +import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter; +import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter; +import org.opendaylight.netconf.sal.streams.listeners.Notificator; +import org.opendaylight.restconf.common.ErrorTags; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.restconf.common.errors.RestconfError; +import org.opendaylight.restconf.common.patch.PatchContext; +import org.opendaylight.restconf.common.patch.PatchStatusContext; +import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.CreateDataChangeEventSubscriptionInput1.Scope; +import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.common.ErrorTag; +import org.opendaylight.yangtools.yang.common.ErrorType; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute; +import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack; + +/** + * Unit tests for BrokerFacade. + * + * @author Thomas Pantelis + */ +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class BrokerFacadeTest { + + @Mock + private DOMDataBroker domDataBroker; + @Mock + private DOMNotificationService domNotification; + @Mock + private DOMRpcService mockRpcService; + @Mock + private DOMMountPoint mockMountInstance; + @Mock + private DOMDataTreeReadTransaction readTransaction; + @Mock + private DOMDataTreeWriteTransaction writeTransaction; + @Mock + private DOMDataTreeReadWriteTransaction rwTransaction; + + private BrokerFacade brokerFacade; + private final NormalizedNode dummyNode = createDummyNode("test:module", "2014-01-09", "interfaces"); + private final FluentFuture<Optional<NormalizedNode>> dummyNodeInFuture = wrapDummyNode(dummyNode); + private final QName qname = TestUtils.buildQName("interfaces","test:module", "2014-01-09"); + private final YangInstanceIdentifier instanceID = YangInstanceIdentifier.builder().node(qname).build(); + private ControllerContext controllerContext; + + @Before + public void setUp() throws Exception { + controllerContext = TestRestconfUtils.newControllerContext( + TestUtils.loadSchemaContext("/full-versions/test-module", "/modules")); + + brokerFacade = BrokerFacade.newInstance(mockRpcService, domDataBroker, domNotification, controllerContext); + + when(domDataBroker.newReadOnlyTransaction()).thenReturn(readTransaction); + when(domDataBroker.newReadWriteTransaction()).thenReturn(rwTransaction); + when(domDataBroker.getExtensions()).thenReturn(ImmutableClassToInstanceMap.of( + DOMDataTreeChangeService.class, Mockito.mock(DOMDataTreeChangeService.class))); + } + + private static FluentFuture<Optional<NormalizedNode>> wrapDummyNode(final NormalizedNode dummyNode) { + return immediateFluentFuture(Optional.of(dummyNode)); + } + + private static FluentFuture<Boolean> wrapExistence(final boolean exists) { + return immediateBooleanFluentFuture(exists); + } + + /** + * Value of this node shouldn't be important for testing purposes. + */ + private static NormalizedNode createDummyNode(final String namespace, final String date, final String localName) { + return Builders.containerBuilder() + .withNodeIdentifier(new NodeIdentifier(QName.create(namespace, date, localName))) + .build(); + } + + @Test + public void testReadConfigurationData() { + when(readTransaction.read(any(LogicalDatastoreType.class), any(YangInstanceIdentifier.class))).thenReturn( + dummyNodeInFuture); + + final NormalizedNode actualNode = brokerFacade.readConfigurationData(instanceID); + + assertSame("readConfigurationData", dummyNode, actualNode); + } + + @Test + public void testReadOperationalData() { + when(readTransaction.read(any(LogicalDatastoreType.class), any(YangInstanceIdentifier.class))).thenReturn( + dummyNodeInFuture); + + final NormalizedNode actualNode = brokerFacade.readOperationalData(instanceID); + + assertSame("readOperationalData", dummyNode, actualNode); + } + + @Test + public void test503() throws Exception { + final RpcError error = RpcResultBuilder.newError(ErrorType.TRANSPORT, ErrorTag.RESOURCE_DENIED, + "Master is down. Please try again."); + doReturn(immediateFailedFluentFuture(new ReadFailedException("Read from transaction failed", error))) + .when(readTransaction).read(any(LogicalDatastoreType.class), any(YangInstanceIdentifier.class)); + + final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class, + () -> brokerFacade.readConfigurationData(instanceID, "explicit")); + final List<RestconfError> errors = ex.getErrors(); + assertEquals(1, errors.size()); + assertEquals("getErrorTag", ErrorTags.RESOURCE_DENIED_TRANSPORT, errors.get(0).getErrorTag()); + assertEquals("getErrorType", ErrorType.TRANSPORT,errors.get(0).getErrorType()); + assertEquals("getErrorMessage", "Master is down. Please try again.", errors.get(0).getErrorMessage()); + } + + @Test + public void testInvokeRpc() throws Exception { + final DOMRpcResult expResult = mock(DOMRpcResult.class); + doReturn(immediateFluentFuture(expResult)).when(mockRpcService).invokeRpc(qname, dummyNode); + + final ListenableFuture<? extends DOMRpcResult> actualFuture = brokerFacade.invokeRpc(qname, + dummyNode); + assertNotNull("Future is null", actualFuture); + final DOMRpcResult actualResult = actualFuture.get(); + assertSame("invokeRpc", expResult, actualResult); + } + + @Test + public void testCommitConfigurationDataPut() throws Exception { + doReturn(CommitInfo.emptyFluentFuture()).when(rwTransaction).commit(); + + doReturn(immediateFluentFuture(Optional.of(mock(NormalizedNode.class)))).when(rwTransaction) + .read(LogicalDatastoreType.CONFIGURATION, instanceID); + + final PutResult result = brokerFacade.commitConfigurationDataPut(mock(EffectiveModelContext.class), + instanceID, dummyNode, null, null); + + assertSame("commitConfigurationDataPut", CommitInfo.emptyFluentFuture(), result.getFutureOfPutData()); + + final InOrder inOrder = inOrder(domDataBroker, rwTransaction); + inOrder.verify(domDataBroker).newReadWriteTransaction(); + inOrder.verify(rwTransaction).put(LogicalDatastoreType.CONFIGURATION, instanceID, dummyNode); + inOrder.verify(rwTransaction).commit(); + } + + @Test + public void testCommitConfigurationDataPost() { + when(rwTransaction.exists(LogicalDatastoreType.CONFIGURATION, instanceID)) + .thenReturn(wrapExistence(false)); + + doReturn(CommitInfo.emptyFluentFuture()).when(rwTransaction).commit(); + + final FluentFuture<? extends CommitInfo> actualFuture = brokerFacade + .commitConfigurationDataPost(mock(EffectiveModelContext.class), instanceID, dummyNode, null, + null); + + assertSame("commitConfigurationDataPost", CommitInfo.emptyFluentFuture(), actualFuture); + + final InOrder inOrder = inOrder(domDataBroker, rwTransaction); + inOrder.verify(domDataBroker).newReadWriteTransaction(); + inOrder.verify(rwTransaction).exists(LogicalDatastoreType.CONFIGURATION, instanceID); + inOrder.verify(rwTransaction).put(LogicalDatastoreType.CONFIGURATION, instanceID, dummyNode); + inOrder.verify(rwTransaction).commit(); + } + + @Test(expected = RestconfDocumentedException.class) + public void testCommitConfigurationDataPostAlreadyExists() { + when(rwTransaction.exists(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class))) + .thenReturn(immediateTrueFluentFuture()); + try { + // Schema context is only necessary for ensuring parent structure + brokerFacade.commitConfigurationDataPost((EffectiveModelContext) null, instanceID, dummyNode, + null, null); + } catch (final RestconfDocumentedException e) { + assertEquals("getErrorTag", ErrorTag.DATA_EXISTS, e.getErrors().get(0).getErrorTag()); + throw e; + } + } + + /** + * Positive test of delete operation when data to delete exits. Returned value and order of steps are validated. + */ + @Test + public void testCommitConfigurationDataDelete() throws Exception { + // assume that data to delete exists + prepareDataForDelete(true); + + // expected result + doReturn(CommitInfo.emptyFluentFuture()).when(rwTransaction).commit(); + + // test + final FluentFuture<? extends CommitInfo> actualFuture = brokerFacade + .commitConfigurationDataDelete(instanceID); + + // verify result and interactions + assertSame("commitConfigurationDataDelete", CommitInfo.emptyFluentFuture(), actualFuture); + + // check exists, delete, submit + final InOrder inOrder = inOrder(domDataBroker, rwTransaction); + inOrder.verify(rwTransaction).exists(LogicalDatastoreType.CONFIGURATION, instanceID); + inOrder.verify(rwTransaction).delete(LogicalDatastoreType.CONFIGURATION, instanceID); + inOrder.verify(rwTransaction).commit(); + } + + /** + * Negative test of delete operation when data to delete does not exist. Error DATA_MISSING should be returned. + */ + @Test + public void testCommitConfigurationDataDeleteNoData() throws Exception { + // assume that data to delete does not exist + prepareDataForDelete(false); + + // try to delete and expect DATA_MISSING error + final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class, + () -> brokerFacade.commitConfigurationDataDelete(instanceID)); + final List<RestconfError> errors = ex.getErrors(); + assertEquals(1, errors.size()); + assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType()); + assertEquals(ErrorTag.DATA_MISSING, errors.get(0).getErrorTag()); + } + + /** + * Prepare conditions to test delete operation. Data to delete exists or does not exist according to value of + * {@code assumeDataExists} parameter. + * @param assumeDataExists boolean to assume if data exists + */ + private void prepareDataForDelete(final boolean assumeDataExists) { + when(rwTransaction.exists(LogicalDatastoreType.CONFIGURATION, instanceID)) + .thenReturn(immediateBooleanFluentFuture(assumeDataExists)); + } + + @Test + public void testRegisterToListenDataChanges() { + final ListenerAdapter listener = Notificator.createListener(instanceID, "stream", + NotificationOutputType.XML, controllerContext); + + @SuppressWarnings("unchecked") + final ListenerRegistration<ListenerAdapter> mockRegistration = mock(ListenerRegistration.class); + + DOMDataTreeChangeService changeService = domDataBroker.getExtensions() + .getInstance(DOMDataTreeChangeService.class); + DOMDataTreeIdentifier loc = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, instanceID); + when(changeService.registerDataTreeChangeListener(eq(loc), eq(listener))).thenReturn(mockRegistration); + + brokerFacade.registerToListenDataChanges(LogicalDatastoreType.CONFIGURATION, Scope.BASE, listener); + + verify(changeService).registerDataTreeChangeListener(loc, listener); + + assertEquals("isListening", true, listener.isListening()); + + brokerFacade.registerToListenDataChanges(LogicalDatastoreType.CONFIGURATION, Scope.BASE, listener); + verifyNoMoreInteractions(changeService); + } + + /** + * Create, register, close and remove notification listener. + */ + @Test + public void testRegisterToListenNotificationChanges() throws Exception { + // create test notification listener + final String identifier = "create-notification-stream/toaster:toastDone"; + Notificator.createNotificationListener( + List.of(Absolute.of(QName.create("http://netconfcentral.org/ns/toaster", "2009-11-20", "toastDone"))), + identifier, "XML", controllerContext); + final NotificationListenerAdapter listener = Notificator.getNotificationListenerFor(identifier).get(0); + + // mock registration + final ListenerRegistration<NotificationListenerAdapter> registration = mock(ListenerRegistration.class); + when(domNotification.registerNotificationListener(listener, listener.getSchemaPath())) + .thenReturn(registration); + + // test to register listener for the first time + brokerFacade.registerToListenNotification(listener); + assertEquals("Registration was not successful", true, listener.isListening()); + + // try to register for the second time + brokerFacade.registerToListenNotification(listener); + assertEquals("Registration was not successful", true, listener.isListening()); + + // registrations should be invoked only once + verify(domNotification, times(1)).registerNotificationListener(listener, listener.getSchemaPath()); + + final DOMTransactionChain transactionChain = mock(DOMTransactionChain.class); + final DOMDataTreeWriteTransaction wTx = mock(DOMDataTreeWriteTransaction.class); + // close and remove test notification listener + listener.close(); + Notificator.removeListenerIfNoSubscriberExists(listener); + } + + /** + * Test Patch method on the server with no data. + */ + @Test + public void testPatchConfigurationDataWithinTransactionServer() throws Exception { + final PatchContext patchContext = mock(PatchContext.class); + + when(patchContext.getData()).thenReturn(List.of()); + // no mount point + doReturn(InstanceIdentifierContext.ofPath(SchemaInferenceStack.of(mock(EffectiveModelContext.class)), + mock(DataSchemaNode.class), YangInstanceIdentifier.empty(), null)) + .when(patchContext).getInstanceIdentifierContext(); + + doReturn(CommitInfo.emptyFluentFuture()).when(rwTransaction).commit(); + + final PatchStatusContext status = brokerFacade.patchConfigurationDataWithinTransaction(patchContext); + + // assert success + assertTrue("Patch operation should be successful on server", status.isOk()); + } + + /** + * Test Patch method on mounted device with no data. + */ + @Test + public void testPatchConfigurationDataWithinTransactionMount() throws Exception { + final PatchContext patchContext = mock(PatchContext.class); + final DOMMountPoint mountPoint = mock(DOMMountPoint.class); + final DOMDataBroker mountDataBroker = mock(DOMDataBroker.class); + final DOMDataTreeReadWriteTransaction transaction = mock(DOMDataTreeReadWriteTransaction.class); + + when(patchContext.getData()).thenReturn(List.of()); + // return mount point with broker + doReturn(InstanceIdentifierContext.ofPath(SchemaInferenceStack.of(mock(EffectiveModelContext.class)), + mock(DataSchemaNode.class), YangInstanceIdentifier.empty(), mountPoint)) + .when(patchContext).getInstanceIdentifierContext(); + + when(mountPoint.getService(DOMDataBroker.class)).thenReturn(Optional.of(mountDataBroker)); + when(mountPoint.getService(DOMSchemaService.class)).thenReturn(Optional.empty()); + when(mountDataBroker.newReadWriteTransaction()).thenReturn(transaction); + doReturn(CommitInfo.emptyFluentFuture()).when(transaction).commit(); + + final PatchStatusContext status = brokerFacade.patchConfigurationDataWithinTransaction(patchContext); + + // assert success + assertTrue("Patch operation should be successful on mounted device", status.isOk()); + } + + /** + * Negative test for Patch operation when mounted device does not support {@link DOMDataBroker service.} + * Patch operation should fail with global error. + */ + @Test + public void testPatchConfigurationDataWithinTransactionMountFail() throws Exception { + final PatchContext patchContext = mock(PatchContext.class); + final DOMMountPoint mountPoint = mock(DOMMountPoint.class); + + doReturn(InstanceIdentifierContext.ofPath(SchemaInferenceStack.of(mock(EffectiveModelContext.class)), + mock(DataSchemaNode.class), YangInstanceIdentifier.empty(), mountPoint)) + .when(patchContext).getInstanceIdentifierContext(); + + // missing broker on mounted device + when(mountPoint.getService(DOMDataBroker.class)).thenReturn(Optional.empty()); + when(mountPoint.getService(DOMSchemaService.class)).thenReturn(Optional.empty()); + + final PatchStatusContext status = brokerFacade.patchConfigurationDataWithinTransaction(patchContext); + + // assert not successful operation with error + assertNotNull(status.getGlobalErrors()); + assertEquals(1, status.getGlobalErrors().size()); + assertEquals(ErrorType.APPLICATION, status.getGlobalErrors().get(0).getErrorType()); + assertEquals(ErrorTag.OPERATION_FAILED, status.getGlobalErrors().get(0).getErrorTag()); + + assertFalse("Patch operation should fail on mounted device without Broker", status.isOk()); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/Bug3595Test.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/Bug3595Test.java new file mode 100644 index 0000000..0d7241d --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/Bug3595Test.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2015 Cisco 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.assertNotNull; + +import java.io.FileNotFoundException; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.Module; + +public class Bug3595Test { + + private static final QName CONT_QNAME = QName.create("leafref:module", "2014-04-17", "cont"); + private static final QName LST_WITH_LFREF_KEY_QNAME = QName.create(CONT_QNAME, "lst-with-lfref-key"); + private static final QName LFREF_KEY_QNAME = QName.create(CONT_QNAME, "lfref-key"); + private static EffectiveModelContext schemaContext; + + private final ControllerContext controllerContext = TestRestconfUtils.newControllerContext(schemaContext); + + @BeforeClass + public static void initialize() throws FileNotFoundException { + schemaContext = TestUtils.loadSchemaContext("/leafref/yang"); + Module module = TestUtils.findModule(schemaContext.getModules(), "leafref-module"); + assertNotNull(module); + module = TestUtils.findModule(schemaContext.getModules(), "referenced-module"); + assertNotNull(module); + } + + @Test + public void testLeafrefListKeyDeserializtion() { + final YangInstanceIdentifier node1IIexpected = YangInstanceIdentifier.of(CONT_QNAME) + .node(LST_WITH_LFREF_KEY_QNAME).node(NodeIdentifierWithPredicates.of( + LST_WITH_LFREF_KEY_QNAME, LFREF_KEY_QNAME, "node1")); + final InstanceIdentifierContext iiContext = + controllerContext.toInstanceIdentifier("leafref-module:cont/lst-with-lfref-key/node1"); + iiContext.getInstanceIdentifier(); + assertEquals(node1IIexpected, iiContext.getInstanceIdentifier()); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/Bug8072Test.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/Bug8072Test.java new file mode 100644 index 0000000..a65babd --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/Bug8072Test.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017 Cisco 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.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import java.io.FileNotFoundException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class Bug8072Test { + private static final String EXTERNAL_MODULE_NAME = "test-module"; + private static final QName MODULES_QNAME = QName.create("test:module", "2014-01-09", "modules"); + private static final QName MODULE_QNAME = QName.create("test:module", "2014-01-09", "module"); + private static final QName NAME_QNAME = QName.create("test:module", "2014-01-09", "name"); + private static final QName TYPE_QNAME = QName.create("test:module", "2014-01-09", "type"); + private static final QName MODULE_TYPE_QNAME = QName.create("test:module", "2014-01-09", "module-type"); + + private static EffectiveModelContext schemaContext; + + private final ControllerContext controllerContext; + + public Bug8072Test() throws FileNotFoundException { + final EffectiveModelContext mountPointContext = TestUtils.loadSchemaContext("/full-versions/test-module"); + final DOMMountPoint mountInstance = mock(DOMMountPoint.class); + controllerContext = TestRestconfUtils.newControllerContext(schemaContext, mountInstance); + doReturn(Optional.of(FixedDOMSchemaService.of(() -> mountPointContext))).when(mountInstance) + .getService(DOMSchemaService.class); + } + + @BeforeClass + public static void init() throws FileNotFoundException, ReactorException { + schemaContext = TestUtils.loadSchemaContext("/full-versions/yangs"); + assertEquals(0, schemaContext.findModules(EXTERNAL_MODULE_NAME).size()); + } + + @Test + public void testIdentityRefFromExternalModule() throws FileNotFoundException, ReactorException { + final InstanceIdentifierContext ctx = controllerContext.toInstanceIdentifier( + "simple-nodes:users/yang-ext:mount/test-module:modules/module/test-module:module-type/name"); + + final Map<QName, Object> keyValues = new HashMap<>(); + keyValues.put(NAME_QNAME, "name"); + keyValues.put(TYPE_QNAME, MODULE_TYPE_QNAME); + final YangInstanceIdentifier expectedYII = YangInstanceIdentifier.of(MODULES_QNAME).node(MODULE_QNAME) + .node(NodeIdentifierWithPredicates.of(MODULE_QNAME, keyValues)); + + assertEquals(expectedYII, ctx.getInstanceIdentifier()); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CnSnToXmlAndJsonInstanceIdentifierTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CnSnToXmlAndJsonInstanceIdentifierTest.java new file mode 100644 index 0000000..96100cb --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CnSnToXmlAndJsonInstanceIdentifierTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2014 Cisco 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.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; +import org.junit.BeforeClass; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class CnSnToXmlAndJsonInstanceIdentifierTest extends YangAndXmlAndDataSchemaLoader { + + @BeforeClass + public static void initialize() throws FileNotFoundException, ReactorException { + dataLoad("/instanceidentifier/yang", 4, "instance-identifier-module", "cont"); + } + + + private static void validateXmlOutput(final String xml) throws XMLStreamException { + final XMLInputFactory xmlInFactory = XMLInputFactory.newInstance(); + XMLEventReader eventReader; + + eventReader = xmlInFactory.createXMLEventReader(new ByteArrayInputStream(xml.getBytes())); + String augmentAugmentModulePrefix = null; + String augmentModulePrefix = null; + String instanceIdentifierModulePrefix = null; + while (eventReader.hasNext()) { + final XMLEvent nextEvent = eventReader.nextEvent(); + if (nextEvent.isStartElement()) { + final StartElement startElement = (StartElement) nextEvent; + if (startElement.getName().getLocalPart().equals("lf111")) { + final Iterator<?> prefixes = + startElement.getNamespaceContext().getPrefixes("augment:augment:module"); + + while (prefixes.hasNext() && augmentAugmentModulePrefix == null) { + final String prefix = (String) prefixes.next(); + if (!prefix.isEmpty()) { + augmentAugmentModulePrefix = prefix; + } + } + + augmentModulePrefix = startElement.getNamespaceContext().getPrefix("augment:module"); + instanceIdentifierModulePrefix = + startElement.getNamespaceContext().getPrefix("instance:identifier:module"); + break; + } + } + } + + assertNotNull(augmentAugmentModulePrefix); + assertNotNull(augmentModulePrefix); + assertNotNull(instanceIdentifierModulePrefix); + + final String instanceIdentifierValue = "/" + instanceIdentifierModulePrefix + ":cont/" + + instanceIdentifierModulePrefix + ":cont1/" + augmentModulePrefix + ":lst11[" + augmentModulePrefix + + ":keyvalue111='value1'][" + augmentModulePrefix + ":keyvalue112='value2']/" + + augmentAugmentModulePrefix + ":lf112"; + + assertTrue(xml.contains(instanceIdentifierValue)); + + } + + private static void validateXmlOutputWithLeafList(final String xml) throws XMLStreamException { + final XMLInputFactory xmlInFactory = XMLInputFactory.newInstance(); + XMLEventReader eventReader; + + eventReader = xmlInFactory.createXMLEventReader(new ByteArrayInputStream(xml.getBytes())); + String augmentModuleLfLstPrefix = null; + String iiModulePrefix = null; + while (eventReader.hasNext()) { + final XMLEvent nextEvent = eventReader.nextEvent(); + if (nextEvent.isStartElement()) { + final StartElement startElement = (StartElement) nextEvent; + if (startElement.getName().getLocalPart().equals("lf111")) { + final Iterator<?> prefixes = + startElement.getNamespaceContext().getPrefixes("augment:module:leaf:list"); + + while (prefixes.hasNext() && augmentModuleLfLstPrefix == null) { + final String prefix = (String) prefixes.next(); + if (!prefix.isEmpty()) { + augmentModuleLfLstPrefix = prefix; + } + } + iiModulePrefix = startElement.getNamespaceContext().getPrefix("instance:identifier:module"); + break; + } + } + } + + assertNotNull(augmentModuleLfLstPrefix); + assertNotNull(iiModulePrefix); + + final String instanceIdentifierValue = "/" + iiModulePrefix + ":cont/" + iiModulePrefix + ":cont1/" + + augmentModuleLfLstPrefix + ":lflst11[.='lflst11_1']"; + + assertTrue(xml.contains(instanceIdentifierValue)); + + } + + private static YangInstanceIdentifier createInstanceIdentifier() { + final List<PathArgument> pathArguments = new ArrayList<>(); + pathArguments.add(new NodeIdentifier(QName.create("instance:identifier:module", "cont"))); + pathArguments.add(new NodeIdentifier(QName.create("instance:identifier:module", "cont1"))); + + final QName qName = QName.create("augment:module", "lst11"); + final Map<QName, Object> keyValues = new HashMap<>(); + keyValues.put(QName.create("augment:module", "keyvalue111"), "value1"); + keyValues.put(QName.create("augment:module", "keyvalue112"), "value2"); + final NodeIdentifierWithPredicates nodeIdentifierWithPredicates = + NodeIdentifierWithPredicates.of(qName, keyValues); + pathArguments.add(nodeIdentifierWithPredicates); + + pathArguments.add(new NodeIdentifier(QName.create("augment:augment:module", "lf112"))); + + return YangInstanceIdentifier.create(pathArguments); + } + + private static YangInstanceIdentifier createInstanceIdentifierWithLeafList() { + final List<PathArgument> pathArguments = new ArrayList<>(); + pathArguments.add(new NodeIdentifier(QName.create("instance:identifier:module", "cont"))); + pathArguments.add(new NodeIdentifier(QName.create("instance:identifier:module", "cont1"))); + pathArguments.add(new NodeWithValue<>(QName.create("augment:module:leaf:list", "lflst11"), "lflst11_1")); + + return YangInstanceIdentifier.create(pathArguments); + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CodecsExceptionsCatchingTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CodecsExceptionsCatchingTest.java new file mode 100644 index 0000000..2110848 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CodecsExceptionsCatchingTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2014, 2015 Brocade Communication Systems, Inc., Cisco 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.assertTrue; +import static org.mockito.Mockito.mock; + +import java.io.FileNotFoundException; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeJsonBodyWriter; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeXmlBodyWriter; +import org.opendaylight.netconf.sal.rest.impl.XmlNormalizedNodeBodyReader; +import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class CodecsExceptionsCatchingTest extends JerseyTest { + + private RestconfImpl restConf; + private ControllerContext controllerContext; + + @Before + public void init() throws FileNotFoundException, ReactorException { + restConf = RestconfImpl.newInstance(mock(BrokerFacade.class), controllerContext); + controllerContext = TestRestconfUtils.newControllerContext(TestUtils.loadSchemaContext( + "/decoding-exception/yang")); + } + + @Override + protected Application configure() { + /* enable/disable Jersey logs to console */ + // enable(TestProperties.LOG_TRAFFIC); + // enable(TestProperties.DUMP_ENTITY); + // enable(TestProperties.RECORD_LOG_LEVEL); + // set(TestProperties.RECORD_LOG_LEVEL, Level.ALL.intValue()); + ResourceConfig resourceConfig = new ResourceConfig(); + resourceConfig = resourceConfig.registerInstances(restConf, new NormalizedNodeJsonBodyWriter(), + new NormalizedNodeXmlBodyWriter(), new XmlNormalizedNodeBodyReader(controllerContext), + new JsonNormalizedNodeBodyReader(controllerContext)); + return resourceConfig; + } + + @Test + @Ignore // TODO RestconfDocumentedExceptionMapper needs be fixed before + public void stringToNumberConversionError() { + final Response response = target("/config/number:cont").request(MediaType.APPLICATION_XML).put( + Entity.entity("<cont xmlns=\"number\"><lf>3f</lf></cont>", MediaType.APPLICATION_XML)); + final String exceptionMessage = response.readEntity(String.class); + assertTrue(exceptionMessage.contains("invalid-value")); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CutDataToCorrectDepthTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CutDataToCorrectDepthTest.java new file mode 100644 index 0000000..e379b3a --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CutDataToCorrectDepthTest.java @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2014 Cisco 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.assertNotNull; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import javax.ws.rs.Consumes; +import javax.ws.rs.Encoded; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.BeforeClass; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +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.WriterParameters; +import org.opendaylight.netconf.sal.rest.impl.XmlNormalizedNodeBodyReader; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.QueryParametersParser; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.SystemLeafSetNode; +import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode; +import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode; +import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder; +import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.api.schema.builder.ListNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CutDataToCorrectDepthTest extends JerseyTest { + + private static final Logger LOG = LoggerFactory.getLogger(JerseyTest.class); + + private static NormalizedNode depth1Cont; + private static NormalizedNode depth2Cont1; + private NormalizedNode globalPayload; + private static EffectiveModelContext schemaContextModules; + + private final ControllerContext controllerContext = + TestRestconfUtils.newControllerContext(schemaContextModules, null); + + @Path("/") + public class RestImpl { + + @GET + @Path("/config/{identifier:.+}") + @Produces({ "application/json", "application/xml" }) + public NormalizedNodeContext getData(@Encoded @PathParam("identifier") final String identifier, + @Context final UriInfo uriInfo) { + + final InstanceIdentifierContext iiWithData = controllerContext.toInstanceIdentifier(identifier); + + NormalizedNode data = null; + if (identifier.equals("nested-module:depth1-cont/depth2-cont1")) { + data = depth2Cont1; + } else if (identifier.equals("nested-module:depth1-cont")) { + data = depth1Cont; + } + + final WriterParameters writerParameters = QueryParametersParser.parseWriterParameters(uriInfo); + return new NormalizedNodeContext(iiWithData, data, writerParameters); + } + + @GET + @Path("/operational/{identifier:.+}") + @Produces({ "application/json", "application/xml" }) + public NormalizedNodeContext getDataOperational(@Encoded @PathParam("identifier") final String identifier, + @Context final UriInfo uriInfo) { + return getData(identifier, uriInfo); + } + + @PUT + @Path("/config/{identifier:.+}") + @Consumes({ "application/json", "application/xml" }) + public void normalizedData(@Encoded @PathParam("identifier") final String identifier, + final NormalizedNodeContext payload) throws InterruptedException { + LOG.info("Payload: {}.", payload); + LOG.info("Instance identifier of payload: {}.", + payload.getInstanceIdentifierContext().getInstanceIdentifier()); + LOG.info("Data of payload: {}.", payload.getData()); + globalPayload = payload.getData(); + } + + @PUT + @Path("/operational/{identifier:.+}") + @Consumes({ "application/json", "application/xml" }) + public void normalizedDataOperational(@Encoded @PathParam("identifier") final String identifier, + final NormalizedNodeContext payload) throws InterruptedException { + normalizedData(identifier, payload); + } + } + + @BeforeClass + public static void initialize() throws FileNotFoundException, ReactorException { + schemaContextModules = TestUtils.loadSchemaContext("/modules"); + final Module module = TestUtils.findModule(schemaContextModules.getModules(), "nested-module"); + assertNotNull(module); + + final UnkeyedListNode listAsUnkeyedList = unkeyedList( + "depth2-cont1", + unkeyedEntry("depth2-cont1", + container("depth3-cont1", + container("depth4-cont1", leaf("depth5-leaf1", "depth5-leaf1-value")), + leaf("depth4-leaf1", "depth4-leaf1-value")), leaf("depth3-leaf1", "depth3-leaf1-value"))); + + final MapNode listAsMap = mapNode( + "depth2-list2", + mapEntryNode("depth2-list2", 2, leaf("depth3-lf1-key", "depth3-lf1-key-value"), + leaf("depth3-lf2-key", "depth3-lf2-key-value"), leaf("depth3-lf3", "depth3-lf3-value"))); + + depth1Cont = container( + "depth1-cont", + listAsUnkeyedList, + listAsMap, + leafList("depth2-lfLst1", "depth2-lflst1-value1", "depth2-lflst1-value2", "depth2-lflst1-value3"), + container( + "depth2-cont2", + container("depth3-cont2", + container("depth4-cont2", leaf("depth5-leaf2", "depth5-leaf2-value")), + leaf("depth4-leaf2", "depth4-leaf2-value")), leaf("depth3-leaf2", "depth3-leaf2-value")), + leaf("depth2-leaf1", "depth2-leaf1-value")); + + depth2Cont1 = listAsUnkeyedList; + } + + // TODO: These tests should be fixed/rewriten because they fail randomly due to data not being de-serialized + // properly in readers + //@Test + public void getDataWithUriDepthParameterTest() throws WebApplicationException, IOException { + getDataWithUriDepthParameter("application/json"); + getDataWithUriDepthParameter("application/xml"); + } + + public void getDataWithUriDepthParameter(final String mediaType) throws WebApplicationException, IOException { + Response response; + + // Test config with depth 1 + response = target("/config/nested-module:depth1-cont").queryParam("depth", "1").request(mediaType) + .get(); + txtDataToNormalizedNode(response, mediaType, "/config/nested-module:depth1-cont"); + verifyResponse(nodeDataDepth1()); + + // Test config with depth 2 + response = target("/config/nested-module:depth1-cont").queryParam("depth", "2").request(mediaType) + .get(); + txtDataToNormalizedNode(response, mediaType, "/config/nested-module:depth1-cont"); + verifyResponse(nodeDataDepth2()); + + // Test config with depth 3 + response = target("/config/nested-module:depth1-cont").queryParam("depth", "3").request(mediaType) + .get(); + txtDataToNormalizedNode(response, mediaType, "/config/nested-module:depth1-cont"); + verifyResponse(nodeDataDepth3()); + + // Test config with depth 4 + response = target("/config/nested-module:depth1-cont").queryParam("depth", "4").request(mediaType) + .get(); + txtDataToNormalizedNode(response, mediaType, "/config/nested-module:depth1-cont"); + verifyResponse(nodeDataDepth4()); + + // Test config with depth 5 + response = target("/config/nested-module:depth1-cont").queryParam("depth", "5").request(mediaType) + .get(); + txtDataToNormalizedNode(response, mediaType, "/config/nested-module:depth1-cont"); + verifyResponse(nodeDataDepth5()); + + // Test config with depth unbounded + + response = target("/config/nested-module:depth1-cont").queryParam("depth", "unbounded") + .request(mediaType).get(); + txtDataToNormalizedNode(response, mediaType, "/config/nested-module:depth1-cont"); + verifyResponse(nodeDataDepth5()); + } + + private void txtDataToNormalizedNode(final Response response, final String mediaType, final String uri) { + final String responseStr = response.readEntity(String.class); + LOG.info("Response entity message: {}.", responseStr); + target(uri).request(mediaType).put(Entity.entity(responseStr, mediaType)); + } + + private void verifyResponse(final NormalizedNode nodeData) throws WebApplicationException, IOException { + assertNotNull(globalPayload); + assertEquals(globalPayload, nodeData); + globalPayload = null; + } + + @Override + protected Application configure() { + ResourceConfig resourceConfig = new ResourceConfig(); + resourceConfig = resourceConfig.registerInstances(new RestImpl()); + resourceConfig.registerClasses(XmlNormalizedNodeBodyReader.class, NormalizedNodeXmlBodyWriter.class, + JsonNormalizedNodeBodyReader.class, NormalizedNodeJsonBodyWriter.class, + RestconfDocumentedExceptionMapper.class); + return resourceConfig; + } + + private static LeafNode<?> leaf(final String localName, final Object value) { + return Builders.leafBuilder().withNodeIdentifier(toIdentifier(localName)).withValue(value).build(); + } + + private static ContainerNode container(final String localName, final DataContainerChild... children) { + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> containerBuilder = + Builders.containerBuilder(); + for (final DataContainerChild child : children) { + containerBuilder.withChild(child); + } + containerBuilder.withNodeIdentifier(toIdentifier(localName)); + return containerBuilder.build(); + } + + private static UnkeyedListNode unkeyedList( + final String localName, + final UnkeyedListEntryNode... entryNodes) { + final CollectionNodeBuilder<UnkeyedListEntryNode, UnkeyedListNode> builder = Builders.unkeyedListBuilder(); + final NodeIdentifier identifier = toIdentifier(localName); + builder.withNodeIdentifier(identifier); + for (final UnkeyedListEntryNode unkeyedListEntryNode : entryNodes) { + builder.withChild(unkeyedListEntryNode); + } + return builder.build(); + } + + private static UnkeyedListEntryNode unkeyedEntry(final String localName, final DataContainerChild... children) { + final DataContainerNodeBuilder<NodeIdentifier, UnkeyedListEntryNode> builder = + Builders.unkeyedListEntryBuilder(); + builder.withNodeIdentifier(toIdentifier(localName)); + for (final DataContainerChild child : children) { + builder.withChild(child); + } + return builder.build(); + } + + private static MapNode mapNode(final String localName, final MapEntryNode... entryNodes) { + final CollectionNodeBuilder<MapEntryNode, SystemMapNode> builder = Builders.mapBuilder(); + builder.withNodeIdentifier(toIdentifier(localName)); + for (final MapEntryNode mapEntryNode : entryNodes) { + builder.withChild(mapEntryNode); + } + return builder.build(); + } + + private static MapEntryNode mapEntryNode(final String localName, final int keysNumber, + final DataContainerChild... children) { + final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder = + Builders.mapEntryBuilder(); + final Map<QName, Object> keys = new HashMap<>(); + for (int i = 0; i < keysNumber; i++) { + keys.put(children[i].getIdentifier().getNodeType(), children[i].body()); + } + builder.withNodeIdentifier(toIdentifier(localName, keys)); + + for (final DataContainerChild child : children) { + builder.withChild(child); + } + return builder.build(); + } + + private static LeafSetNode<?> leafList(final String localName, final String... children) { + final ListNodeBuilder<Object, SystemLeafSetNode<Object>> builder = Builders.leafSetBuilder(); + builder.withNodeIdentifier(toIdentifier(localName)); + for (final String child : children) { + builder.withChild(Builders.leafSetEntryBuilder().withNodeIdentifier(toIdentifier(localName, child)) + .withValue(child).build()); + } + return builder.build(); + } + + private static NodeIdentifier toIdentifier(final String localName) { + return new NodeIdentifier(QName.create("urn:nested:module", "2014-06-03", localName)); + } + + private static NodeIdentifierWithPredicates toIdentifier(final String localName, final Map<QName, Object> keys) { + return NodeIdentifierWithPredicates.of(QName.create("urn:nested:module", "2014-06-03", localName), keys); + } + + private static NodeWithValue<?> toIdentifier(final String localName, final Object value) { + return new NodeWithValue<>(QName.create("urn:nested:module", "2014-06-03", localName), value); + } + + private static UnkeyedListEntryNode nodeDataDepth3Operational() { + return unkeyedEntry("depth2-cont1", + container("depth3-cont1", container("depth4-cont1"), leaf("depth4-leaf1", "depth4-leaf1-value")), + leaf("depth3-leaf1", "depth3-leaf1-value")); + } + + private static ContainerNode nodeDataDepth5() { + return container( + "depth1-cont", + unkeyedList( + "depth2-cont1", + unkeyedEntry("depth2-cont1", + container("depth3-cont1", + container("depth4-cont1", leaf("depth5-leaf1", "depth5-leaf1-value")), + leaf("depth4-leaf1", "depth4-leaf1-value")), + leaf("depth3-leaf1", "depth3-leaf1-value"))), + mapNode("depth2-list2", + mapEntryNode("depth2-list2", 2, leaf("depth3-lf1-key", "depth3-lf1-key-value"), + leaf("depth3-lf2-key", "depth3-lf2-key-value"), leaf("depth3-lf3", "depth3-lf3-value"))), + leafList("depth2-lfLst1", "depth2-lflst1-value1", "depth2-lflst1-value2", "depth2-lflst1-value3"), + container( + "depth2-cont2", + container("depth3-cont2", + container("depth4-cont2", leaf("depth5-leaf2", "depth5-leaf2-value")), + leaf("depth4-leaf2", "depth4-leaf2-value")), leaf("depth3-leaf2", "depth3-leaf2-value")), + leaf("depth2-leaf1", "depth2-leaf1-value")); + } + + private static ContainerNode nodeDataDepth4() { + return container( + "depth1-cont", + unkeyedList("depth2-cont1", nodeDataDepth3Operational()), + mapNode("depth2-list2", + mapEntryNode("depth2-list2", 2, leaf("depth3-lf1-key", "depth3-lf1-key-value"), + leaf("depth3-lf2-key", "depth3-lf2-key-value"), leaf("depth3-lf3", "depth3-lf3-value"))), + leafList("depth2-lfLst1", "depth2-lflst1-value1", "depth2-lflst1-value2", "depth2-lflst1-value3"), + container( + "depth2-cont2", + container("depth3-cont2", container("depth4-cont2"), leaf("depth4-leaf2", "depth4-leaf2-value")), + leaf("depth3-leaf2", "depth3-leaf2-value")), leaf("depth2-leaf1", "depth2-leaf1-value")); + } + + private static ContainerNode nodeDataDepth3() { + return container( + "depth1-cont", + unkeyedList("depth2-cont1", + unkeyedEntry("depth2-cont1", container("depth3-cont1"), leaf("depth3-leaf1", "depth3-leaf1-value"))), + mapNode("depth2-list2", + mapEntryNode("depth2-list2", 2, leaf("depth3-lf1-key", "depth3-lf1-key-value"), + leaf("depth3-lf2-key", "depth3-lf2-key-value"), leaf("depth3-lf3", "depth3-lf3-value"))), + leafList("depth2-lfLst1", "depth2-lflst1-value1", "depth2-lflst1-value2", "depth2-lflst1-value3"), + container("depth2-cont2", container("depth3-cont2"), leaf("depth3-leaf2", "depth3-leaf2-value")), + leaf("depth2-leaf1", "depth2-leaf1-value")); + } + + private static ContainerNode nodeDataDepth2() { + return container( + "depth1-cont", + unkeyedList("depth2-cont1", unkeyedEntry("depth2-cont1")), + mapNode("depth2-list2", + mapEntryNode("depth2-list2", 2, leaf("depth3-lf1-key", "depth3-lf1-key-value"), + leaf("depth3-lf2-key", "depth3-lf2-key-value"))), container("depth2-cont2"), +// leafList("depth2-lfLst1"), + leaf("depth2-leaf1", "depth2-leaf1-value")); + } + + private static ContainerNode nodeDataDepth1() { + return container("depth1-cont"); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyFuture.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyFuture.java new file mode 100644 index 0000000..30a0c50 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyFuture.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2014 Cisco 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 java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.opendaylight.yangtools.yang.common.RpcResult; + +public class DummyFuture<T> implements Future<RpcResult<T>> { + + private final boolean cancel; + private final boolean isCancelled; + private final boolean isDone; + private final RpcResult<T> result; + + public DummyFuture() { + cancel = false; + isCancelled = false; + isDone = false; + result = null; + } + + private DummyFuture(final Builder<T> builder) { + cancel = builder.cancel; + isCancelled = builder.isCancelled; + isDone = builder.isDone; + result = builder.result; + } + + @Override + public boolean cancel(final boolean mayInterruptIfRunning) { + return cancel; + } + + @Override + public boolean isCancelled() { + return isCancelled; + } + + @Override + public boolean isDone() { + return isDone; + } + + @Override + public RpcResult<T> get() throws InterruptedException, ExecutionException { + return result; + } + + @Override + public RpcResult<T> get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, + TimeoutException { + return result; + } + + public static class Builder<T> { + + private boolean cancel; + private boolean isCancelled; + private boolean isDone; + private RpcResult<T> result; + + public Builder<T> cancel(final boolean newCancel) { + this.cancel = newCancel; + return this; + } + + public Builder<T> isCancelled(final boolean cancelled) { + this.isCancelled = cancelled; + return this; + } + + public Builder<T> isDone(final boolean done) { + this.isDone = done; + return this; + } + + public Builder<T> rpcResult(final RpcResult<T> newResult) { + this.result = newResult; + return this; + } + + public Future<RpcResult<T>> build() { + return new DummyFuture<>(this); + } + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyRpcResult.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyRpcResult.java new file mode 100644 index 0000000..ff01937 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyRpcResult.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2014 Cisco 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 java.util.List; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResult; + +public class DummyRpcResult<T> implements RpcResult<T> { + + private final boolean isSuccessful; + private final T result; + private final List<RpcError> errors; + + public DummyRpcResult() { + isSuccessful = false; + result = null; + errors = null; + } + + private DummyRpcResult(final Builder<T> builder) { + isSuccessful = builder.isSuccessful; + result = builder.result; + errors = builder.errors; + } + + @Override + public boolean isSuccessful() { + return isSuccessful; + } + + @Override + public T getResult() { + return result; + } + + @Override + public List<RpcError> getErrors() { + return errors; + } + + public static class Builder<T> { + private boolean isSuccessful; + private T result; + private List<RpcError> errors; + + public Builder<T> isSuccessful(final boolean successful) { + this.isSuccessful = successful; + return this; + } + + public Builder<T> result(final T newResult) { + this.result = newResult; + return this; + } + + public Builder<T> errors(final List<RpcError> newErrors) { + this.errors = newErrors; + return this; + } + + public RpcResult<T> build() { + return new DummyRpcResult<>(this); + } + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyType.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyType.java new file mode 100644 index 0000000..af408e6 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyType.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2014 Cisco 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 java.util.List; +import java.util.Optional; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.Status; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode; + +public class DummyType implements TypeDefinition<DummyType> { + QName dummyQName = TestUtils.buildQName("dummy type", "simple:uri", "2012-12-17"); + + @Override + public QName getQName() { + return dummyQName; + } + + @Override + public Optional<String> getDescription() { + return Optional.empty(); + } + + @Override + public Optional<String> getReference() { + return Optional.empty(); + } + + @Override + public Status getStatus() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List<UnknownSchemaNode> getUnknownSchemaNodes() { + // TODO Auto-generated method stub + return null; + } + + @Override + public DummyType getBaseType() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Optional<String> getUnits() { + return Optional.empty(); + } + + @Override + public Optional<? extends Object> getDefaultValue() { + return Optional.empty(); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/ExpressionParserTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/ExpressionParserTest.java new file mode 100644 index 0000000..f4ed8f9 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/ExpressionParserTest.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2016 Cisco 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 java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.Collection; +import java.util.Optional; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter; +import org.opendaylight.netconf.sal.streams.listeners.Notificator; +import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; + +public class ExpressionParserTest { + + private Collection<File> xmls; + + @Before + public void setup() throws Exception { + this.xmls = TestRestconfUtils.loadFiles("/notifications/xml/output/"); + } + + @Test + public void trueDownFilterTest() throws Exception { + final boolean parser = + parser("notification/data-changed-notification/data-change-event/data/toasterStatus='down'", + "data_change_notification_toaster_status_DOWN.xml"); + Assert.assertTrue(parser); + } + + @Test + public void falseDownFilterTest() throws Exception { + final boolean parser = + parser("notification/data-changed-notification/data-change-event/data/toasterStatus='up'", + "data_change_notification_toaster_status_DOWN.xml"); + Assert.assertFalse(parser); + } + + @Test + public void trueNumberEqualsFilterTest() throws Exception { + final boolean parser = parser( + "notification/data-changed-notification/data-change-event/data/toasterStatus=1", + "data_change_notification_toaster_status_NUMBER.xml"); + Assert.assertTrue(parser); + } + + @Test + public void falseNumberEqualsFilterTest() throws Exception { + final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus=0", + "data_change_notification_toaster_status_NUMBER.xml"); + Assert.assertFalse(parser); + } + + @Test + public void trueNumberLessFilterTest() throws Exception { + final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus<2", + "data_change_notification_toaster_status_NUMBER.xml"); + Assert.assertTrue(parser); + } + + @Test + public void falseNumberLessFilterTest() throws Exception { + final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus<0", + "data_change_notification_toaster_status_NUMBER.xml"); + Assert.assertFalse(parser); + } + + @Test + public void trueNumberLessEqualsFilterTest() throws Exception { + final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus<=2", + "data_change_notification_toaster_status_NUMBER.xml"); + Assert.assertTrue(parser); + } + + @Test + public void falseNumberLessEqualsFilterTest() throws Exception { + final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus<=-1", + "data_change_notification_toaster_status_NUMBER.xml"); + Assert.assertFalse(parser); + } + + @Test + public void trueNumberGreaterFilterTest() throws Exception { + final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus>0", + "data_change_notification_toaster_status_NUMBER.xml"); + Assert.assertTrue(parser); + } + + @Test + public void falseNumberGreaterFilterTest() throws Exception { + final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus>5", + "data_change_notification_toaster_status_NUMBER.xml"); + Assert.assertFalse(parser); + } + + @Test + public void trueNumberGreaterEqualsFilterTest() throws Exception { + final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus>=0", + "data_change_notification_toaster_status_NUMBER.xml"); + Assert.assertTrue(parser); + } + + @Test + public void falseNumberGreaterEqualsFilterTest() throws Exception { + final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus>=5", + "data_change_notification_toaster_status_NUMBER.xml"); + Assert.assertFalse(parser); + } + + private boolean parser(final String filter, final String fileName) throws Exception { + File xml = null; + for (final File file : this.xmls) { + if (file.getName().equals(fileName)) { + xml = file; + } + } + final YangInstanceIdentifier path = Mockito.mock(YangInstanceIdentifier.class); + final PathArgument pathValue = NodeIdentifier.create(QName.create("module", "2016-12-14", "localName")); + Mockito.when(path.getLastPathArgument()).thenReturn(pathValue); + final ListenerAdapter listener = Notificator.createListener(path, "streamName", NotificationOutputType.JSON, + null); + listener.setQueryParams(Instant.now(), Optional.empty(), Optional.ofNullable(filter), false, false); + + // FIXME: do not use reflection here + final Class<?> superclass = listener.getClass().getSuperclass().getSuperclass(); + Method method = null; + for (final Method met : superclass.getDeclaredMethods()) { + if (met.getName().equals("parseFilterParam")) { + method = met; + } + } + if (method == null) { + throw new Exception("Methode parseFilterParam doesn't exist in " + superclass.getName()); + } + method.setAccessible(true); + return (boolean) method.invoke(listener, readFile(xml)); + } + + private static String readFile(final File xml) throws IOException { + try (BufferedReader br = new BufferedReader(new FileReader(xml, StandardCharsets.UTF_8))) { + final StringBuilder sb = new StringBuilder(); + String line = br.readLine(); + + while (line != null) { + sb.append(line); + sb.append("\n"); + line = br.readLine(); + } + return sb.toString(); + } + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/InvokeRpcMethodTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/InvokeRpcMethodTest.java new file mode 100644 index 0000000..19386a1 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/InvokeRpcMethodTest.java @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2013, 2015 Brocade Communication Systems, Inc., Cisco 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.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFailedFluentFuture; +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture; + +import java.io.FileNotFoundException; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException; +import org.opendaylight.mdsal.dom.api.DOMRpcResult; +import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl; +import org.opendaylight.restconf.common.ErrorTags; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.restconf.common.errors.RestconfError; +import org.opendaylight.yangtools.yang.common.ErrorTag; +import org.opendaylight.yangtools.yang.common.ErrorType; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.opendaylight.yangtools.yang.common.XMLNamespace; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.api.schema.builder.NormalizedNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders; +import org.opendaylight.yangtools.yang.model.api.ContainerLike; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.InputSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class InvokeRpcMethodTest { + + private static UriInfo uriInfo; + private static EffectiveModelContext schemaContext; + + private final RestconfImpl restconfImpl; + private final ControllerContext controllerContext; + private final BrokerFacade brokerFacade = mock(BrokerFacade.class); + + public InvokeRpcMethodTest() { + controllerContext = TestRestconfUtils.newControllerContext(schemaContext); + restconfImpl = RestconfImpl.newInstance(brokerFacade, controllerContext); + } + + @BeforeClass + public static void init() throws FileNotFoundException, ReactorException { + schemaContext = TestUtils.loadSchemaContext("/full-versions/yangs", "/invoke-rpc"); + final Collection<? extends Module> allModules = schemaContext.getModules(); + assertNotNull(allModules); + final Module module = TestUtils.resolveModule("invoke-rpc-module", allModules); + assertNotNull(module); + + uriInfo = mock(UriInfo.class); + final MultivaluedMap<String, String> map = new MultivaluedHashMap<>(); + map.put("prettyPrint", List.of("true")); + doReturn(map).when(uriInfo).getQueryParameters(any(Boolean.class)); + } + + /** + * Test method invokeRpc in RestconfImpl class tests if composite node as input parameter of method invokeRpc + * (second argument) is wrapped to parent composite node which has QName equals to QName of rpc (resolved from + * string - first argument). + */ + @Test + public void invokeRpcMethodTest() { + controllerContext.findModuleNameByNamespace(XMLNamespace.of("invoke:rpc:module")); + + final NormalizedNodeContext payload = prepareDomPayload(); + + final NormalizedNodeContext rpcResponse = + restconfImpl.invokeRpc("invoke-rpc-module:rpc-test", payload, uriInfo); + assertNotNull(rpcResponse); + assertNull(rpcResponse.getData()); + + } + + private NormalizedNodeContext prepareDomPayload() { + final EffectiveModelContext schema = controllerContext.getGlobalSchema(); + final Module rpcModule = schema.findModules("invoke-rpc-module").iterator().next(); + assertNotNull(rpcModule); + final QName rpcQName = QName.create(rpcModule.getQNameModule(), "rpc-test"); + RpcDefinition rpcSchemaNode = null; + for (final RpcDefinition rpc : rpcModule.getRpcs()) { + if (rpcQName.isEqualWithoutRevision(rpc.getQName())) { + rpcSchemaNode = rpc; + break; + } + } + assertNotNull(rpcSchemaNode); + final InputSchemaNode rpcInputSchemaNode = rpcSchemaNode.getInput(); + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> container = + SchemaAwareBuilders.containerBuilder(rpcInputSchemaNode); + + final QName contQName = QName.create(rpcModule.getQNameModule(), "cont"); + final DataSchemaNode contSchemaNode = rpcInputSchemaNode.getDataChildByName(contQName); + assertTrue(contSchemaNode instanceof ContainerSchemaNode); + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> contNode = + SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) contSchemaNode); + + final QName lfQName = QName.create(rpcModule.getQNameModule(), "lf"); + final DataSchemaNode lfSchemaNode = ((ContainerSchemaNode) contSchemaNode).getDataChildByName(lfQName); + assertTrue(lfSchemaNode instanceof LeafSchemaNode); + final LeafNode<Object> lfNode = + SchemaAwareBuilders.leafBuilder((LeafSchemaNode) lfSchemaNode).withValue("any value").build(); + contNode.withChild(lfNode); + container.withChild(contNode.build()); + + return new NormalizedNodeContext(InstanceIdentifierContext.ofRpcInput(schema, rpcSchemaNode, null), + container.build()); + } + + @Test + public void testInvokeRpcWithNoPayloadRpc_FailNoErrors() { + final QName qname = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast"); + + doReturn(immediateFailedFluentFuture(new DOMRpcImplementationNotAvailableException("testExeption"))) + .when(brokerFacade).invokeRpc(eq(qname), any()); + + final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class, + () -> restconfImpl.invokeRpc("toaster:cancel-toast", null, uriInfo)); + verifyRestconfDocumentedException(ex, 0, ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED, + Optional.empty(), Optional.empty()); + } + + void verifyRestconfDocumentedException(final RestconfDocumentedException restDocumentedException, final int index, + final ErrorType expErrorType, final ErrorTag expErrorTag, final Optional<String> expErrorMsg, + final Optional<String> expAppTag) { + + final List<RestconfError> errors = restDocumentedException.getErrors(); + assertTrue("RestconfError not found at index " + index, errors.size() > index); + + RestconfError actual = errors.get(index); + + assertEquals("getErrorType", expErrorType, actual.getErrorType()); + assertEquals("getErrorTag", expErrorTag, actual.getErrorTag()); + assertNotNull("getErrorMessage is null", actual.getErrorMessage()); + + if (expErrorMsg.isPresent()) { + assertEquals("getErrorMessage", expErrorMsg.get(), actual.getErrorMessage()); + } + + if (expAppTag.isPresent()) { + assertEquals("getErrorAppTag", expAppTag.get(), actual.getErrorAppTag()); + } + } + + @Test + public void testInvokeRpcWithNoPayloadRpc_FailWithRpcError() { + final List<RpcError> rpcErrors = List.of( + RpcResultBuilder.newError(ErrorType.TRANSPORT, new ErrorTag("bogusTag"), "foo"), + RpcResultBuilder.newWarning(ErrorType.RPC, ErrorTag.IN_USE, "bar", "app-tag", null, null)); + + final DOMRpcResult result = new DefaultDOMRpcResult(rpcErrors); + final QName path = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast"); + doReturn(immediateFluentFuture(result)).when(brokerFacade).invokeRpc(eq(path), any()); + + final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class, + () -> restconfImpl.invokeRpc("toaster:cancel-toast", null, uriInfo)); + + // We are performing pass-through here of error-tag, hence the tag remains as specified, but we want to make + // sure the HTTP status remains the same as + final ErrorTag bogus = new ErrorTag("bogusTag"); + verifyRestconfDocumentedException(ex, 0, ErrorType.TRANSPORT, bogus, Optional.of("foo"), Optional.empty()); + assertEquals(ErrorTags.statusOf(ErrorTag.OPERATION_FAILED), ErrorTags.statusOf(bogus)); + + verifyRestconfDocumentedException(ex, 1, ErrorType.RPC, ErrorTag.IN_USE, Optional.of("bar"), + Optional.of("app-tag")); + } + + @Test + public void testInvokeRpcWithNoPayload_Success() { + final NormalizedNode resultObj = null; + final DOMRpcResult expResult = new DefaultDOMRpcResult(resultObj); + + final QName qname = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast"); + + doReturn(immediateFluentFuture(expResult)).when(brokerFacade).invokeRpc(eq(qname), any()); + + final NormalizedNodeContext output = restconfImpl.invokeRpc("toaster:cancel-toast", null, uriInfo); + assertNotNull(output); + assertEquals(null, output.getData()); + // additional validation in the fact that the restconfImpl does not + // throw an exception. + } + + @Test + public void testInvokeRpcWithEmptyOutput() { + final ContainerNode resultObj = mock(ContainerNode.class); + doReturn(Set.of()).when(resultObj).body(); + doCallRealMethod().when(resultObj).isEmpty(); + final DOMRpcResult expResult = new DefaultDOMRpcResult(resultObj); + + final QName qname = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast"); + doReturn(immediateFluentFuture(expResult)).when(brokerFacade).invokeRpc(eq(qname), any()); + + WebApplicationException exceptionToBeThrown = assertThrows(WebApplicationException.class, + () -> restconfImpl.invokeRpc("toaster:cancel-toast", null, uriInfo)); + assertEquals(Response.Status.NO_CONTENT.getStatusCode(), exceptionToBeThrown.getResponse().getStatus()); + } + + @Test + public void testInvokeRpcMethodWithBadMethodName() { + final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class, + () -> restconfImpl.invokeRpc("toaster:bad-method", null, uriInfo)); + verifyRestconfDocumentedException(ex, 0, ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT, + Optional.empty(), Optional.empty()); + } + + @Test + @Ignore + public void testInvokeRpcMethodWithInput() { + final DOMRpcResult expResult = mock(DOMRpcResult.class); + final QName path = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)make-toast"); + + final Module rpcModule = schemaContext.findModules("toaster").iterator().next(); + assertNotNull(rpcModule); + final QName rpcQName = QName.create(rpcModule.getQNameModule(), "make-toast"); + + RpcDefinition rpcDef = null; + for (final RpcDefinition rpc : rpcModule.getRpcs()) { + if (rpcQName.isEqualWithoutRevision(rpc.getQName())) { + rpcDef = rpc; + break; + } + } + + assertNotNull(rpcDef); + + final NormalizedNodeContext payload = new NormalizedNodeContext( + InstanceIdentifierContext.ofLocalRpcInput(schemaContext, rpcDef), + SchemaAwareBuilders.containerBuilder(rpcDef.getInput()).build()); + + doReturn(immediateFluentFuture(expResult)).when(brokerFacade).invokeRpc(eq(path), any(NormalizedNode.class)); + + final NormalizedNodeContext output = restconfImpl.invokeRpc("toaster:make-toast", payload, uriInfo); + assertNotNull(output); + assertEquals(null, output.getData()); + // additional validation in the fact that the restconfImpl does not + // throw an exception. + } + + @Test + public void testThrowExceptionWhenSlashInModuleName() { + final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class, + () -> restconfImpl.invokeRpc("toaster/slash", null, uriInfo)); + verifyRestconfDocumentedException(ex, 0, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, + Optional.empty(), Optional.empty()); + } + + @Test + public void testInvokeRpcWithNoPayloadWithOutput_Success() { + final SchemaContext schema = controllerContext.getGlobalSchema(); + final Module rpcModule = schema.findModules("toaster").iterator().next(); + assertNotNull(rpcModule); + final QName rpcQName = QName.create(rpcModule.getQNameModule(), "testOutput"); + final QName rpcOutputQName = QName.create(rpcModule.getQNameModule(),"output"); + + RpcDefinition rpcDef = null; + ContainerLike rpcOutputSchemaNode = null; + for (final RpcDefinition rpc : rpcModule.getRpcs()) { + if (rpcQName.isEqualWithoutRevision(rpc.getQName())) { + rpcOutputSchemaNode = rpc.getOutput(); + rpcDef = rpc; + break; + } + } + assertNotNull(rpcDef); + assertNotNull(rpcOutputSchemaNode); + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> containerBuilder = + SchemaAwareBuilders.containerBuilder(rpcOutputSchemaNode); + final DataSchemaNode leafSchema = rpcOutputSchemaNode + .getDataChildByName(QName.create(rpcModule.getQNameModule(), "textOut")); + assertTrue(leafSchema instanceof LeafSchemaNode); + final NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>> leafBuilder = + SchemaAwareBuilders.leafBuilder((LeafSchemaNode) leafSchema); + leafBuilder.withValue("brm"); + containerBuilder.withChild(leafBuilder.build()); + final ContainerNode container = containerBuilder.build(); + + final DOMRpcResult result = new DefaultDOMRpcResult(container); + + doReturn(immediateFluentFuture(result)).when(brokerFacade).invokeRpc(eq(rpcDef.getQName()), any()); + + final NormalizedNodeContext output = restconfImpl.invokeRpc("toaster:testOutput", null, uriInfo); + assertNotNull(output); + assertNotNull(output.getData()); + assertSame(container, output.getData()); + assertNotNull(output.getInstanceIdentifierContext()); + assertNotNull(output.getInstanceIdentifierContext().getSchemaContext()); + } + + /** + * Tests calling of RestConfImpl method invokeRpc. In the method there is searched rpc in remote schema context. + * This rpc is then executed. + * I wasn't able to simulate calling of rpc on remote device therefore this testing method raise method when rpc is + * invoked. + */ + @Test + public void testMountedRpcCallNoPayload_Success() throws Exception { + // FIXME find how to use mockito for it + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/JSONRestconfServiceImplTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/JSONRestconfServiceImplTest.java new file mode 100644 index 0000000..48db5de --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/JSONRestconfServiceImplTest.java @@ -0,0 +1,532 @@ +/* + * Copyright (c) 2015 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.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFailedFluentFuture; +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture; + +import com.google.common.io.Resources; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Optional; +import javax.ws.rs.core.Response.Status; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.common.api.CommitInfo; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.common.api.TransactionCommitFailedException; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.mdsal.dom.api.DOMRpcException; +import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException; +import org.opendaylight.mdsal.dom.api.DOMRpcResult; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult; +import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService; +import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.JSONRestconfServiceImpl; +import org.opendaylight.netconf.sal.restconf.impl.PutResult; +import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl; +import org.opendaylight.restconf.common.patch.PatchContext; +import org.opendaylight.restconf.common.patch.PatchStatusContext; +import org.opendaylight.restconf.common.patch.PatchStatusEntity; +import org.opendaylight.yangtools.yang.common.OperationFailedException; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.Uint32; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +/** + * Unit tests for JSONRestconfServiceImpl. + * + * @author Thomas Pantelis + */ +@Deprecated +public class JSONRestconfServiceImplTest { + static final String IETF_INTERFACES_NS = "urn:ietf:params:xml:ns:yang:ietf-interfaces"; + static final String IETF_INTERFACES_VERSION = "2013-07-04"; + static final QName INTERFACES_QNAME = QName.create(IETF_INTERFACES_NS, IETF_INTERFACES_VERSION, "interfaces"); + static final QName INTERFACE_QNAME = QName.create(IETF_INTERFACES_NS, IETF_INTERFACES_VERSION, "interface"); + static final QName NAME_QNAME = QName.create(IETF_INTERFACES_NS, IETF_INTERFACES_VERSION, "name"); + static final QName TYPE_QNAME = QName.create(IETF_INTERFACES_NS, IETF_INTERFACES_VERSION, "type"); + static final QName ENABLED_QNAME = QName.create(IETF_INTERFACES_NS, IETF_INTERFACES_VERSION, "enabled"); + static final QName DESC_QNAME = QName.create(IETF_INTERFACES_NS, IETF_INTERFACES_VERSION, "description"); + + static final String TEST_MODULE_NS = "test:module"; + static final String TEST_MODULE_VERSION = "2014-01-09"; + static final QName TEST_CONT_QNAME = QName.create(TEST_MODULE_NS, TEST_MODULE_VERSION, "cont"); + static final QName TEST_CONT1_QNAME = QName.create(TEST_MODULE_NS, TEST_MODULE_VERSION, "cont1"); + static final QName TEST_LF11_QNAME = QName.create(TEST_MODULE_NS, TEST_MODULE_VERSION, "lf11"); + static final QName TEST_LF12_QNAME = QName.create(TEST_MODULE_NS, TEST_MODULE_VERSION, "lf12"); + + static final String TOASTER_MODULE_NS = "http://netconfcentral.org/ns/toaster"; + static final String TOASTER_MODULE_VERSION = "2009-11-20"; + static final QName TOASTER_DONENESS_QNAME = + QName.create(TOASTER_MODULE_NS, TOASTER_MODULE_VERSION, "toasterDoneness"); + static final QName TOASTER_TYPE_QNAME = QName.create(TOASTER_MODULE_NS, TOASTER_MODULE_VERSION, "toasterToastType"); + static final QName WHEAT_BREAD_QNAME = QName.create(TOASTER_MODULE_NS, TOASTER_MODULE_VERSION, "wheat-bread"); + static final QName MAKE_TOAST_QNAME = QName.create(TOASTER_MODULE_NS, TOASTER_MODULE_VERSION, "make-toast"); + static final QName CANCEL_TOAST_QNAME = QName.create(TOASTER_MODULE_NS, TOASTER_MODULE_VERSION, "cancel-toast"); + static final QName TEST_OUTPUT_QNAME = QName.create(TOASTER_MODULE_NS, TOASTER_MODULE_VERSION, "testOutput"); + static final QName TEXT_OUT_QNAME = QName.create(TOASTER_MODULE_NS, TOASTER_MODULE_VERSION, "textOut"); + + private static EffectiveModelContext schemaContext; + + private final BrokerFacade brokerFacade = mock(BrokerFacade.class); + private final DOMMountPoint mockMountPoint = mock(DOMMountPoint.class); + private JSONRestconfServiceImpl service; + + @BeforeClass + public static void init() throws IOException, ReactorException { + schemaContext = TestUtils.loadSchemaContext("/full-versions/yangs"); + } + + @Before + public void setup() throws FileNotFoundException { + final EffectiveModelContext mountPointSchemaContext = TestUtils.loadSchemaContext("/full-versions/test-module"); + final ControllerContext controllerContext = + TestRestconfUtils.newControllerContext(schemaContext, mockMountPoint); + doReturn(java.util.Optional.of(FixedDOMSchemaService.of(() -> mountPointSchemaContext))).when(mockMountPoint) + .getService(eq(DOMSchemaService.class)); + + service = new JSONRestconfServiceImpl(controllerContext, + RestconfImpl.newInstance(brokerFacade, controllerContext)); + } + + private static String loadData(final String path) throws IOException { + return Resources.asCharSource(JSONRestconfServiceImplTest.class.getResource(path), + StandardCharsets.UTF_8).read(); + } + + @Test + public void testPut() throws Exception { + final PutResult result = mock(PutResult.class); + when(brokerFacade.commitConfigurationDataPut(any(EffectiveModelContext.class), + any(YangInstanceIdentifier.class), any(NormalizedNode.class), isNull(), isNull())) + .thenReturn(result); + doReturn(CommitInfo.emptyFluentFuture()).when(result).getFutureOfPutData(); + when(result.getStatus()).thenReturn(Status.OK); + final String uriPath = "ietf-interfaces:interfaces/interface/eth0"; + final String payload = loadData("/parts/ietf-interfaces_interfaces.json"); + service.put(uriPath, payload); + + final ArgumentCaptor<YangInstanceIdentifier> capturedPath = + ArgumentCaptor.forClass(YangInstanceIdentifier.class); + final ArgumentCaptor<NormalizedNode> capturedNode = ArgumentCaptor.forClass(NormalizedNode.class); + verify(brokerFacade).commitConfigurationDataPut(any(EffectiveModelContext.class), capturedPath.capture(), + capturedNode.capture(), isNull(), isNull()); + + verifyPath(capturedPath.getValue(), INTERFACES_QNAME, INTERFACE_QNAME, + new Object[]{INTERFACE_QNAME, NAME_QNAME, "eth0"}); + + assertTrue("Expected MapEntryNode. Actual " + capturedNode.getValue().getClass(), + capturedNode.getValue() instanceof MapEntryNode); + final MapEntryNode actualNode = (MapEntryNode) capturedNode.getValue(); + assertEquals("MapEntryNode node type", INTERFACE_QNAME, actualNode.getIdentifier().getNodeType()); + verifyLeafNode(actualNode, NAME_QNAME, "eth0"); + verifyLeafNode(actualNode, TYPE_QNAME, "ethernetCsmacd"); + verifyLeafNode(actualNode, ENABLED_QNAME, Boolean.FALSE); + verifyLeafNode(actualNode, DESC_QNAME, "some interface"); + } + + @Test + public void testPutBehindMountPoint() throws Exception { + final PutResult result = mock(PutResult.class); + when(brokerFacade.commitMountPointDataPut(any(DOMMountPoint.class), + any(YangInstanceIdentifier.class), any(NormalizedNode.class), isNull(), isNull())) + .thenReturn(result); + doReturn(CommitInfo.emptyFluentFuture()).when(result).getFutureOfPutData(); + when(result.getStatus()).thenReturn(Status.OK); + final String uriPath = "ietf-interfaces:interfaces/yang-ext:mount/test-module:cont/cont1"; + final String payload = loadData("/full-versions/testCont1Data.json"); + + service.put(uriPath, payload); + + final ArgumentCaptor<YangInstanceIdentifier> capturedPath = + ArgumentCaptor.forClass(YangInstanceIdentifier.class); + final ArgumentCaptor<NormalizedNode> capturedNode = ArgumentCaptor.forClass(NormalizedNode.class); + verify(brokerFacade).commitMountPointDataPut(same(mockMountPoint), capturedPath.capture(), + capturedNode.capture(), isNull(), isNull()); + + verifyPath(capturedPath.getValue(), TEST_CONT_QNAME, TEST_CONT1_QNAME); + + assertTrue("Expected ContainerNode", capturedNode.getValue() instanceof ContainerNode); + final ContainerNode actualNode = (ContainerNode) capturedNode.getValue(); + assertEquals("ContainerNode node type", TEST_CONT1_QNAME, actualNode.getIdentifier().getNodeType()); + verifyLeafNode(actualNode, TEST_LF11_QNAME, "lf11 data"); + verifyLeafNode(actualNode, TEST_LF12_QNAME, "lf12 data"); + } + + @Test(expected = OperationFailedException.class) + @SuppressWarnings("checkstyle:IllegalThrows") + public void testPutFailure() throws Throwable { + final PutResult result = mock(PutResult.class); + + doReturn(immediateFailedFluentFuture(new TransactionCommitFailedException("mock"))).when(result) + .getFutureOfPutData(); + when(result.getStatus()).thenReturn(Status.OK); + when(brokerFacade.commitConfigurationDataPut(any(EffectiveModelContext.class), + any(YangInstanceIdentifier.class), any(NormalizedNode.class), anyString(), + anyString())).thenReturn(result); + + final String uriPath = "ietf-interfaces:interfaces/interface/eth0"; + final String payload = loadData("/parts/ietf-interfaces_interfaces.json"); + + service.put(uriPath, payload); + } + + @Test + public void testPost() throws Exception { + doReturn(CommitInfo.emptyFluentFuture()).when(brokerFacade).commitConfigurationDataPost( + any(EffectiveModelContext.class), any(YangInstanceIdentifier.class), any(NormalizedNode.class), + isNull(), isNull()); + + final String uriPath = null; + final String payload = loadData("/parts/ietf-interfaces_interfaces_absolute_path.json"); + + service.post(uriPath, payload); + + final ArgumentCaptor<YangInstanceIdentifier> capturedPath = + ArgumentCaptor.forClass(YangInstanceIdentifier.class); + final ArgumentCaptor<NormalizedNode> capturedNode = ArgumentCaptor.forClass(NormalizedNode.class); + verify(brokerFacade).commitConfigurationDataPost(any(EffectiveModelContext.class), capturedPath.capture(), + capturedNode.capture(), isNull(), isNull()); + + verifyPath(capturedPath.getValue(), INTERFACES_QNAME); + + assertTrue("Expected ContainerNode", capturedNode.getValue() instanceof ContainerNode); + final ContainerNode actualNode = (ContainerNode) capturedNode.getValue(); + assertEquals("ContainerNode node type", INTERFACES_QNAME, actualNode.getIdentifier().getNodeType()); + + final java.util.Optional<DataContainerChild> mapChild = actualNode.findChildByArg( + new NodeIdentifier(INTERFACE_QNAME)); + assertEquals(INTERFACE_QNAME.toString() + " present", true, mapChild.isPresent()); + assertTrue("Expected MapNode. Actual " + mapChild.get().getClass(), mapChild.get() instanceof MapNode); + final MapNode mapNode = (MapNode)mapChild.get(); + + final NodeIdentifierWithPredicates entryNodeID = NodeIdentifierWithPredicates.of( + INTERFACE_QNAME, NAME_QNAME, "eth0"); + final java.util.Optional<MapEntryNode> entryChild = mapNode.findChildByArg(entryNodeID); + assertEquals(entryNodeID.toString() + " present", true, entryChild.isPresent()); + final MapEntryNode entryNode = entryChild.get(); + verifyLeafNode(entryNode, NAME_QNAME, "eth0"); + verifyLeafNode(entryNode, TYPE_QNAME, "ethernetCsmacd"); + verifyLeafNode(entryNode, ENABLED_QNAME, Boolean.FALSE); + verifyLeafNode(entryNode, DESC_QNAME, "some interface"); + } + + @Test + public void testPostBehindMountPoint() throws Exception { + doReturn(CommitInfo.emptyFluentFuture()).when(brokerFacade).commitConfigurationDataPost( + any(DOMMountPoint.class), any(YangInstanceIdentifier.class), any(NormalizedNode.class), + isNull(), isNull()); + + final String uriPath = "ietf-interfaces:interfaces/yang-ext:mount/test-module:cont"; + final String payload = loadData("/full-versions/testCont1Data.json"); + + service.post(uriPath, payload); + + final ArgumentCaptor<YangInstanceIdentifier> capturedPath = + ArgumentCaptor.forClass(YangInstanceIdentifier.class); + final ArgumentCaptor<NormalizedNode> capturedNode = ArgumentCaptor.forClass(NormalizedNode.class); + verify(brokerFacade).commitConfigurationDataPost(same(mockMountPoint), capturedPath.capture(), + capturedNode.capture(), isNull(), isNull()); + + verifyPath(capturedPath.getValue(), TEST_CONT_QNAME, TEST_CONT1_QNAME); + + assertTrue("Expected ContainerNode", capturedNode.getValue() instanceof ContainerNode); + final ContainerNode actualNode = (ContainerNode) capturedNode.getValue(); + assertEquals("ContainerNode node type", TEST_CONT1_QNAME, actualNode.getIdentifier().getNodeType()); + verifyLeafNode(actualNode, TEST_LF11_QNAME, "lf11 data"); + verifyLeafNode(actualNode, TEST_LF12_QNAME, "lf12 data"); + } + + @Test(expected = TransactionCommitFailedException.class) + @SuppressWarnings({ "checkstyle:IllegalThrows", "checkstyle:avoidHidingCauseException" }) + public void testPostFailure() throws Throwable { + doReturn(immediateFailedFluentFuture(new TransactionCommitFailedException("mock"))).when(brokerFacade) + .commitConfigurationDataPost(any(EffectiveModelContext.class), any(YangInstanceIdentifier.class), + any(NormalizedNode.class), isNull(), isNull()); + + final String uriPath = null; + final String payload = loadData("/parts/ietf-interfaces_interfaces_absolute_path.json"); + + try { + service.post(uriPath, payload); + } catch (final OperationFailedException e) { + assertNotNull(e.getCause()); + throw e.getCause(); + } + } + + @Test + public void testPatch() throws Exception { + final PatchStatusContext result = mock(PatchStatusContext.class); + when(brokerFacade.patchConfigurationDataWithinTransaction(any(PatchContext.class))) + .thenReturn(result); + + when(result.getEditCollection()).thenReturn(List.of(new PatchStatusEntity("edit1", true, null))); + when(result.getGlobalErrors()).thenReturn(List.of()); + when(result.getPatchId()).thenReturn("1"); + final String uriPath = "ietf-interfaces:interfaces/interface/eth0"; + final String payload = loadData("/parts/ietf-interfaces_interfaces_patch.json"); + final Optional<String> patchResult = service.patch(uriPath, payload); + + assertTrue(patchResult.get().contains("\"ok\":[null]")); + } + + @Test + public void testPatchBehindMountPoint() throws Exception { + final PatchStatusContext result = mock(PatchStatusContext.class); + when(brokerFacade.patchConfigurationDataWithinTransaction(any(PatchContext.class))).thenReturn(result); + + when(result.getEditCollection()).thenReturn(List.of(new PatchStatusEntity("edit1", true, null))); + when(result.getGlobalErrors()).thenReturn(List.of()); + when(result.getPatchId()).thenReturn("1"); + + final String uriPath = "ietf-interfaces:interfaces/yang-ext:mount/test-module:cont/cont1"; + final String payload = loadData("/full-versions/testCont1DataPatch.json"); + + final Optional<String> patchResult = service.patch(uriPath, payload); + + assertTrue(patchResult.get().contains("\"ok\":[null]")); + } + + @Test(expected = OperationFailedException.class) + @SuppressWarnings("checkstyle:IllegalThrows") + public void testPatchFailure() throws Throwable { + final PatchStatusContext result = mock(PatchStatusContext.class); + when(brokerFacade.patchConfigurationDataWithinTransaction(any(PatchContext.class))) + .thenThrow(new TransactionCommitFailedException("Transaction failed")); + + final String uriPath = "ietf-interfaces:interfaces/interface/eth0"; + final String payload = loadData("/parts/ietf-interfaces_interfaces_patch.json"); + + final Optional<String> patchResult = service.patch(uriPath, payload); + + assertTrue("Patch output is not null", patchResult.isPresent()); + String patch = patchResult.get(); + assertTrue(patch.contains("TransactionCommitFailedException")); + } + + @Test + public void testDelete() throws Exception { + doReturn(CommitInfo.emptyFluentFuture()).when(brokerFacade) + .commitConfigurationDataDelete(any(YangInstanceIdentifier.class)); + + final String uriPath = "ietf-interfaces:interfaces/interface/eth0"; + + service.delete(uriPath); + + final ArgumentCaptor<YangInstanceIdentifier> capturedPath = + ArgumentCaptor.forClass(YangInstanceIdentifier.class); + verify(brokerFacade).commitConfigurationDataDelete(capturedPath.capture()); + + verifyPath(capturedPath.getValue(), INTERFACES_QNAME, INTERFACE_QNAME, + new Object[]{INTERFACE_QNAME, NAME_QNAME, "eth0"}); + } + + @Test(expected = OperationFailedException.class) + public void testDeleteFailure() throws Exception { + final String invalidUriPath = "ietf-interfaces:interfaces/invalid"; + + service.delete(invalidUriPath); + } + + @Test + public void testGetConfig() throws Exception { + testGet(LogicalDatastoreType.CONFIGURATION); + } + + @Test + public void testGetOperational() throws Exception { + testGet(LogicalDatastoreType.OPERATIONAL); + } + + @Test + public void testGetWithNoData() throws OperationFailedException { + doReturn(null).when(brokerFacade).readConfigurationData(any(YangInstanceIdentifier.class), anyString()); + final String uriPath = "ietf-interfaces:interfaces"; + service.get(uriPath, LogicalDatastoreType.CONFIGURATION); + } + + @Test(expected = OperationFailedException.class) + public void testGetFailure() throws Exception { + final String invalidUriPath = "/ietf-interfaces:interfaces/invalid"; + service.get(invalidUriPath, LogicalDatastoreType.CONFIGURATION); + } + + @Test + public void testInvokeRpcWithInput() throws Exception { + final DOMRpcResult expResult = new DefaultDOMRpcResult((NormalizedNode)null); + doReturn(immediateFluentFuture(expResult)).when(brokerFacade).invokeRpc(eq(MAKE_TOAST_QNAME), + any(NormalizedNode.class)); + + final String uriPath = "toaster:make-toast"; + final String input = loadData("/full-versions/make-toast-rpc-input.json"); + + final Optional<String> output = service.invokeRpc(uriPath, Optional.of(input)); + + assertEquals("Output present", false, output.isPresent()); + + final ArgumentCaptor<NormalizedNode> capturedNode = ArgumentCaptor.forClass(NormalizedNode.class); + verify(brokerFacade).invokeRpc(eq(MAKE_TOAST_QNAME), capturedNode.capture()); + + assertTrue("Expected ContainerNode. Actual " + capturedNode.getValue().getClass(), + capturedNode.getValue() instanceof ContainerNode); + final ContainerNode actualNode = (ContainerNode) capturedNode.getValue(); + verifyLeafNode(actualNode, TOASTER_DONENESS_QNAME, Uint32.valueOf(10)); + verifyLeafNode(actualNode, TOASTER_TYPE_QNAME, WHEAT_BREAD_QNAME); + } + + @Test + public void testInvokeRpcWithNoInput() throws Exception { + final DOMRpcResult expResult = new DefaultDOMRpcResult((NormalizedNode)null); + doReturn(immediateFluentFuture(expResult)).when(brokerFacade).invokeRpc(any(QName.class), any()); + + final String uriPath = "toaster:cancel-toast"; + + final Optional<String> output = service.invokeRpc(uriPath, Optional.empty()); + + assertEquals("Output present", false, output.isPresent()); + + verify(brokerFacade).invokeRpc(eq(CANCEL_TOAST_QNAME), any()); + } + + @Test + public void testInvokeRpcWithOutput() throws Exception { + final NormalizedNode outputNode = ImmutableContainerNodeBuilder.create() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TEST_OUTPUT_QNAME)) + .withChild(ImmutableNodes.leafNode(TEXT_OUT_QNAME, "foo")).build(); + final DOMRpcResult expResult = new DefaultDOMRpcResult(outputNode); + doReturn(immediateFluentFuture(expResult)).when(brokerFacade).invokeRpc(any(QName.class), any()); + + final String uriPath = "toaster:testOutput"; + + final Optional<String> output = service.invokeRpc(uriPath, Optional.empty()); + + assertEquals("Output present", true, output.isPresent()); + assertNotNull("Returned null response", output.get()); + assertThat("Missing \"textOut\"", output.get(), containsString("\"textOut\":\"foo\"")); + + verify(brokerFacade).invokeRpc(eq(TEST_OUTPUT_QNAME), any()); + } + + @Test(expected = OperationFailedException.class) + public void testInvokeRpcFailure() throws Exception { + final DOMRpcException exception = new DOMRpcImplementationNotAvailableException("testExeption"); + doReturn(immediateFailedFluentFuture(exception)).when(brokerFacade).invokeRpc(any(QName.class), + any(NormalizedNode.class)); + + final String uriPath = "toaster:cancel-toast"; + + service.invokeRpc(uriPath, Optional.empty()); + } + + void testGet(final LogicalDatastoreType datastoreType) throws OperationFailedException { + final MapEntryNode entryNode = ImmutableNodes.mapEntryBuilder(INTERFACE_QNAME, NAME_QNAME, "eth0") + .withChild(ImmutableNodes.leafNode(NAME_QNAME, "eth0")) + .withChild(ImmutableNodes.leafNode(TYPE_QNAME, "ethernetCsmacd")) + .withChild(ImmutableNodes.leafNode(ENABLED_QNAME, Boolean.TRUE)) + .withChild(ImmutableNodes.leafNode(DESC_QNAME, "eth interface")) + .build(); + + if (datastoreType == LogicalDatastoreType.CONFIGURATION) { + doReturn(entryNode).when(brokerFacade).readConfigurationData(any(YangInstanceIdentifier.class), + isNull()); + } else { + doReturn(entryNode).when(brokerFacade).readOperationalData(any(YangInstanceIdentifier.class)); + } + + final String uriPath = "/ietf-interfaces:interfaces/interface/eth0"; + + final Optional<String> optionalResp = service.get(uriPath, datastoreType); + assertEquals("Response present", true, optionalResp.isPresent()); + final String jsonResp = optionalResp.get(); + + assertNotNull("Returned null response", jsonResp); + assertThat("Missing \"name\"", jsonResp, containsString("\"name\":\"eth0\"")); + assertThat("Missing \"type\"", jsonResp, containsString("\"type\":\"ethernetCsmacd\"")); + assertThat("Missing \"enabled\"", jsonResp, containsString("\"enabled\":true")); + assertThat("Missing \"description\"", jsonResp, containsString("\"description\":\"eth interface\"")); + + final ArgumentCaptor<YangInstanceIdentifier> capturedPath = + ArgumentCaptor.forClass(YangInstanceIdentifier.class); + if (datastoreType == LogicalDatastoreType.CONFIGURATION) { + verify(brokerFacade).readConfigurationData(capturedPath.capture(), isNull()); + } else { + verify(brokerFacade).readOperationalData(capturedPath.capture()); + } + + verifyPath(capturedPath.getValue(), INTERFACES_QNAME, INTERFACE_QNAME, + new Object[]{INTERFACE_QNAME, NAME_QNAME, "eth0"}); + } + + void verifyLeafNode(final DataContainerNode parent, final QName leafType, final Object leafValue) { + final java.util.Optional<DataContainerChild> leafChild = parent.findChildByArg(new NodeIdentifier(leafType)); + assertTrue(leafType.toString() + " present", leafChild.isPresent()); + assertEquals(leafType.toString() + " value", leafValue, leafChild.get().body()); + } + + void verifyPath(final YangInstanceIdentifier path, final Object... expArgs) { + final List<PathArgument> pathArgs = path.getPathArguments(); + assertEquals("Arg count for actual path " + path, expArgs.length, pathArgs.size()); + int index = 0; + for (final PathArgument actual: pathArgs) { + QName expNodeType; + if (expArgs[index] instanceof Object[]) { + final Object[] listEntry = (Object[]) expArgs[index]; + expNodeType = (QName) listEntry[0]; + + assertTrue(actual instanceof NodeIdentifierWithPredicates); + final NodeIdentifierWithPredicates nip = (NodeIdentifierWithPredicates)actual; + assertEquals(String.format("Path arg %d keyValues size", index + 1), 1, nip.size()); + final QName expKey = (QName) listEntry[1]; + assertEquals(String.format("Path arg %d keyValue for %s", index + 1, expKey), listEntry[2], + nip.getValue(expKey)); + } else { + expNodeType = (QName) expArgs[index]; + } + + assertEquals(String.format("Path arg %d node type", index + 1), expNodeType, actual.getNodeType()); + index++; + } + + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/MediaTypesTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/MediaTypesTest.java new file mode 100644 index 0000000..2bddec2 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/MediaTypesTest.java @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2014 Cisco 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.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.opendaylight.controller.sal.restconf.impl.test.RestOperationUtils.JSON; +import static org.opendaylight.controller.sal.restconf.impl.test.RestOperationUtils.XML; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; +import org.opendaylight.netconf.sal.rest.api.Draft02; +import org.opendaylight.netconf.sal.rest.api.RestconfService; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeJsonBodyWriter; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeXmlBodyWriter; + +public class MediaTypesTest extends JerseyTest { + + private static String jsonData; + private static String xmlData; + + private RestconfService restconfService; + + @BeforeClass + public static void init() throws IOException { + final String jsonPath = RestconfImplTest.class.getResource("/parts/ietf-interfaces_interfaces.json").getPath(); + jsonData = TestUtils.loadTextFile(jsonPath); + final InputStream xmlStream = + RestconfImplTest.class.getResourceAsStream("/parts/ietf-interfaces_interfaces.xml"); + xmlData = TestUtils.getDocumentInPrintableForm(TestUtils.loadDocumentFrom(xmlStream)); + } + + @Override + protected Application configure() { + /* enable/disable Jersey logs to console */ + // enable(TestProperties.LOG_TRAFFIC); + // enable(TestProperties.DUMP_ENTITY); + // enable(TestProperties.RECORD_LOG_LEVEL); + // set(TestProperties.RECORD_LOG_LEVEL, Level.ALL.intValue());' + restconfService = mock(RestconfService.class); + ResourceConfig resourceConfig = new ResourceConfig(); + resourceConfig = resourceConfig.registerInstances(restconfService, new NormalizedNodeJsonBodyWriter(), + new NormalizedNodeXmlBodyWriter()); + return resourceConfig; + } + + @Test + @Ignore + public void testPostOperationsWithInputDataMediaTypes() throws UnsupportedEncodingException { + final String uriPrefix = "/operations/"; + final String uriPath = "ietf-interfaces:interfaces"; + final String uri = uriPrefix + uriPath; + when(restconfService.invokeRpc(eq(uriPath), any(NormalizedNodeContext.class), any(UriInfo.class))) + .thenReturn(null); + post(uri, Draft02.MediaTypes.OPERATION + JSON, Draft02.MediaTypes.OPERATION + JSON, jsonData); + verify(restconfService, times(1)).invokeRpc(eq(uriPath), any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, Draft02.MediaTypes.OPERATION + XML, Draft02.MediaTypes.OPERATION + XML, xmlData); + verify(restconfService, times(2)).invokeRpc(eq(uriPath), any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON, jsonData); + verify(restconfService, times(3)).invokeRpc(eq(uriPath), any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, MediaType.APPLICATION_XML, MediaType.APPLICATION_XML, xmlData); + verify(restconfService, times(4)).invokeRpc(eq(uriPath), any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, MediaType.TEXT_XML, MediaType.TEXT_XML, xmlData); + verify(restconfService, times(5)).invokeRpc(eq(uriPath), any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, null, MediaType.TEXT_XML, xmlData); + verify(restconfService, times(6)).invokeRpc(eq(uriPath), any(NormalizedNodeContext.class), any(UriInfo.class)); + + // negative tests + post(uri, MediaType.TEXT_PLAIN, MediaType.TEXT_XML, xmlData); + verify(restconfService, times(6)).invokeRpc(eq(uriPath), any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, MediaType.TEXT_XML, MediaType.TEXT_PLAIN, xmlData); + verify(restconfService, times(6)).invokeRpc(eq(uriPath), any(NormalizedNodeContext.class), any(UriInfo.class)); + } + + @Test + public void testGetConfigMediaTypes() throws UnsupportedEncodingException { + final String uriPrefix = "/config/"; + final String uriPath = "ietf-interfaces:interfaces"; + final String uri = uriPrefix + uriPath; + when(restconfService.readConfigurationData(eq(uriPath), any(UriInfo.class))).thenReturn(null); + get(uri, Draft02.MediaTypes.DATA + JSON); + verify(restconfService, times(1)).readConfigurationData(eq(uriPath), any(UriInfo.class)); + get(uri, Draft02.MediaTypes.DATA + XML); + verify(restconfService, times(2)).readConfigurationData(eq(uriPath), any(UriInfo.class)); + get(uri, MediaType.APPLICATION_JSON); + verify(restconfService, times(3)).readConfigurationData(eq(uriPath), any(UriInfo.class)); + get(uri, MediaType.APPLICATION_XML); + verify(restconfService, times(4)).readConfigurationData(eq(uriPath), any(UriInfo.class)); + get(uri, MediaType.TEXT_XML); + verify(restconfService, times(5)).readConfigurationData(eq(uriPath), any(UriInfo.class)); + + // negative tests + get(uri, MediaType.TEXT_PLAIN); + verify(restconfService, times(5)).readConfigurationData(eq(uriPath), any(UriInfo.class)); + } + + @Test + public void testGetOperationalMediaTypes() throws UnsupportedEncodingException { + final String uriPrefix = "/operational/"; + final String uriPath = "ietf-interfaces:interfaces"; + final String uri = uriPrefix + uriPath; + when(restconfService.readOperationalData(eq(uriPath), any(UriInfo.class))).thenReturn(null); + get(uri, Draft02.MediaTypes.DATA + JSON); + verify(restconfService, times(1)).readOperationalData(eq(uriPath), any(UriInfo.class)); + get(uri, Draft02.MediaTypes.DATA + XML); + verify(restconfService, times(2)).readOperationalData(eq(uriPath), any(UriInfo.class)); + get(uri, MediaType.APPLICATION_JSON); + verify(restconfService, times(3)).readOperationalData(eq(uriPath), any(UriInfo.class)); + get(uri, MediaType.APPLICATION_XML); + verify(restconfService, times(4)).readOperationalData(eq(uriPath), any(UriInfo.class)); + get(uri, MediaType.TEXT_XML); + verify(restconfService, times(5)).readOperationalData(eq(uriPath), any(UriInfo.class)); + + // negative tests + get(uri, MediaType.TEXT_PLAIN); + verify(restconfService, times(5)).readOperationalData(eq(uriPath), any(UriInfo.class)); + } + + @Test + @Ignore + public void testPutConfigMediaTypes() throws UnsupportedEncodingException { + final String uriPrefix = "/config/"; + final String uriPath = "ietf-interfaces:interfaces"; + final String uri = uriPrefix + uriPath; + final UriInfo uriInfo = Mockito.mock(UriInfo.class); + when(restconfService.updateConfigurationData(eq(uriPath), any(NormalizedNodeContext.class), uriInfo)) + .thenReturn(null); + put(uri, null, Draft02.MediaTypes.DATA + JSON, jsonData); + verify(restconfService, times(1)).updateConfigurationData(eq(uriPath), any(NormalizedNodeContext.class), + uriInfo); + put(uri, null, Draft02.MediaTypes.DATA + XML, xmlData); + verify(restconfService, times(2)).updateConfigurationData(eq(uriPath), any(NormalizedNodeContext.class), + uriInfo); + put(uri, null, MediaType.APPLICATION_JSON, jsonData); + verify(restconfService, times(3)).updateConfigurationData(eq(uriPath), any(NormalizedNodeContext.class), + uriInfo); + put(uri, null, MediaType.APPLICATION_XML, xmlData); + verify(restconfService, times(4)).updateConfigurationData(eq(uriPath), any(NormalizedNodeContext.class), + uriInfo); + put(uri, null, MediaType.TEXT_XML, xmlData); + verify(restconfService, times(5)).updateConfigurationData(eq(uriPath), any(NormalizedNodeContext.class), + uriInfo); + put(uri, "fooMediaType", MediaType.TEXT_XML, xmlData); + verify(restconfService, times(6)).updateConfigurationData(eq(uriPath), any(NormalizedNodeContext.class), + uriInfo); + } + + @Test + @Ignore + public void testPostConfigWithPathMediaTypes() throws UnsupportedEncodingException { + final String uriPrefix = "/config/"; + final String uriPath = "ietf-interfaces:interfaces"; + final String uri = uriPrefix + uriPath; + when(restconfService.createConfigurationData(eq(uriPath), any(NormalizedNodeContext.class), + any(UriInfo.class))).thenReturn(null); + post(uri, null, Draft02.MediaTypes.DATA + JSON, jsonData); + verify(restconfService, times(1)).createConfigurationData(eq(uriPath), + any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, null, Draft02.MediaTypes.DATA + XML, xmlData); + verify(restconfService, times(2)).createConfigurationData(eq(uriPath), + any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, null, MediaType.APPLICATION_JSON, jsonData); + verify(restconfService, times(3)).createConfigurationData(eq(uriPath), + any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, null, MediaType.APPLICATION_XML, xmlData); + verify(restconfService, times(4)).createConfigurationData(eq(uriPath), + any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, null, MediaType.TEXT_XML, xmlData); + verify(restconfService, times(5)).createConfigurationData(eq(uriPath), + any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, "fooMediaType", MediaType.TEXT_XML, xmlData); + verify(restconfService, times(6)).createConfigurationData(eq(uriPath), + any(NormalizedNodeContext.class), any(UriInfo.class)); + } + + @Test + @Ignore + public void testPostConfigMediaTypes() throws UnsupportedEncodingException { + final String uriPrefix = "/config/"; + final String uri = uriPrefix; + when(restconfService.createConfigurationData(any(NormalizedNodeContext.class), + any(UriInfo.class))).thenReturn(null); + post(uri, null, Draft02.MediaTypes.DATA + JSON, jsonData); + verify(restconfService, times(1)).createConfigurationData( + any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, null, Draft02.MediaTypes.DATA + XML, xmlData); + verify(restconfService, times(2)).createConfigurationData( + any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, null, MediaType.APPLICATION_JSON, jsonData); + verify(restconfService, times(3)).createConfigurationData( + any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, null, MediaType.APPLICATION_XML, xmlData); + verify(restconfService, times(4)).createConfigurationData( + any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, null, MediaType.TEXT_XML, xmlData); + verify(restconfService, times(5)).createConfigurationData( + any(NormalizedNodeContext.class), any(UriInfo.class)); + post(uri, "fooMediaType", MediaType.TEXT_XML, xmlData); + verify(restconfService, times(6)).createConfigurationData( + any(NormalizedNodeContext.class), any(UriInfo.class)); + } + + @Test + public void testDeleteConfigMediaTypes() throws UnsupportedEncodingException { + final String uriPrefix = "/config/"; + final String uriPath = "ietf-interfaces:interfaces"; + final String uri = uriPrefix + uriPath; + when(restconfService.deleteConfigurationData(eq(uriPath))).thenReturn(null); + target(uri).request("fooMediaType").delete(); + verify(restconfService, times(1)).deleteConfigurationData(uriPath); + } + + private int get(final String uri, final String acceptMediaType) { + return target(uri).request(acceptMediaType).get().getStatus(); + } + + private int put(final String uri, final String acceptMediaType, final String contentTypeMediaType, + final String data) { + if (acceptMediaType == null) { + return target(uri).request().put(Entity.entity(data, contentTypeMediaType)).getStatus(); + } + return target(uri).request(acceptMediaType).put(Entity.entity(data, contentTypeMediaType)).getStatus(); + } + + private int post(final String uri, final String acceptMediaType, final String contentTypeMediaType, + final String data) { + if (acceptMediaType == null) { + if (contentTypeMediaType == null || data == null) { + return target(uri).request().post(null).getStatus(); + } + return target(uri).request().post(Entity.entity(data, contentTypeMediaType)).getStatus(); + } + if (contentTypeMediaType == null || data == null) { + return target(uri).request(acceptMediaType).post(null).getStatus(); + } + return target(uri).request(acceptMediaType).post(Entity.entity(data, contentTypeMediaType)).getStatus(); + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/NormalizeNodeTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/NormalizeNodeTest.java new file mode 100644 index 0000000..3af5f96 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/NormalizeNodeTest.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2014 Cisco 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 java.io.FileNotFoundException; +import org.junit.BeforeClass; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class NormalizeNodeTest extends YangAndXmlAndDataSchemaLoader { + + @BeforeClass + public static void initialization() throws FileNotFoundException, ReactorException { + dataLoad("/normalize-node/yang/"); + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestCodecExceptionsTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestCodecExceptionsTest.java new file mode 100644 index 0000000..bd2d48b --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestCodecExceptionsTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014 Cisco 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.assertNull; +import static org.mockito.Mockito.mock; + +import org.junit.Test; +import org.opendaylight.netconf.sal.restconf.impl.RestCodec; +import org.opendaylight.yangtools.concepts.IllegalArgumentCodec; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition; +import org.opendaylight.yangtools.yang.model.ri.type.BaseTypes; + +public class RestCodecExceptionsTest { + @Test + public void serializeExceptionTest() { + final IllegalArgumentCodec<Object, Object> codec = RestCodec.from( + BaseTypes.bitsTypeBuilder(QName.create("test", "2014-05-30", "test")).build(), null, null); + final String serializedValue = (String) codec.serialize("incorrect value"); // set + // expected + assertEquals("incorrect value", serializedValue); + } + + @Test + public void deserializeExceptionTest() { + final IdentityrefTypeDefinition mockedIidentityrefType = mock(IdentityrefTypeDefinition.class); + + final IllegalArgumentCodec<Object, Object> codec = RestCodec.from(mockedIidentityrefType, null, null); + assertNull(codec.deserialize("incorrect value")); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestDeleteOperationTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestDeleteOperationTest.java new file mode 100644 index 0000000..3ebbf28 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestDeleteOperationTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2014 Cisco 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.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFailedFluentFuture; + +import java.io.FileNotFoundException; +import java.io.UnsupportedEncodingException; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.common.api.CommitInfo; +import org.opendaylight.mdsal.common.api.TransactionCommitFailedException; +import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader; +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.BrokerFacade; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class RestDeleteOperationTest extends JerseyTest { + private static EffectiveModelContext schemaContext; + + private ControllerContext controllerContext; + private BrokerFacade brokerFacade; + private RestconfImpl restconfImpl; + + @BeforeClass + public static void init() throws FileNotFoundException, ReactorException { + schemaContext = TestUtils.loadSchemaContext("/test-config-data/yang1"); + } + + @Override + protected Application configure() { + /* enable/disable Jersey logs to console */ + // enable(TestProperties.LOG_TRAFFIC); + // enable(TestProperties.DUMP_ENTITY); + // enable(TestProperties.RECORD_LOG_LEVEL); + // set(TestProperties.RECORD_LOG_LEVEL, Level.ALL.intValue()); + controllerContext = TestRestconfUtils.newControllerContext(schemaContext); + controllerContext.setSchemas(schemaContext); + brokerFacade = mock(BrokerFacade.class); + restconfImpl = RestconfImpl.newInstance(brokerFacade, controllerContext); + + ResourceConfig resourceConfig = new ResourceConfig(); + resourceConfig = resourceConfig.registerInstances(restconfImpl, new NormalizedNodeJsonBodyWriter(), + new NormalizedNodeXmlBodyWriter(), new XmlNormalizedNodeBodyReader(controllerContext), + new JsonNormalizedNodeBodyReader(controllerContext), + new RestconfDocumentedExceptionMapper(controllerContext)); + return resourceConfig; + } + + @Test + public void deleteConfigStatusCodes() throws UnsupportedEncodingException { + final String uri = "/config/test-interface:interfaces"; + doReturn(CommitInfo.emptyFluentFuture()).when(brokerFacade) + .commitConfigurationDataDelete(any(YangInstanceIdentifier.class)); + Response response = target(uri).request(MediaType.APPLICATION_XML).delete(); + assertEquals(200, response.getStatus()); + + doThrow(RestconfDocumentedException.class).when(brokerFacade).commitConfigurationDataDelete( + any(YangInstanceIdentifier.class)); + response = target(uri).request(MediaType.APPLICATION_XML).delete(); + assertEquals(500, response.getStatus()); + } + + @Test + public void deleteFailTest() throws Exception { + final String uri = "/config/test-interface:interfaces"; + doReturn(immediateFailedFluentFuture(new TransactionCommitFailedException("failed test"))).when(brokerFacade) + .commitConfigurationDataDelete(any(YangInstanceIdentifier.class)); + final Response response = target(uri).request(MediaType.APPLICATION_XML).delete(); + assertEquals(500, response.getStatus()); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestGetAugmentedElementWhenEqualNamesTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestGetAugmentedElementWhenEqualNamesTest.java new file mode 100644 index 0000000..d0b30cb --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestGetAugmentedElementWhenEqualNamesTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2014 Cisco 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.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.io.FileNotFoundException; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.yangtools.yang.common.XMLNamespace; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; + +public class RestGetAugmentedElementWhenEqualNamesTest { + + private static EffectiveModelContext schemaContext; + + private final ControllerContext controllerContext = TestRestconfUtils.newControllerContext(schemaContext); + + @BeforeClass + public static void init() throws FileNotFoundException { + schemaContext = TestUtils.loadSchemaContext("/common/augment/yang"); + } + + @Test + public void augmentedNodesInUri() { + InstanceIdentifierContext iiWithData = + controllerContext.toInstanceIdentifier("main:cont/augment-main-a:cont1"); + assertEquals(XMLNamespace.of("ns:augment:main:a"), iiWithData.getSchemaNode().getQName().getNamespace()); + iiWithData = controllerContext.toInstanceIdentifier("main:cont/augment-main-b:cont1"); + assertEquals(XMLNamespace.of("ns:augment:main:b"), iiWithData.getSchemaNode().getQName().getNamespace()); + } + + @Test + public void nodeWithoutNamespaceHasMoreAugments() { + final var ex = assertThrows(RestconfDocumentedException.class, + () -> controllerContext.toInstanceIdentifier("main:cont/cont1")); + assertThat(ex.getErrors().get(0).getErrorMessage(), + containsString("is added as augment from more than one module")); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestGetOperationTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestGetOperationTest.java new file mode 100644 index 0000000..65fa8e0 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestGetOperationTest.java @@ -0,0 +1,742 @@ +/* + * Copyright (c) 2014 Cisco 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.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +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.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService; +import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader; +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.BrokerFacade; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +public class RestGetOperationTest extends JerseyTest { + + static class NodeData { + Object key; + Object data; // List for a CompositeNode, value Object for a SimpleNode + + NodeData(final Object key, final Object data) { + this.key = key; + this.data = data; + } + } + + private static EffectiveModelContext schemaContextYangsIetf; + private static EffectiveModelContext schemaContextTestModule; + private static EffectiveModelContext schemaContextModules; + private static EffectiveModelContext schemaContextBehindMountPoint; + + private static NormalizedNode answerFromGet; + + private BrokerFacade brokerFacade; + private RestconfImpl restconfImpl; + private ControllerContext controllerContext; + private DOMMountPoint mountInstance; + + private static final String RESTCONF_NS = "urn:ietf:params:xml:ns:yang:ietf-restconf"; + + @BeforeClass + public static void init() throws Exception { + schemaContextYangsIetf = TestUtils.loadSchemaContext("/full-versions/yangs"); + schemaContextTestModule = TestUtils.loadSchemaContext("/full-versions/test-module"); + schemaContextModules = TestUtils.loadSchemaContext("/modules"); + schemaContextBehindMountPoint = TestUtils.loadSchemaContext("/modules/modules-behind-mount-point"); + + answerFromGet = TestUtils.prepareNormalizedNodeWithIetfInterfacesInterfacesData(); + } + + @Override + protected Application configure() { + /* enable/disable Jersey logs to console */ + // enable(TestProperties.LOG_TRAFFIC); + // enable(TestProperties.DUMP_ENTITY); + // enable(TestProperties.RECORD_LOG_LEVEL); + // set(TestProperties.RECORD_LOG_LEVEL, Level.ALL.intValue()); + + mountInstance = mock(DOMMountPoint.class); + controllerContext = TestRestconfUtils.newControllerContext(schemaContextYangsIetf, mountInstance); + brokerFacade = mock(BrokerFacade.class); + restconfImpl = RestconfImpl.newInstance(brokerFacade, controllerContext); + + ResourceConfig resourceConfig = new ResourceConfig(); + resourceConfig = resourceConfig.registerInstances(restconfImpl, new NormalizedNodeJsonBodyWriter(), + new NormalizedNodeXmlBodyWriter(), new XmlNormalizedNodeBodyReader(controllerContext), + new JsonNormalizedNodeBodyReader(controllerContext), + new RestconfDocumentedExceptionMapper(controllerContext)); + return resourceConfig; + } + + private void setControllerContext(final EffectiveModelContext schemaContext) { + controllerContext.setSchemas(schemaContext); + } + + /** + * Tests of status codes for "/operational/{identifier}". + */ + @Test + public void getOperationalStatusCodes() throws Exception { + setControllerContext(schemaContextYangsIetf); + mockReadOperationalDataMethod(); + String uri = "/operational/ietf-interfaces:interfaces/interface/eth0"; + assertEquals(200, get(uri, MediaType.APPLICATION_XML)); + + uri = "/operational/wrong-module:interfaces/interface/eth0"; + assertEquals(400, get(uri, MediaType.APPLICATION_XML)); + } + + /** + * Tests of status codes for "/config/{identifier}". + */ + @Test + public void getConfigStatusCodes() throws Exception { + setControllerContext(schemaContextYangsIetf); + mockReadConfigurationDataMethod(); + String uri = "/config/ietf-interfaces:interfaces/interface/eth0"; + assertEquals(200, get(uri, MediaType.APPLICATION_XML)); + + uri = "/config/wrong-module:interfaces/interface/eth0"; + assertEquals(400, get(uri, MediaType.APPLICATION_XML)); + } + + /** + * MountPoint test. URI represents mount point. + */ + @Test + public void getDataWithUrlMountPoint() throws Exception { + when(brokerFacade.readConfigurationData(any(DOMMountPoint.class), any(YangInstanceIdentifier.class), + isNull())).thenReturn(prepareCnDataForMountPointTest(false)); + when(mountInstance.getService(DOMSchemaService.class)) + .thenReturn(Optional.of(FixedDOMSchemaService.of(schemaContextTestModule))); + + String uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/test-module:cont/cont1"; + assertEquals(200, get(uri, MediaType.APPLICATION_XML)); + + uri = "/config/ietf-interfaces:interfaces/yang-ext:mount/test-module:cont/cont1"; + assertEquals(200, get(uri, MediaType.APPLICATION_XML)); + } + + /** + * MountPoint test. URI represents mount point. + * Slashes in URI behind mount point. lst1 element with key GigabitEthernet0%2F0%2F0%2F0 (GigabitEthernet0/0/0/0) is + * requested via GET HTTP operation. It is tested whether %2F character is replaced with simple / in + * InstanceIdentifier parameter in method + * {@link BrokerFacade#readConfigurationData(DOMMountPoint, YangInstanceIdentifier)} which is called in + * method {@link RestconfImpl#readConfigurationData} + */ + @Test + public void getDataWithSlashesBehindMountPoint() throws Exception { + final YangInstanceIdentifier awaitedInstanceIdentifier = prepareInstanceIdentifierForList(); + when(brokerFacade.readConfigurationData(any(DOMMountPoint.class), eq(awaitedInstanceIdentifier), + isNull())).thenReturn(prepareCnDataForSlashesBehindMountPointTest()); + when(mountInstance.getService(DOMSchemaService.class)) + .thenReturn(Optional.of(FixedDOMSchemaService.of(schemaContextTestModule))); + + final String uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/" + + "test-module:cont/lst1/GigabitEthernet0%2F0%2F0%2F0"; + assertEquals(200, get(uri, MediaType.APPLICATION_XML)); + } + + private static YangInstanceIdentifier prepareInstanceIdentifierForList() throws Exception { + final List<PathArgument> parameters = new ArrayList<>(); + + final QName qNameCont = newTestModuleQName("cont"); + final QName qNameList = newTestModuleQName("lst1"); + final QName qNameKeyList = newTestModuleQName("lf11"); + + parameters.add(new NodeIdentifier(qNameCont)); + parameters.add(new NodeIdentifier(qNameList)); + parameters.add(NodeIdentifierWithPredicates.of(qNameList, qNameKeyList, "GigabitEthernet0/0/0/0")); + return YangInstanceIdentifier.create(parameters); + } + + private static QName newTestModuleQName(final String localPart) throws Exception { + return QName.create("test:module", "2014-01-09", localPart); + } + + @Test + public void getDataMountPointIntoHighestElement() throws Exception { + when(brokerFacade.readConfigurationData(any(DOMMountPoint.class), any(YangInstanceIdentifier.class), + isNull())).thenReturn(prepareCnDataForMountPointTest(true)); + when(mountInstance.getService(DOMSchemaService.class)) + .thenReturn(Optional.of(FixedDOMSchemaService.of(schemaContextTestModule))); + + final String uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/test-module:cont"; + assertEquals(200, get(uri, MediaType.APPLICATION_XML)); + } + + @Test + public void getDataWithIdentityrefInURL() throws Exception { + setControllerContext(schemaContextTestModule); + + final QName moduleQN = newTestModuleQName("module"); + final ImmutableMap<QName, Object> keyMap = ImmutableMap.of( + newTestModuleQName("type"), newTestModuleQName("test-identity"), + newTestModuleQName("name"), "foo"); + final YangInstanceIdentifier iid = YangInstanceIdentifier.builder().node(newTestModuleQName("modules")) + .node(moduleQN).nodeWithKey(moduleQN, keyMap).build(); + final NormalizedNode data = ImmutableMapNodeBuilder.create() + .withNodeIdentifier(new NodeIdentifier(moduleQN)) + .withChild(ImmutableNodes.mapEntryBuilder() + .withNodeIdentifier(NodeIdentifierWithPredicates.of(moduleQN, keyMap)) + .withChild(ImmutableNodes.leafNode(newTestModuleQName("type"), newTestModuleQName("test-identity"))) + .withChild(ImmutableNodes.leafNode(newTestModuleQName("name"), "foo")) + .withChild(ImmutableNodes.leafNode(newTestModuleQName("data"), "bar")).build()).build(); + when(brokerFacade.readConfigurationData(iid, null)).thenReturn(data); + + final String uri = "/config/test-module:modules/module/test-module:test-identity/foo"; + assertEquals(200, get(uri, MediaType.APPLICATION_XML)); + } + + // /modules + @Test + public void getModulesTest() throws Exception { + setControllerContext(schemaContextModules); + + final String uri = "/modules"; + + Response response = target(uri).request("application/yang.api+json").get(); + validateModulesResponseJson(response); + + response = target(uri).request("application/yang.api+xml").get(); + validateModulesResponseXml(response,schemaContextModules); + } + + // /streams/ + @Test + @Ignore // FIXME : find why it is fail by in gerrit build + public void getStreamsTest() throws Exception { + setControllerContext(schemaContextModules); + + final String uri = "/streams"; + + Response response = target(uri).request("application/yang.api+json").get(); + final String responseBody = response.readEntity(String.class); + assertEquals(200, response.getStatus()); + assertNotNull(responseBody); + assertTrue(responseBody.contains("streams")); + + response = target(uri).request("application/yang.api+xml").get(); + assertEquals(200, response.getStatus()); + final Document responseXmlBody = response.readEntity(Document.class); + assertNotNull(responseXmlBody); + final Element rootNode = responseXmlBody.getDocumentElement(); + + assertEquals("streams", rootNode.getLocalName()); + assertEquals(RESTCONF_NS, rootNode.getNamespaceURI()); + } + + // /modules/module + @Test + public void getModuleTest() throws Exception { + setControllerContext(schemaContextModules); + + final String uri = "/modules/module/module2/2014-01-02"; + + Response response = target(uri).request("application/yang.api+xml").get(); + assertEquals(200, response.getStatus()); + final Document responseXml = response.readEntity(Document.class); + + final QName qname = assertedModuleXmlToModuleQName(responseXml.getDocumentElement()); + assertNotNull(qname); + + assertEquals("module2", qname.getLocalName()); + assertEquals("module:2", qname.getNamespace().toString()); + assertEquals("2014-01-02", qname.getRevision().get().toString()); + + response = target(uri).request("application/yang.api+json").get(); + assertEquals(200, response.getStatus()); + final String responseBody = response.readEntity(String.class); + assertTrue("Module2 in json wasn't found", prepareJsonRegex("module2", "2014-01-02", "module:2", responseBody) + .find()); + final String[] split = responseBody.split("\"module\""); + assertEquals("\"module\" element is returned more then once", 2, split.length); + + } + + // /operations + @Ignore + @Test + public void getOperationsTest() throws Exception { + setControllerContext(schemaContextModules); + + final String uri = "/operations"; + + Response response = target(uri).request("application/yang.api+xml").get(); + assertEquals(500, response.getStatus()); + final Document responseDoc = response.readEntity(Document.class); + validateOperationsResponseXml(responseDoc, schemaContextModules); + + response = target(uri).request("application/yang.api+json").get(); + assertEquals(200, response.getStatus()); + final String responseBody = response.readEntity(String.class); + assertTrue("Json response for /operations dummy-rpc1-module1 is incorrect", + validateOperationsResponseJson(responseBody, "dummy-rpc1-module1", "module1").find()); + assertTrue("Json response for /operations dummy-rpc2-module1 is incorrect", + validateOperationsResponseJson(responseBody, "dummy-rpc2-module1", "module1").find()); + assertTrue("Json response for /operations dummy-rpc1-module2 is incorrect", + validateOperationsResponseJson(responseBody, "dummy-rpc1-module2", "module2").find()); + assertTrue("Json response for /operations dummy-rpc2-module2 is incorrect", + validateOperationsResponseJson(responseBody, "dummy-rpc2-module2", "module2").find()); + } + + private static void validateOperationsResponseXml(final Document responseDoc, final SchemaContext schemaContext) { + + final Element operationsElem = responseDoc.getDocumentElement(); + assertEquals(RESTCONF_NS, operationsElem.getNamespaceURI()); + assertEquals("operations", operationsElem.getLocalName()); + + final NodeList operationsList = operationsElem.getChildNodes(); + final HashSet<String> foundOperations = new HashSet<>(); + + for (int i = 0; i < operationsList.getLength(); i++) { + final org.w3c.dom.Node operation = operationsList.item(i); + foundOperations.add(operation.getLocalName()); + } + + for (final RpcDefinition schemaOp : schemaContext.getOperations()) { + assertTrue(foundOperations.contains(schemaOp.getQName().getLocalName())); + } + } + + // /operations/pathToMountPoint/yang-ext:mount + @Ignore + @Test + public void getOperationsBehindMountPointTest() throws Exception { + setControllerContext(schemaContextModules); + + mockMountPoint(); + + final String uri = "/operations/ietf-interfaces:interfaces/interface/0/yang-ext:mount/"; + + Response response = target(uri).request("application/yang.api+xml").get(); + assertEquals(500, response.getStatus()); + + final Document responseDoc = response.readEntity(Document.class); + validateOperationsResponseXml(responseDoc, schemaContextBehindMountPoint); + + response = target(uri).request("application/yang.api+json").get(); + assertEquals(200, response.getStatus()); + final String responseBody = response.readEntity(String.class); + assertTrue("Json response for /operations/mount_point rpc-behind-module1 is incorrect", + validateOperationsResponseJson(responseBody, "rpc-behind-module1", "module1-behind-mount-point").find()); + assertTrue("Json response for /operations/mount_point rpc-behind-module2 is incorrect", + validateOperationsResponseJson(responseBody, "rpc-behind-module2", "module2-behind-mount-point").find()); + + } + + private static Matcher validateOperationsResponseJson(final String searchIn, final String rpcName, + final String moduleName) { + final StringBuilder regex = new StringBuilder(); + regex.append(".*\"" + rpcName + "\""); + final Pattern ptrn = Pattern.compile(regex.toString(), Pattern.DOTALL); + return ptrn.matcher(searchIn); + + } + + // /restconf/modules/pathToMountPoint/yang-ext:mount + @Test + public void getModulesBehindMountPoint() throws Exception { + setControllerContext(schemaContextModules); + + mockMountPoint(); + + final String uri = "/modules/ietf-interfaces:interfaces/interface/0/yang-ext:mount/"; + + Response response = target(uri).request("application/yang.api+json").get(); + assertEquals(200, response.getStatus()); + final String responseBody = response.readEntity(String.class); + + assertTrue( + "module1-behind-mount-point in json wasn't found", + prepareJsonRegex("module1-behind-mount-point", "2014-02-03", "module:1:behind:mount:point", + responseBody).find()); + assertTrue( + "module2-behind-mount-point in json wasn't found", + prepareJsonRegex("module2-behind-mount-point", "2014-02-04", "module:2:behind:mount:point", + responseBody).find()); + + response = target(uri).request("application/yang.api+xml").get(); + assertEquals(200, response.getStatus()); + validateModulesResponseXml(response, schemaContextBehindMountPoint); + + } + + // /restconf/modules/module/pathToMountPoint/yang-ext:mount/moduleName/revision + @Test + public void getModuleBehindMountPoint() throws Exception { + setControllerContext(schemaContextModules); + + mockMountPoint(); + + final String uri = "/modules/module/ietf-interfaces:interfaces/interface/0/yang-ext:mount/" + + "module1-behind-mount-point/2014-02-03"; + + Response response = target(uri).request("application/yang.api+json").get(); + assertEquals(200, response.getStatus()); + final String responseBody = response.readEntity(String.class); + + assertTrue( + "module1-behind-mount-point in json wasn't found", + prepareJsonRegex("module1-behind-mount-point", "2014-02-03", "module:1:behind:mount:point", + responseBody).find()); + final String[] split = responseBody.split("\"module\""); + assertEquals("\"module\" element is returned more then once", 2, split.length); + + response = target(uri).request("application/yang.api+xml").get(); + assertEquals(200, response.getStatus()); + final Document responseXml = response.readEntity(Document.class); + + final QName module = assertedModuleXmlToModuleQName(responseXml.getDocumentElement()); + + assertEquals("module1-behind-mount-point", module.getLocalName()); + assertEquals("2014-02-03", module.getRevision().get().toString()); + assertEquals("module:1:behind:mount:point", module.getNamespace().toString()); + } + + private static void validateModulesResponseXml(final Response response, final SchemaContext schemaContext) { + assertEquals(200, response.getStatus()); + final Document responseBody = response.readEntity(Document.class); + final NodeList moduleNodes = responseBody.getDocumentElement().getElementsByTagNameNS(RESTCONF_NS, "module"); + + assertTrue(moduleNodes.getLength() > 0); + + final HashSet<QName> foundModules = new HashSet<>(); + + for (int i = 0; i < moduleNodes.getLength(); i++) { + final org.w3c.dom.Node module = moduleNodes.item(i); + + final QName name = assertedModuleXmlToModuleQName(module); + foundModules.add(name); + } + + assertAllModules(foundModules,schemaContext); + } + + private static void assertAllModules(final Set<QName> foundModules, final SchemaContext schemaContext) { + for (final Module module : schemaContext.getModules()) { + final QName current = QName.create(module.getQNameModule(), module.getName()); + assertTrue("Module not found in response.", foundModules.contains(current)); + } + + } + + private static QName assertedModuleXmlToModuleQName(final org.w3c.dom.Node module) { + assertEquals("module", module.getLocalName()); + assertEquals(RESTCONF_NS, module.getNamespaceURI()); + String revision = null; + String namespace = null; + String name = null; + + + final NodeList childNodes = module.getChildNodes(); + + for (int i = 0; i < childNodes.getLength(); i++) { + final org.w3c.dom.Node child = childNodes.item(i); + assertEquals(RESTCONF_NS, child.getNamespaceURI()); + + switch (child.getLocalName()) { + case "name": + assertNull("Name element appeared multiple times", name); + name = child.getTextContent().trim(); + break; + case "revision": + assertNull("Revision element appeared multiple times", revision); + revision = child.getTextContent().trim(); + break; + case "namespace": + assertNull("Namespace element appeared multiple times", namespace); + namespace = child.getTextContent().trim(); + break; + default: + break; + } + } + + assertNotNull("Revision was not part of xml",revision); + assertNotNull("Module namespace was not part of xml",namespace); + assertNotNull("Module identiffier was not part of xml",name); + + return QName.create(namespace,revision,name); + } + + private static void validateModulesResponseJson(final Response response) { + assertEquals(200, response.getStatus()); + final String responseBody = response.readEntity(String.class); + + assertTrue("Module1 in json wasn't found", prepareJsonRegex("module1", "2014-01-01", "module:1", responseBody) + .find()); + assertTrue("Module2 in json wasn't found", prepareJsonRegex("module2", "2014-01-02", "module:2", responseBody) + .find()); + assertTrue("Module3 in json wasn't found", prepareJsonRegex("module3", "2014-01-03", "module:3", responseBody) + .find()); + } + + private static Matcher prepareJsonRegex(final String module, final String revision, final String namespace, + final String searchIn) { + final StringBuilder regex = new StringBuilder(); + regex.append("^"); + + regex.append(".*\\{"); + regex.append(".*\"name\""); + regex.append(".*:"); + regex.append(".*\"" + module + "\","); + + regex.append(".*\"revision\""); + regex.append(".*:"); + regex.append(".*\"" + revision + "\","); + + regex.append(".*\"namespace\""); + regex.append(".*:"); + regex.append(".*\"" + namespace + "\""); + + regex.append(".*\\}"); + + regex.append(".*"); + regex.append("$"); + final Pattern ptrn = Pattern.compile(regex.toString(), Pattern.DOTALL); + return ptrn.matcher(searchIn); + + } + + + private int get(final String uri, final String mediaType) { + return target(uri).request(mediaType).get().getStatus(); + } + + /** + * Container structure. + * + * <p> + * container cont { + * container cont1 { + * leaf lf11 { + * type string; + * } + */ + private static NormalizedNode prepareCnDataForMountPointTest(final boolean wrapToCont) throws Exception { + final String testModuleDate = "2014-01-09"; + final ContainerNode contChild = Builders + .containerBuilder() + .withNodeIdentifier(TestUtils.getNodeIdentifier("cont1", "test:module", testModuleDate)) + .withChild( + Builders.leafBuilder() + .withNodeIdentifier(TestUtils.getNodeIdentifier("lf11", "test:module", testModuleDate)) + .withValue("lf11 value").build()).build(); + + if (wrapToCont) { + return Builders.containerBuilder() + .withNodeIdentifier(TestUtils.getNodeIdentifier("cont", "test:module", testModuleDate)) + .withChild(contChild).build(); + } + return contChild; + + } + + private void mockReadOperationalDataMethod() { + when(brokerFacade.readOperationalData(any(YangInstanceIdentifier.class))).thenReturn(answerFromGet); + } + + private void mockReadConfigurationDataMethod() { + when(brokerFacade.readConfigurationData(any(YangInstanceIdentifier.class), isNull())) + .thenReturn(answerFromGet); + } + + private static NormalizedNode prepareCnDataForSlashesBehindMountPointTest() throws Exception { + return ImmutableMapEntryNodeBuilder.create() + .withNodeIdentifier( + TestUtils.getNodeIdentifierPredicate("lst1", "test:module", "2014-01-09", "lf11", + "GigabitEthernet0/0/0/0")) + .withChild( + ImmutableLeafNodeBuilder.create() + .withNodeIdentifier(TestUtils.getNodeIdentifier("lf11", "test:module", "2014-01-09")) + .withValue("GigabitEthernet0/0/0/0").build()).build(); + + } + + /** + * If includeWhiteChars URI parameter is set to false then no white characters can be included in returned output. + */ + @Test + public void getDataWithUriIncludeWhiteCharsParameterTest() throws Exception { + getDataWithUriIncludeWhiteCharsParameter("config"); + getDataWithUriIncludeWhiteCharsParameter("operational"); + } + + private void getDataWithUriIncludeWhiteCharsParameter(final String target) throws Exception { + mockReadConfigurationDataMethod(); + mockReadOperationalDataMethod(); + final String uri = "/" + target + "/ietf-interfaces:interfaces/interface/eth0"; + Response response = target(uri).queryParam("prettyPrint", "false").request("application/xml").get(); + final String xmlData = response.readEntity(String.class); + + Pattern pattern = Pattern.compile(".*(>\\s+|\\s+<).*", Pattern.DOTALL); + Matcher matcher = pattern.matcher(xmlData); + // XML element can't surrounded with white character (e.g "> " or + // " <") + assertFalse(matcher.matches()); + + response = target(uri).queryParam("prettyPrint", "false").request("application/json").get(); + final String jsonData = response.readEntity(String.class); + pattern = Pattern.compile(".*(\\}\\s+|\\s+\\{|\\]\\s+|\\s+\\[|\\s+:|:\\s+).*", Pattern.DOTALL); + matcher = pattern.matcher(jsonData); + // JSON element can't surrounded with white character (e.g "} ", " {", + // "] ", " [", " :" or ": ") + assertFalse(matcher.matches()); + } + + /** + * Tests behavior when invalid value of depth URI parameter. + */ + @Test + @Ignore + public void getDataWithInvalidDepthParameterTest() { + setControllerContext(schemaContextModules); + + final MultivaluedMap<String, String> paramMap = new MultivaluedHashMap<>(); + paramMap.putSingle("depth", "1o"); + final UriInfo mockInfo = mock(UriInfo.class); + when(mockInfo.getQueryParameters(false)).thenAnswer(invocation -> paramMap); + + getDataWithInvalidDepthParameterTest(mockInfo); + + paramMap.putSingle("depth", "0"); + getDataWithInvalidDepthParameterTest(mockInfo); + + paramMap.putSingle("depth", "-1"); + getDataWithInvalidDepthParameterTest(mockInfo); + } + + private void getDataWithInvalidDepthParameterTest(final UriInfo uriInfo) { + try { + final QName qNameDepth1Cont = QName.create("urn:nested:module", "2014-06-3", "depth1-cont"); + final YangInstanceIdentifier ii = YangInstanceIdentifier.builder().node(qNameDepth1Cont).build(); + final NormalizedNode value = + Builders.containerBuilder().withNodeIdentifier(new NodeIdentifier(qNameDepth1Cont)).build(); + when(brokerFacade.readConfigurationData(eq(ii))).thenReturn(value); + restconfImpl.readConfigurationData("nested-module:depth1-cont", uriInfo); + fail("Expected RestconfDocumentedException"); + } catch (final RestconfDocumentedException e) { + assertTrue("Unexpected error message: " + e.getErrors().get(0).getErrorMessage(), e.getErrors().get(0) + .getErrorMessage().contains("depth")); + } + } + + @SuppressWarnings("unused") + private void verifyXMLResponse(final Response response, final NodeData nodeData) { + final Document doc = response.readEntity(Document.class); + assertNotNull("Could not parse XML document", doc); + + verifyContainerElement(doc.getDocumentElement(), nodeData); + } + + @SuppressWarnings("unchecked") + private void verifyContainerElement(final Element element, final NodeData nodeData) { + + assertEquals("Element local name", nodeData.key, element.getLocalName()); + + final NodeList childNodes = element.getChildNodes(); + if (nodeData.data == null) { // empty container + assertTrue( + "Expected no child elements for \"" + element.getLocalName() + "\"", childNodes.getLength() == 0); + return; + } + + final Map<String, NodeData> expChildMap = new HashMap<>(); + for (final NodeData expChild : (List<NodeData>) nodeData.data) { + expChildMap.put(expChild.key.toString(), expChild); + } + + for (int i = 0; i < childNodes.getLength(); i++) { + final org.w3c.dom.Node actualChild = childNodes.item(i); + if (!(actualChild instanceof Element)) { + continue; + } + + final Element actualElement = (Element) actualChild; + final NodeData expChild = expChildMap.remove(actualElement.getLocalName()); + assertNotNull( + "Unexpected child element for parent \"" + element.getLocalName() + "\": " + + actualElement.getLocalName(), expChild); + + if (expChild.data == null || expChild.data instanceof List) { + verifyContainerElement(actualElement, expChild); + } else { + assertEquals("Text content for element: " + actualElement.getLocalName(), expChild.data, + actualElement.getTextContent()); + } + } + + if (!expChildMap.isEmpty()) { + fail("Missing elements for parent \"" + element.getLocalName() + "\": " + expChildMap.keySet()); + } + } + + private void mockMountPoint() { + when(mountInstance.getService(DOMSchemaService.class)) + .thenReturn(Optional.of(FixedDOMSchemaService.of(schemaContextBehindMountPoint))); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestOperationUtils.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestOperationUtils.java new file mode 100644 index 0000000..bdc9679 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestOperationUtils.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2014 Cisco 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; + +public final class RestOperationUtils { + + public static final String JSON = "+json"; + public static final String XML = "+xml"; + + private RestOperationUtils() { + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPostOperationTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPostOperationTest.java new file mode 100644 index 0000000..3aa6a9c --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPostOperationTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2014 Cisco 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.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.opendaylight.controller.sal.restconf.impl.test.RestOperationUtils.XML; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.FluentFuture; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.text.ParseException; +import java.util.Optional; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.MediaType; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.common.api.CommitInfo; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService; +import org.opendaylight.netconf.sal.rest.api.Draft02; +import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader; +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.BrokerFacade; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; + +public class RestPostOperationTest extends JerseyTest { + + private static String xmlBlockData; + private static String xmlData3; + private static String xmlData4; + + private static EffectiveModelContext schemaContextYangsIetf; + private static EffectiveModelContext schemaContextTestModule; + private static EffectiveModelContext schemaContext; + + private BrokerFacade brokerFacade; + private RestconfImpl restconfImpl; + private ControllerContext controllerContext; + private DOMMountPoint mountInstance; + + @BeforeClass + public static void init() throws URISyntaxException, IOException { + schemaContextYangsIetf = TestUtils.loadSchemaContext("/full-versions/yangs"); + schemaContextTestModule = TestUtils.loadSchemaContext("/full-versions/test-module"); + schemaContext = TestUtils.loadSchemaContext("/test-config-data/yang1"); + loadData(); + } + + @Override + protected Application configure() { + /* enable/disable Jersey logs to console */ + // enable(TestProperties.LOG_TRAFFIC); + // enable(TestProperties.DUMP_ENTITY); + // enable(TestProperties.RECORD_LOG_LEVEL); + // set(TestProperties.RECORD_LOG_LEVEL, Level.ALL.intValue()); + + mountInstance = mock(DOMMountPoint.class); + controllerContext = TestRestconfUtils.newControllerContext(schemaContext, mountInstance); + brokerFacade = mock(BrokerFacade.class); + restconfImpl = RestconfImpl.newInstance(brokerFacade, controllerContext); + + ResourceConfig resourceConfig = new ResourceConfig(); + resourceConfig = resourceConfig.registerInstances(restconfImpl, + new XmlNormalizedNodeBodyReader(controllerContext), new NormalizedNodeXmlBodyWriter(), + new JsonNormalizedNodeBodyReader(controllerContext), new NormalizedNodeJsonBodyWriter(), + new RestconfDocumentedExceptionMapper(controllerContext)); + return resourceConfig; + } + + private void setSchemaControllerContext(final EffectiveModelContext schema) { + controllerContext.setSchemas(schema); + } + + @SuppressWarnings("unchecked") + @Test + @Ignore /// xmlData* need netconf-yang + public void postDataViaUrlMountPoint() throws UnsupportedEncodingException { + setSchemaControllerContext(schemaContextYangsIetf); + when(brokerFacade.commitConfigurationDataPost(any(DOMMountPoint.class), any(YangInstanceIdentifier.class), + any(NormalizedNode.class), null, null)).thenReturn(mock(FluentFuture.class)); + + when(mountInstance.getService(DOMSchemaService.class)) + .thenReturn(Optional.of(FixedDOMSchemaService.of(schemaContextTestModule))); + + String uri = "/config/ietf-interfaces:interfaces/interface/0/"; + assertEquals(204, post(uri, Draft02.MediaTypes.DATA + XML, xmlData4)); + uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/test-module:cont"; + assertEquals(204, post(uri, Draft02.MediaTypes.DATA + XML, xmlData3)); + + assertEquals(400, post(uri, MediaType.APPLICATION_JSON, "")); + } + + @SuppressWarnings("unchecked") + @Test + @Ignore //jenkins has problem with JerseyTest + // - we expecting problems with singletons ControllerContext as schemaContext holder + public void createConfigurationDataTest() throws UnsupportedEncodingException, ParseException { + when(brokerFacade.commitConfigurationDataPost((EffectiveModelContext) null, any(YangInstanceIdentifier.class), + any(NormalizedNode.class), null, null)) + .thenReturn(mock(FluentFuture.class)); + + final ArgumentCaptor<YangInstanceIdentifier> instanceIdCaptor = + ArgumentCaptor.forClass(YangInstanceIdentifier.class); + final ArgumentCaptor<NormalizedNode> compNodeCaptor = ArgumentCaptor.forClass(NormalizedNode.class); + + + // FIXME : identify who is set the schemaContext +// final String URI_1 = "/config"; +// assertEquals(204, post(URI_1, Draft02.MediaTypes.DATA + XML, xmlTestInterface)); +// verify(brokerFacade).commitConfigurationDataPost(instanceIdCaptor.capture(), compNodeCaptor.capture()); + final String identifier = "[(urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)interfaces]"; +// assertEquals(identifier, ImmutableList.copyOf(instanceIdCaptor.getValue().getPathArguments()).toString()); + + final String URI_2 = "/config/test-interface:interfaces"; + assertEquals(204, post(URI_2, Draft02.MediaTypes.DATA + XML, xmlBlockData)); + // FIXME : NEVER test a nr. of call some service in complex test suite +// verify(brokerFacade, times(2)) + verify(brokerFacade, times(1)) + .commitConfigurationDataPost((EffectiveModelContext) null, instanceIdCaptor.capture(), + compNodeCaptor.capture(), null, null); +// identifier = "[(urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)interfaces," + +// "(urn:ietf:params:xml:ns:yang:test-interface?revision=2014-07-01)block]"; + assertEquals(identifier, ImmutableList.copyOf(instanceIdCaptor.getValue().getPathArguments()).toString()); + } + + @Test + public void createConfigurationDataNullTest() throws UnsupportedEncodingException { + doReturn(CommitInfo.emptyFluentFuture()).when(brokerFacade).commitConfigurationDataPost( + any(EffectiveModelContext.class), any(YangInstanceIdentifier.class), any(NormalizedNode.class), isNull(), + isNull()); + + //FIXME : find who is set schemaContext +// final String URI_1 = "/config"; +// assertEquals(204, post(URI_1, Draft02.MediaTypes.DATA + XML, xmlTestInterface)); + + final String URI_2 = "/config/test-interface:interfaces"; + assertEquals(204, post(URI_2, Draft02.MediaTypes.DATA + XML, xmlBlockData)); + } + + private int post(final String uri, final String mediaType, final String data) { + return target(uri).request(mediaType).post(Entity.entity(data, mediaType)).getStatus(); + } + + private static void loadData() throws IOException, URISyntaxException { + final String xmlPathBlockData = + RestconfImplTest.class.getResource("/test-config-data/xml/block-data.xml").getPath(); + xmlBlockData = TestUtils.loadTextFile(xmlPathBlockData); + final String data3Input = RestconfImplTest.class.getResource("/full-versions/test-data2/data3.xml").getPath(); + xmlData3 = TestUtils.loadTextFile(data3Input); + final String data4Input = RestconfImplTest.class.getResource("/full-versions/test-data2/data7.xml").getPath(); + xmlData4 = TestUtils.loadTextFile(data4Input); + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPutConfigTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPutConfigTest.java new file mode 100644 index 0000000..9cd03d8 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPutConfigTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2015 Cisco 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 java.io.FileNotFoundException; +import java.util.HashSet; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.common.api.CommitInfo; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.PutResult; +import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; + +@RunWith(MockitoJUnitRunner.class) +public class RestPutConfigTest { + + private static EffectiveModelContext schemaContext; + private RestconfImpl restconfService; + private ControllerContext controllerCx; + + @Mock + private BrokerFacade brokerFacade; + + @BeforeClass + public static void staticInit() throws FileNotFoundException { + schemaContext = TestRestconfUtils.loadSchemaContext("/test-config-data/yang1/", null); + } + + @Before + public void init() { + controllerCx = TestRestconfUtils.newControllerContext(schemaContext); + restconfService = RestconfImpl.newInstance(brokerFacade, controllerCx); + } + + @Test + public void testPutConfigData() { + final String identifier = "test-interface:interfaces/interface/key"; + final InstanceIdentifierContext iiCx = controllerCx.toInstanceIdentifier(identifier); + final MapEntryNode data = Mockito.mock(MapEntryNode.class); + final QName qName = QName.create("urn:ietf:params:xml:ns:yang:test-interface", "2014-07-01", "interface"); + final QName qNameKey = QName.create("urn:ietf:params:xml:ns:yang:test-interface", "2014-07-01", "name"); + final NodeIdentifierWithPredicates identWithPredicates = + NodeIdentifierWithPredicates.of(qName, qNameKey, "key"); + Mockito.when(data.getIdentifier()).thenReturn(identWithPredicates); + final NormalizedNodeContext payload = new NormalizedNodeContext(iiCx, data); + + mockingBrokerPut(iiCx.getInstanceIdentifier(), data); + + final UriInfo uriInfo = Mockito.mock(UriInfo.class); + final MultivaluedMap<String, String> value = Mockito.mock(MultivaluedMap.class); + Mockito.when(value.entrySet()).thenReturn(new HashSet<>()); + Mockito.when(uriInfo.getQueryParameters()).thenReturn(value); + restconfService.updateConfigurationData(identifier, payload, uriInfo); + } + + @Test + public void testPutConfigDataCheckOnlyLastElement() { + final String identifier = "test-interface:interfaces/interface/key/sub-interface/subkey"; + final InstanceIdentifierContext iiCx = controllerCx.toInstanceIdentifier(identifier); + final MapEntryNode data = Mockito.mock(MapEntryNode.class); + final QName qName = QName.create("urn:ietf:params:xml:ns:yang:test-interface", "2014-07-01", "sub-interface"); + final QName qNameSubKey = QName.create("urn:ietf:params:xml:ns:yang:test-interface", "2014-07-01", "sub-name"); + final NodeIdentifierWithPredicates identWithPredicates = + NodeIdentifierWithPredicates.of(qName, qNameSubKey, "subkey"); + Mockito.when(data.getIdentifier()).thenReturn(identWithPredicates); + final NormalizedNodeContext payload = new NormalizedNodeContext(iiCx, data); + + mockingBrokerPut(iiCx.getInstanceIdentifier(), data); + + final UriInfo uriInfo = Mockito.mock(UriInfo.class); + final MultivaluedMap<String, String> value = Mockito.mock(MultivaluedMap.class); + Mockito.when(value.entrySet()).thenReturn(new HashSet<>()); + Mockito.when(uriInfo.getQueryParameters()).thenReturn(value); + restconfService.updateConfigurationData(identifier, payload, uriInfo); + } + + @Test(expected = RestconfDocumentedException.class) + public void testPutConfigDataMissingUriKey() { + final String identifier = "test-interface:interfaces/interface"; + controllerCx.toInstanceIdentifier(identifier); + } + + @Test(expected = RestconfDocumentedException.class) + public void testPutConfigDataDiferentKey() { + final String identifier = "test-interface:interfaces/interface/key"; + final InstanceIdentifierContext iiCx = controllerCx.toInstanceIdentifier(identifier); + final MapEntryNode data = Mockito.mock(MapEntryNode.class); + final QName qName = QName.create("urn:ietf:params:xml:ns:yang:test-interface", "2014-07-01", "interface"); + final QName qNameKey = QName.create("urn:ietf:params:xml:ns:yang:test-interface", "2014-07-01", "name"); + final NodeIdentifierWithPredicates identWithPredicates = + NodeIdentifierWithPredicates.of(qName, qNameKey, "notSameKey"); + Mockito.when(data.getIdentifier()).thenReturn(identWithPredicates); + final NormalizedNodeContext payload = new NormalizedNodeContext(iiCx, data); + + mockingBrokerPut(iiCx.getInstanceIdentifier(), data); + + final UriInfo uriInfo = Mockito.mock(UriInfo.class); + final MultivaluedMap<String, String> value = Mockito.mock(MultivaluedMap.class); + Mockito.when(value.entrySet()).thenReturn(new HashSet<>()); + Mockito.when(uriInfo.getQueryParameters()).thenReturn(value); + restconfService.updateConfigurationData(identifier, payload, uriInfo); + } + + private void mockingBrokerPut(final YangInstanceIdentifier yii, final NormalizedNode data) { + final PutResult result = Mockito.mock(PutResult.class); + Mockito.when(brokerFacade.commitConfigurationDataPut(schemaContext, yii, data, null, null)) + .thenReturn(result); + Mockito.doReturn(CommitInfo.emptyFluentFuture()).when(result).getFutureOfPutData(); + Mockito.when(result.getStatus()).thenReturn(Status.OK); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPutOperationTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPutOperationTest.java new file mode 100644 index 0000000..8c04e50 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPutOperationTest.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2014 Cisco 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.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.util.Optional; +import javax.ws.rs.client.Entity; +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 org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.common.api.CommitInfo; +import org.opendaylight.mdsal.common.api.OptimisticLockFailedException; +import org.opendaylight.mdsal.common.api.TransactionCommitFailedException; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService; +import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader; +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.BrokerFacade; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.PutResult; +import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +//TODO UNSTABLE TESTS - FIX ME +@Ignore +public class RestPutOperationTest extends JerseyTest { + + private static String xmlData; + private static String xmlData2; + private static String xmlData3; + + private static EffectiveModelContext schemaContextYangsIetf; + private static EffectiveModelContext schemaContextTestModule; + + private BrokerFacade brokerFacade; + private RestconfImpl restconfImpl; + private DOMMountPoint mountInstance; + + @BeforeClass + public static void init() throws IOException, ReactorException { + schemaContextYangsIetf = TestUtils.loadSchemaContext("/full-versions/yangs"); + schemaContextTestModule = TestUtils.loadSchemaContext("/full-versions/test-module"); + loadData(); + } + + private static void loadData() throws IOException { + final InputStream xmlStream = + RestconfImplTest.class.getResourceAsStream("/parts/ietf-interfaces_interfaces.xml"); + xmlData = TestUtils.getDocumentInPrintableForm(TestUtils.loadDocumentFrom(xmlStream)); + final InputStream xmlStream2 = + RestconfImplTest.class.getResourceAsStream("/full-versions/test-data2/data2.xml"); + xmlData2 = TestUtils.getDocumentInPrintableForm(TestUtils.loadDocumentFrom(xmlStream2)); + final InputStream xmlStream3 = + RestconfImplTest.class.getResourceAsStream("/full-versions/test-data2/data7.xml"); + xmlData3 = TestUtils.getDocumentInPrintableForm(TestUtils.loadDocumentFrom(xmlStream3)); + } + + @Override + protected Application configure() { + /* enable/disable Jersey logs to console */ + // enable(TestProperties.LOG_TRAFFIC); + // enable(TestProperties.DUMP_ENTITY); + // enable(TestProperties.RECORD_LOG_LEVEL); + // set(TestProperties.RECORD_LOG_LEVEL, Level.ALL.intValue()); + + mountInstance = mock(DOMMountPoint.class); + final ControllerContext controllerContext = + TestRestconfUtils.newControllerContext(schemaContextYangsIetf, mountInstance); + brokerFacade = mock(BrokerFacade.class); + restconfImpl = RestconfImpl.newInstance(brokerFacade, controllerContext); + + ResourceConfig resourceConfig = new ResourceConfig(); + resourceConfig = resourceConfig.registerInstances(restconfImpl, + new XmlNormalizedNodeBodyReader(controllerContext), new NormalizedNodeXmlBodyWriter(), + new JsonNormalizedNodeBodyReader(controllerContext), new NormalizedNodeJsonBodyWriter(), + new RestconfDocumentedExceptionMapper(controllerContext)); + return resourceConfig; + } + + /** + * Tests of status codes for "/config/{identifier}". + */ + @Test + public void putConfigStatusCodes() throws UnsupportedEncodingException { + final String uri = "/config/ietf-interfaces:interfaces/interface/eth0"; + mockCommitConfigurationDataPutMethod(true); + assertEquals(500, put(uri, MediaType.APPLICATION_XML, xmlData)); + + mockCommitConfigurationDataPutMethod(false); + assertEquals(500, put(uri, MediaType.APPLICATION_XML, xmlData)); + + assertEquals(400, put(uri, MediaType.APPLICATION_JSON, "")); + } + + @Test + public void putConfigStatusCodesEmptyBody() throws UnsupportedEncodingException { + final String uri = "/config/ietf-interfaces:interfaces/interface/eth0"; + @SuppressWarnings("unused") + final Response resp = target(uri).request(MediaType.APPLICATION_JSON).put( + Entity.entity("", MediaType.APPLICATION_JSON)); + assertEquals(400, put(uri, MediaType.APPLICATION_JSON, "")); + } + + @Test + public void testRpcResultCommitedToStatusCodesWithMountPoint() throws UnsupportedEncodingException, + FileNotFoundException, URISyntaxException { + final PutResult result = mock(PutResult.class); + when(brokerFacade.commitMountPointDataPut(any(DOMMountPoint.class), any(YangInstanceIdentifier.class), + any(NormalizedNode.class), null, null)).thenReturn(result); + doReturn(CommitInfo.emptyFluentFuture()).when(result).getFutureOfPutData(); + when(result.getStatus()).thenReturn(Status.OK); + + mockMountPoint(); + + String uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/test-module:cont"; + assertEquals(200, put(uri, MediaType.APPLICATION_XML, xmlData2)); + + uri = "/config/ietf-interfaces:interfaces/yang-ext:mount/test-module:cont"; + assertEquals(200, put(uri, MediaType.APPLICATION_XML, xmlData2)); + } + + @Test + public void putDataMountPointIntoHighestElement() throws UnsupportedEncodingException, URISyntaxException { + final PutResult result = mock(PutResult.class); + doReturn(result).when(brokerFacade).commitMountPointDataPut(any(DOMMountPoint.class), + any(YangInstanceIdentifier.class), any(NormalizedNode.class), null, null); + doReturn(CommitInfo.emptyFluentFuture()).when(result).getFutureOfPutData(); + when(result.getStatus()).thenReturn(Status.OK); + + mockMountPoint(); + + final String uri = "/config/ietf-interfaces:interfaces/yang-ext:mount"; + assertEquals(200, put(uri, MediaType.APPLICATION_XML, xmlData3)); + } + + @Test + public void putWithOptimisticLockFailedException() throws UnsupportedEncodingException { + + final String uri = "/config/ietf-interfaces:interfaces/interface/eth0"; + + doThrow(OptimisticLockFailedException.class).when(brokerFacade).commitConfigurationDataPut( + any(EffectiveModelContext.class), any(YangInstanceIdentifier.class), any(NormalizedNode.class), null, + null); + + assertEquals(500, put(uri, MediaType.APPLICATION_XML, xmlData)); + + doThrow(OptimisticLockFailedException.class).doReturn(mock(PutResult.class)).when(brokerFacade) + .commitConfigurationDataPut(any(EffectiveModelContext.class), any(YangInstanceIdentifier.class), + any(NormalizedNode.class), null, null); + + assertEquals(500, put(uri, MediaType.APPLICATION_XML, xmlData)); + } + + @Test + public void putWithTransactionCommitFailedException() throws UnsupportedEncodingException { + + final String uri = "/config/ietf-interfaces:interfaces/interface/eth0"; + + doThrow(TransactionCommitFailedException.class) + .when(brokerFacade).commitConfigurationDataPut( + any(EffectiveModelContext.class), any(YangInstanceIdentifier.class), any(NormalizedNode.class), + null, null); + + assertEquals(500, put(uri, MediaType.APPLICATION_XML, xmlData)); + } + + private int put(final String uri, final String mediaType, final String data) throws UnsupportedEncodingException { + return target(uri).request(mediaType).put(Entity.entity(data, mediaType)).getStatus(); + } + + private void mockMountPoint() { + when(mountInstance.getService(DOMSchemaService.class)) + .thenReturn(Optional.of(FixedDOMSchemaService.of(schemaContextTestModule))); + } + + private void mockCommitConfigurationDataPutMethod(final boolean noErrors) { + final PutResult putResMock = mock(PutResult.class); + if (noErrors) { + doReturn(putResMock).when(brokerFacade).commitConfigurationDataPut( + any(EffectiveModelContext.class), any(YangInstanceIdentifier.class), any(NormalizedNode.class), + null, null); + } else { + doThrow(RestconfDocumentedException.class).when(brokerFacade).commitConfigurationDataPut( + any(EffectiveModelContext.class), any(YangInstanceIdentifier.class), any(NormalizedNode.class), + null, null); + } + } + +} 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; + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfErrorTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfErrorTest.java new file mode 100644 index 0000000..30a0449 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfErrorTest.java @@ -0,0 +1,147 @@ +/* + * 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.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.junit.Test; +import org.opendaylight.restconf.common.errors.RestconfError; +import org.opendaylight.yangtools.yang.common.ErrorTag; +import org.opendaylight.yangtools.yang.common.ErrorType; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; + +/** + * Unit tests for RestconfError. + * + * @author Devin Avery + * @author Thomas Pantelis + * + */ +public class RestconfErrorTest { + + static class Contains extends BaseMatcher<String> { + + private final String text; + + Contains(final String text) { + this.text = text; + } + + @Override + public void describeTo(final Description desc) { + desc.appendText("contains ").appendValue(text); + } + + @Override + public boolean matches(final Object arg) { + return arg != null && arg.toString().contains(text); + } + } + + @Test + public void testRestConfDocumentedException_NoCause() { + String expectedMessage = "Message"; + ErrorType expectedErrorType = ErrorType.RPC; + ErrorTag expectedErrorTag = ErrorTag.IN_USE; + RestconfError error = new RestconfError(expectedErrorType, expectedErrorTag, expectedMessage); + + validateRestConfError(expectedMessage, expectedErrorType, expectedErrorTag, null, (String) null, error); + } + + @Test + public void testRestConfDocumentedException_WithAppTag() { + String expectedMessage = "Message"; + ErrorType expectedErrorType = ErrorType.RPC; + ErrorTag expectedErrorTag = ErrorTag.IN_USE; + String expectedErrorAppTag = "application.tag"; + + RestconfError error = + new RestconfError(expectedErrorType, expectedErrorTag, expectedMessage, expectedErrorAppTag); + + validateRestConfError(expectedMessage, expectedErrorType, expectedErrorTag, expectedErrorAppTag, (String) null, + error); + } + + @Test + public void testRestConfDocumentedException_WithAppTagErrorInfo() { + String expectedMessage = "Message"; + ErrorType expectedErrorType = ErrorType.RPC; + ErrorTag expectedErrorTag = ErrorTag.IN_USE; + String expectedErrorAppTag = "application.tag"; + String errorInfo = "<extra><sessionid>session.id</sessionid></extra>"; + + RestconfError error = + new RestconfError(expectedErrorType, expectedErrorTag, expectedMessage, expectedErrorAppTag, errorInfo); + + validateRestConfError(expectedMessage, expectedErrorType, expectedErrorTag, expectedErrorAppTag, errorInfo, + error); + } + + @Test + public void testRestConfErrorWithRpcError() { + + // All fields set + RpcError rpcError = RpcResultBuilder.newError(ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE, "mock error-message", + "mock app-tag", "mock error-info", new Exception("mock cause")); + + validateRestConfError("mock error-message", ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE, "mock app-tag", + "mock error-info", new RestconfError(rpcError)); + + // All fields set except 'info' - expect error-info set to 'cause' + rpcError = RpcResultBuilder.newError(ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE, "mock error-message", + "mock app-tag", null, new Exception("mock cause")); + + validateRestConfError("mock error-message", ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE, "mock app-tag", + new Contains("mock cause"), new RestconfError(rpcError)); + + // Some fields set - expect error-info set to ErrorSeverity + rpcError = RpcResultBuilder.newError(ErrorType.RPC, ErrorTag.ACCESS_DENIED, null, null, null, null); + + validateRestConfError(null, ErrorType.RPC, ErrorTag.ACCESS_DENIED, null, "<severity>error</severity>", + new RestconfError(rpcError)); + + // 'tag' field not mapped to ErrorTag - expect error-tag set to OPERATION_FAILED + rpcError = RpcResultBuilder.newWarning(ErrorType.TRANSPORT, new ErrorTag("not mapped"), null, null, null, null); + + validateRestConfError(null, ErrorType.TRANSPORT, new ErrorTag("not mapped"), null, + "<severity>warning</severity>", new RestconfError(rpcError)); + + // No fields set - edge case + rpcError = RpcResultBuilder.newError(ErrorType.APPLICATION, null, null, null, null, null); + + validateRestConfError(null, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, + null, "<severity>error</severity>", new RestconfError(rpcError)); + } + + private static void validateRestConfError(final String expectedMessage, final ErrorType expectedErrorType, + final ErrorTag expectedErrorTag, final String expectedErrorAppTag, final String errorInfo, + final RestconfError error) { + + validateRestConfError(expectedMessage, expectedErrorType, expectedErrorTag, expectedErrorAppTag, + equalTo(errorInfo), error); + } + + private static void validateRestConfError(final String expectedMessage, final ErrorType expectedErrorType, + final ErrorTag expectedErrorTag, final String expectedErrorAppTag, final Matcher<String> errorInfoMatcher, + final RestconfError error) { + + assertEquals("getErrorMessage", expectedMessage, error.getErrorMessage()); + assertEquals("getErrorType", expectedErrorType, error.getErrorType()); + assertEquals("getErrorTag", expectedErrorTag, error.getErrorTag()); + assertEquals("getErrorAppTag", expectedErrorAppTag, error.getErrorAppTag()); + assertThat("getErrorInfo", error.getErrorInfo(), errorInfoMatcher); + error.toString(); // really just checking for NPE etc. Don't care about + // contents. + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfImplNotificationSubscribingTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfImplNotificationSubscribingTest.java new file mode 100644 index 0000000..b69d105 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfImplNotificationSubscribingTest.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2016 Cisco 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.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.FileNotFoundException; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl; +import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter; +import org.opendaylight.netconf.sal.streams.listeners.Notificator; +import org.opendaylight.netconf.sal.streams.websockets.WebSocketServer; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; + +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class RestconfImplNotificationSubscribingTest { + + private final String identifier = "data-change-event-subscription/datastore=OPERATIONAL/scope=ONE"; + + private static EffectiveModelContext schemaContext; + + @Mock + private BrokerFacade broker; + + @Mock + private UriInfo uriInfo; + + private ControllerContext controllerContext; + private RestconfImpl restconfImpl; + + @BeforeClass + public static void init() throws FileNotFoundException { + schemaContext = YangParserTestUtils.parseYangFiles(TestRestconfUtils.loadFiles("/notifications")); + } + + @AfterClass + public static void cleanUp() { + WebSocketServer.destroyInstance(); // NETCONF-604 + } + + @Before + public void setup() { + controllerContext = TestRestconfUtils.newControllerContext(schemaContext); + restconfImpl = RestconfImpl.newInstance(broker, controllerContext); + + final YangInstanceIdentifier path = mock(YangInstanceIdentifier.class); + Notificator.createListener(path, identifier, NotificationOutputType.XML, controllerContext); + } + + @Test + public void startTimeTest() { + subscribe(Set.of(Map.entry("start-time", List.of("2014-10-25T10:02:00Z")))); + Notificator.removeAllListeners(); + } + + @Test + public void milisecsTest() { + subscribe(Set.of(Map.entry("start-time", List.of("2014-10-25T10:02:00.12345Z")))); + Notificator.removeAllListeners(); + } + + @Test + public void zonesPlusTest() { + subscribe(Set.of(Map.entry("start-time", List.of("2014-10-25T10:02:00+01:00")))); + Notificator.removeAllListeners(); + } + + @Test + public void zonesMinusTest() { + subscribe(Set.of(Map.entry("start-time", List.of("2014-10-25T10:02:00-01:00")))); + Notificator.removeAllListeners(); + } + + @Test + public void startAndStopTimeTest() { + subscribe(Set.of(Map.entry("start-time", List.of("2014-10-25T10:02:00Z")), + Map.entry("stop-time", List.of("2014-10-25T12:31:00Z")))); + Notificator.removeAllListeners(); + } + + @Test(expected = RestconfDocumentedException.class) + public void stopTimeTest() { + subscribe(Set.of(Map.entry("stop-time", List.of("2014-10-25T12:31:00Z")))); + Notificator.removeAllListeners(); + } + + @Test(expected = RestconfDocumentedException.class) + public void badParamTest() { + subscribe(Set.of(Map.entry("time", List.of("2014-10-25T12:31:00Z")))); + Notificator.removeAllListeners(); + } + + @Test(expected = IllegalArgumentException.class) + public void badValueTest() { + subscribe(Set.of(Map.entry("start-time", List.of("badvalue")))); + Notificator.removeAllListeners(); + } + + @Test(expected = IllegalArgumentException.class) + public void badZonesTest() { + subscribe(Set.of(Map.entry("start-time", List.of("2014-10-25T10:02:00Z+1:00")))); + Notificator.removeAllListeners(); + } + + @Test(expected = IllegalArgumentException.class) + public void badMilisecsTest() { + subscribe(Set.of(Map.entry("start-time", List.of("2014-10-25T10:02:00:0026Z")))); + Notificator.removeAllListeners(); + } + + @Test + public void onNotifiTest() throws Exception { + final YangInstanceIdentifier path = mock(YangInstanceIdentifier.class); + final PathArgument pathValue = NodeIdentifier.create(QName.create("module", "2016-12-14", "localName")); + final ListenerAdapter listener = Notificator.createListener(path, identifier, NotificationOutputType.XML, + controllerContext); + + subscribe(Set.of(Map.entry("start-time", List.of("2014-10-25T10:02:00Z")))); + + Instant startOrig = listener.getStart(); + assertNotNull(startOrig); + listener.onDataTreeChanged(List.of()); + + startOrig = listener.getStart(); + assertNull(startOrig); + } + + private void subscribe(final Set<Entry<String, List<String>>> entries) { + final MultivaluedMap<String, String> map = mock(MultivaluedMap.class); + when(uriInfo.getQueryParameters()).thenReturn(map); + final UriBuilder uriBuilder = UriBuilder.fromPath("http://localhost:8181/" + identifier); + when(uriInfo.getAbsolutePathBuilder()).thenReturn(uriBuilder); + when(map.entrySet()).thenReturn(entries); + restconfImpl.subscribeToStream(identifier, uriInfo); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfImplTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfImplTest.java new file mode 100644 index 0000000..3174908 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfImplTest.java @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2014 Cisco 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.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; +import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture; + +import java.io.FileNotFoundException; +import java.net.URI; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.mdsal.dom.api.DOMRpcResult; +import org.opendaylight.mdsal.dom.api.DOMRpcService; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService; +import org.opendaylight.netconf.sal.rest.api.Draft02; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl; +import org.opendaylight.netconf.sal.streams.listeners.Notificator; +import org.opendaylight.netconf.sal.streams.websockets.WebSocketServer; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.restconf.common.errors.RestconfError; +import org.opendaylight.yangtools.yang.common.Empty; +import org.opendaylight.yangtools.yang.common.ErrorTag; +import org.opendaylight.yangtools.yang.common.ErrorType; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.YangConstants; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.InputSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.OutputSchemaNode; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.opendaylight.yangtools.yang.model.api.stmt.InputEffectiveStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.OutputEffectiveStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +/** + * See {@link InvokeRpcMethodTest}. + */ +public class RestconfImplTest { + + private static EffectiveModelContext schemaContext; + + private final BrokerFacade brokerFacade = mock(BrokerFacade.class); + private final ControllerContext controllerContext = TestRestconfUtils.newControllerContext(schemaContext); + private final RestconfImpl restconfImpl = RestconfImpl.newInstance(brokerFacade, controllerContext); + + @BeforeClass + public static void init() throws FileNotFoundException, ReactorException { + schemaContext = TestUtils.loadSchemaContext("/full-versions/yangs", "/modules/restconf-module-testing"); + } + + @AfterClass + public static void cleanUp() { + WebSocketServer.destroyInstance(); // NETCONF-604 + } + + @Test + public void binaryKeyTest() { + final List<Byte> al = new ArrayList<>(); + al.add((byte) 1); + binaryKeyTest(al, al); + } + + private static void binaryKeyTest(final List<Byte> al, final List<Byte> al2) { + + final QName keyDef = QName.create("test:key:binary", "2017-08-14", "b1"); + + final Map<QName, Object> uriKeyValues = new HashMap<>(); + uriKeyValues.put(keyDef, al.toArray()); + + final MapEntryNode payload = mock(MapEntryNode.class); + final NodeIdentifierWithPredicates nodeIdWithPred = + NodeIdentifierWithPredicates.of(keyDef, keyDef, al2.toArray()); + when(payload.getIdentifier()).thenReturn(nodeIdWithPred); + + final List<QName> keyDefinitions = new ArrayList<>(); + keyDefinitions.add(keyDef); + RestconfImpl.isEqualUriAndPayloadKeyValues(uriKeyValues, payload, keyDefinitions); + } + + @Test + public void binaryKeyFailTest() { + final List<Byte> al = new ArrayList<>(); + al.add((byte) 1); + final List<Byte> al2 = new ArrayList<>(); + try { + binaryKeyTest(al, al2); + } catch (final RestconfDocumentedException e) { + final RestconfError err = e.getErrors().iterator().next(); + assertEquals(ErrorType.PROTOCOL, err.getErrorType()); + assertEquals(ErrorTag.INVALID_VALUE, err.getErrorTag()); + } + } + + @Test + public void testExample() throws FileNotFoundException, ParseException { + final NormalizedNode normalizedNodeData = TestUtils.prepareNormalizedNodeWithIetfInterfacesInterfacesData(); + when(brokerFacade.readOperationalData(isNull())).thenReturn(normalizedNodeData); + assertEquals(normalizedNodeData, + brokerFacade.readOperationalData(null)); + } + + @Test + public void testRpcForMountpoint() throws Exception { + final QName qname = QName.create("namespace", "2010-10-10", "localname"); + final UriInfo uriInfo = mock(UriInfo.class); + doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters(anyBoolean()); + + final NormalizedNodeContext ctx = mock(NormalizedNodeContext.class); + final RpcDefinition rpc = mock(RpcDefinition.class, + withSettings().extraInterfaces(RpcEffectiveStatement.class)); + doReturn(qname).when(rpc).getQName(); + + final InputSchemaNode input = mock(InputSchemaNode.class, + withSettings().extraInterfaces(InputEffectiveStatement.class)); + final QName inputQName = YangConstants.operationInputQName(qname.getModule()); + doReturn(input).when(rpc).getInput(); + doReturn(inputQName).when(input).getQName(); + doReturn(Optional.of(input)).when((RpcEffectiveStatement) rpc).findSchemaTreeNode(inputQName); + + final OutputSchemaNode output = mock(OutputSchemaNode.class, + withSettings().extraInterfaces(OutputEffectiveStatement.class)); + final QName outputQName = YangConstants.operationInputQName(qname.getModule()); + doReturn(output).when(rpc).getOutput(); + doReturn(outputQName).when(output).getQName(); + doReturn(Optional.of(output)).when((RpcEffectiveStatement) rpc).findSchemaTreeNode(outputQName); + + final EffectiveModelContext mountContext = mock(EffectiveModelContext.class); + final ModuleEffectiveStatement mountModule = mock(ModuleEffectiveStatement.class); + doReturn(Map.of(qname.getModule(), mountModule)).when(mountContext).getModuleStatements(); + doReturn(Optional.of(rpc)).when(mountModule).findSchemaTreeNode(qname); + + final DOMMountPoint mount = mock(DOMMountPoint.class); + doReturn(Optional.of(FixedDOMSchemaService.of(mountContext))).when(mount).getService(DOMSchemaService.class); + + doReturn(InstanceIdentifierContext.ofRpcInput(mountContext, rpc, mount)) + .when(ctx).getInstanceIdentifierContext(); + + final DOMRpcService rpcService = mock(DOMRpcService.class); + doReturn(Optional.of(rpcService)).when(mount).getService(DOMRpcService.class); + doReturn(immediateFluentFuture(mock(DOMRpcResult.class))).when(rpcService) + .invokeRpc(any(QName.class), any(NormalizedNode.class)); + restconfImpl.invokeRpc("randomId", ctx, uriInfo); + restconfImpl.invokeRpc("ietf-netconf", ctx, uriInfo); + verify(rpcService, times(2)).invokeRpc(any(QName.class), any()); + } + + /** + * Create notification stream for toaster module. + */ + @Test + public void createNotificationStreamTest() { + final QName rpcQName = QName.create("urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote", + "2014-01-14", "create-notification-stream"); + + final RpcDefinition schemaNode = schemaContext.getOperations().stream() + .filter(rpc -> rpc.getQName().equals(rpcQName)) + .findFirst() + .orElseThrow(); + + final NormalizedNodeContext payload = mock(NormalizedNodeContext.class); + doReturn(InstanceIdentifierContext.ofRpcInput(schemaContext, schemaNode, null)).when(payload) + .getInstanceIdentifierContext(); + + final Set<DataContainerChild> children = new HashSet<>(); + final LeafSetNode child = mock(LeafSetNode.class); + + final LeafSetEntryNode entryNode = mock(LeafSetEntryNode.class); + when(entryNode.body()).thenReturn("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)toastDone"); + when(child.body()).thenReturn(Set.of(entryNode)); + children.add(child); + + final ContainerNode normalizedNode = mock(ContainerNode.class); + doReturn(normalizedNode).when(payload).getData(); + doReturn(children).when(normalizedNode).body(); + + // register notification + final NormalizedNodeContext context = restconfImpl + .invokeRpc("sal-remote:create-notification-stream", payload, null); + assertNotNull(context); + } + + /** + * Tests stream entry node. + */ + @Test + public void toStreamEntryNodeTest() { + final Module restconfModule = controllerContext.getRestconfModule(); + final DataSchemaNode streamSchemaNode = controllerContext + .getRestconfModuleRestConfSchemaNode(restconfModule, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE); + final ListSchemaNode listStreamSchemaNode = (ListSchemaNode) streamSchemaNode; + final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamNodeValues = + SchemaAwareBuilders.mapEntryBuilder(listStreamSchemaNode); + var instanceDataChildrenByName = + ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "name"); + final DataSchemaNode nameSchemaNode = instanceDataChildrenByName.get(0).child; + streamNodeValues.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) nameSchemaNode) + .withValue("") + .build()); + + instanceDataChildrenByName = + ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "description"); + final DataSchemaNode descriptionSchemaNode = instanceDataChildrenByName.get(0).child; + streamNodeValues.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) nameSchemaNode) + .withValue("DESCRIPTION_PLACEHOLDER") + .build()); + + instanceDataChildrenByName = + ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "replay-support"); + final DataSchemaNode replaySupportSchemaNode = instanceDataChildrenByName.get(0).child; + streamNodeValues.withChild( + SchemaAwareBuilders.leafBuilder((LeafSchemaNode) replaySupportSchemaNode).withValue(Boolean.TRUE).build()); + + instanceDataChildrenByName = + ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "replay-log-creation-time"); + final DataSchemaNode replayLogCreationTimeSchemaNode = instanceDataChildrenByName.get(0).child; + streamNodeValues.withChild( + SchemaAwareBuilders.leafBuilder((LeafSchemaNode) replayLogCreationTimeSchemaNode).withValue("").build()); + instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "events"); + final DataSchemaNode eventsSchemaNode = instanceDataChildrenByName.get(0).child; + streamNodeValues.withChild( + SchemaAwareBuilders.leafBuilder((LeafSchemaNode) eventsSchemaNode).withValue(Empty.value()).build()); + assertNotNull(streamNodeValues.build()); + } + + /** + * Subscribe for notification stream of toaster module. + */ + @Test + public void subscribeToNotificationStreamTest() throws Exception { + final String identifier = "create-notification-stream/toaster:toastDone"; + + // register test notification stream + Notificator.createNotificationListener( + List.of(Absolute.of(QName.create("http://netconfcentral.org/ns/toaster", "2009-11-20", "toastDone"))), + identifier, "XML", controllerContext); + + final UriInfo uriInfo = mock(UriInfo.class); + final UriBuilder uriBuilder = mock(UriBuilder.class); + when(uriBuilder.port(8181)).thenReturn(uriBuilder); + when(uriBuilder.replacePath(identifier)).thenReturn(uriBuilder); + when(uriBuilder.build()).thenReturn(new URI("")); + when(uriBuilder.scheme("ws")).thenReturn(uriBuilder); + when(uriInfo.getAbsolutePathBuilder()).thenReturn(uriBuilder); + final MultivaluedMap<String, String> map = mock(MultivaluedMap.class); + final Set<Entry<String, List<String>>> set = new HashSet<>(); + when(map.entrySet()).thenReturn(set); + when(uriInfo.getQueryParameters()).thenReturn(map); + + // subscribe to stream and verify response + final NormalizedNodeContext response = restconfImpl.subscribeToStream(identifier, uriInfo); + + // remove test notification stream + Notificator.removeAllListeners(); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java new file mode 100644 index 0000000..d96cc77 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2014 Cisco 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 com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; +import static org.junit.Assert.assertNotNull; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.opendaylight.yangtools.util.xml.UntrustedXML; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.common.XMLNamespace; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +public final class TestUtils { + + private static final Logger LOG = LoggerFactory.getLogger(TestUtils.class); + + private TestUtils() { + + } + + public static EffectiveModelContext loadSchemaContext(final String... yangPath) throws FileNotFoundException { + final List<File> files = new ArrayList<>(); + for (final String path : yangPath) { + final String pathToFile = TestUtils.class.getResource(path).getPath(); + final File testDir = new File(pathToFile); + final String[] fileList = testDir.list(); + if (fileList == null) { + throw new FileNotFoundException(pathToFile); + } + + for (final String fileName : fileList) { + final File file = new File(testDir, fileName); + if (file.isDirectory() == false) { + files.add(file); + } + } + } + + return YangParserTestUtils.parseYangFiles(files); + } + + public static Module findModule(final Collection<? extends Module> modules, final String moduleName) { + for (final Module module : modules) { + if (module.getName().equals(moduleName)) { + return module; + } + } + return null; + } + + public static Document loadDocumentFrom(final InputStream inputStream) { + try { + return UntrustedXML.newDocumentBuilder().parse(inputStream); + } catch (SAXException | IOException e) { + LOG.error("Error during loading Document from XML", e); + return null; + } + } + + public static String getDocumentInPrintableForm(final Document doc) { + try { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final TransformerFactory tf = TransformerFactory.newInstance(); + final Transformer transformer = tf.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); + + transformer.transform(new DOMSource(requireNonNull(doc)), new StreamResult(new OutputStreamWriter(out, + StandardCharsets.UTF_8))); + final byte[] charData = out.toByteArray(); + return new String(charData, StandardCharsets.UTF_8); + } catch (final TransformerException e) { + final String msg = "Error during transformation of Document into String"; + LOG.error(msg, e); + return msg; + } + + } + + /** + * Searches module with name {@code searchedModuleName} in {@code modules}. If module name isn't specified and + * module set has only one element then this element is returned. + * + */ + public static Module resolveModule(final String searchedModuleName, final Collection<? extends Module> modules) { + assertNotNull("Modules can't be null.", modules); + if (searchedModuleName != null) { + for (final Module m : modules) { + if (m.getName().equals(searchedModuleName)) { + return m; + } + } + } else if (modules.size() == 1) { + return modules.iterator().next(); + } + return null; + } + + public static DataSchemaNode resolveDataSchemaNode(final String searchedDataSchemaName, final Module module) { + assertNotNull("Module can't be null", module); + + if (searchedDataSchemaName != null) { + for (final DataSchemaNode dsn : module.getChildNodes()) { + if (dsn.getQName().getLocalName().equals(searchedDataSchemaName)) { + return dsn; + } + } + } else if (module.getChildNodes().size() == 1) { + return module.getChildNodes().iterator().next(); + } + return null; + } + + public static QName buildQName(final String name, final String uri, final String date, final String prefix) { + return QName.create(XMLNamespace.of(uri), Revision.ofNullable(date), name); + } + + public static QName buildQName(final String name, final String uri, final String date) { + return buildQName(name, uri, date, null); + } + + public static QName buildQName(final String name) { + return buildQName(name, "", null); + } + + public static String loadTextFile(final String filePath) throws IOException { + final FileReader fileReader = new FileReader(filePath, StandardCharsets.UTF_8); + final BufferedReader bufReader = new BufferedReader(fileReader); + + String line = null; + final StringBuilder result = new StringBuilder(); + while ((line = bufReader.readLine()) != null) { + result.append(line); + } + bufReader.close(); + return result.toString(); + } + + private static Pattern patternForStringsSeparatedByWhiteChars(final String... substrings) { + final StringBuilder pattern = new StringBuilder(); + pattern.append(".*"); + for (final String substring : substrings) { + pattern.append(substring); + pattern.append("\\s*"); + } + pattern.append(".*"); + return Pattern.compile(pattern.toString(), Pattern.DOTALL); + } + + public static boolean containsStringData(final String jsonOutput, final String... substrings) { + final Pattern pattern = patternForStringsSeparatedByWhiteChars(substrings); + final Matcher matcher = pattern.matcher(jsonOutput); + return matcher.matches(); + } + + public static NodeIdentifier getNodeIdentifier(final String localName, final String namespace, + final String revision) throws ParseException { + return new NodeIdentifier(QName.create(namespace, revision, localName)); + } + + public static NodeIdentifierWithPredicates getNodeIdentifierPredicate(final String localName, + final String namespace, final String revision, final Map<String, Object> keys) throws ParseException { + final Map<QName, Object> predicate = new HashMap<>(); + for (final String key : keys.keySet()) { + predicate.put(QName.create(namespace, revision, key), keys.get(key)); + } + + return NodeIdentifierWithPredicates.of(QName.create(namespace, revision, localName), predicate); + } + + public static NodeIdentifierWithPredicates getNodeIdentifierPredicate(final String localName, + final String namespace, final String revision, final String... keysAndValues) throws ParseException { + checkArgument(keysAndValues.length % 2 == 0, "number of keys argument have to be divisible by 2 (map)"); + final Map<QName, Object> predicate = new HashMap<>(); + + int index = 0; + while (index < keysAndValues.length) { + predicate.put(QName.create(namespace, revision, keysAndValues[index++]), keysAndValues[index++]); + } + + return NodeIdentifierWithPredicates.of(QName.create(namespace, revision, localName), predicate); + } + + public static NormalizedNode prepareNormalizedNodeWithIetfInterfacesInterfacesData() throws ParseException { + final String ietfInterfacesDate = "2013-07-04"; + final String namespace = "urn:ietf:params:xml:ns:yang:ietf-interfaces"; + final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryNode = + ImmutableMapEntryNodeBuilder.create(); + + final Map<String, Object> predicates = new HashMap<>(); + predicates.put("name", "eth0"); + + mapEntryNode.withNodeIdentifier(getNodeIdentifierPredicate("interface", namespace, ietfInterfacesDate, + predicates)); + mapEntryNode + .withChild(new ImmutableLeafNodeBuilder<String>() + .withNodeIdentifier(getNodeIdentifier("name", namespace, ietfInterfacesDate)).withValue("eth0") + .build()); + mapEntryNode.withChild(new ImmutableLeafNodeBuilder<String>() + .withNodeIdentifier(getNodeIdentifier("type", namespace, ietfInterfacesDate)) + .withValue("ethernetCsmacd").build()); + mapEntryNode.withChild(new ImmutableLeafNodeBuilder<Boolean>() + .withNodeIdentifier(getNodeIdentifier("enabled", namespace, ietfInterfacesDate)) + .withValue(Boolean.FALSE).build()); + mapEntryNode.withChild(new ImmutableLeafNodeBuilder<String>() + .withNodeIdentifier(getNodeIdentifier("description", namespace, ietfInterfacesDate)) + .withValue("some interface").build()); + + return mapEntryNode.build(); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/URIParametersParsing.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/URIParametersParsing.java new file mode 100644 index 0000000..1b61ea1 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/URIParametersParsing.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2014 Cisco 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 java.util.Objects.requireNonNull; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.FileNotFoundException; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext; +import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl; +import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter; +import org.opendaylight.netconf.sal.streams.listeners.Notificator; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.CreateDataChangeEventSubscriptionInput1.Scope; +import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.InstanceIdentifierBuilder; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders; +import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ContainerLike; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.InputSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class URIParametersParsing { + + private RestconfImpl restconf; + private BrokerFacade mockedBrokerFacade; + private ControllerContext controllerContext; + + @Before + public void init() throws FileNotFoundException, ReactorException { + mockedBrokerFacade = mock(BrokerFacade.class); + controllerContext = TestRestconfUtils.newControllerContext( + TestUtils.loadSchemaContext("/datastore-and-scope-specification")); + restconf = RestconfImpl.newInstance(mockedBrokerFacade, controllerContext); + } + + @Test + public void resolveURIParametersConcreteValues() { + resolveURIParameters("OPERATIONAL", "SUBTREE", LogicalDatastoreType.OPERATIONAL, Scope.SUBTREE); + } + + @Test + public void resolveURIParametersDefaultValues() { + resolveURIParameters(null, null, LogicalDatastoreType.CONFIGURATION, Scope.BASE); + } + + private void resolveURIParameters(final String datastore, final String scope, + final LogicalDatastoreType datastoreExpected, final Scope scopeExpected) { + + final InstanceIdentifierBuilder iiBuilder = YangInstanceIdentifier.builder(); + iiBuilder.node(QName.create("", "dummyStreamName")); + + final String datastoreValue = datastore == null ? "CONFIGURATION" : datastore; + final String scopeValue = scope == null ? "BASE" : scope + ""; + Notificator.createListener(iiBuilder.build(), "dummyStreamName/datastore=" + datastoreValue + "/scope=" + + scopeValue, NotificationOutputType.XML, controllerContext); + + final UriInfo mockedUriInfo = mock(UriInfo.class); + @SuppressWarnings("unchecked") + final MultivaluedMap<String, String> mockedMultivaluedMap = mock(MultivaluedMap.class); + when(mockedMultivaluedMap.getFirst(eq("datastore"))).thenReturn(datastoreValue); + when(mockedMultivaluedMap.getFirst(eq("scope"))).thenReturn(scopeValue); + + when(mockedUriInfo.getQueryParameters(eq(false))).thenReturn(mockedMultivaluedMap); + + final UriBuilder uriBuilder = UriBuilder.fromUri("www.whatever.com"); + when(mockedUriInfo.getAbsolutePathBuilder()).thenReturn(uriBuilder); + + restconf.invokeRpc("sal-remote:create-data-change-event-subscription", + prepareDomRpcNode(datastoreValue, scopeValue), mockedUriInfo); + + final ListenerAdapter listener = + Notificator.getListenerFor("data-change-event-subscription/opendaylight-inventory:nodes/datastore=" + + datastoreValue + "/scope=" + scopeValue); + assertNotNull(listener); + } + + private NormalizedNodeContext prepareDomRpcNode(final String datastore, final String scope) { + final EffectiveModelContext schema = controllerContext.getGlobalSchema(); + final Module rpcSalRemoteModule = schema.findModule("sal-remote", Revision.of("2014-01-14")).get(); + final QName rpcQName = + QName.create(rpcSalRemoteModule.getQNameModule(), "create-data-change-event-subscription"); + final RpcDefinition rpcDef = Mockito.mock(RpcDefinition.class); + ContainerLike rpcInputSchemaNode = null; + for (final RpcDefinition rpc : rpcSalRemoteModule.getRpcs()) { + if (rpcQName.isEqualWithoutRevision(rpc.getQName())) { + rpcInputSchemaNode = rpc.getInput(); + break; + } + } + assertNotNull("RPC ContainerSchemaNode was not found!", rpcInputSchemaNode); + + final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> container = + SchemaAwareBuilders.containerBuilder(rpcInputSchemaNode); + + final QName pathQName = + QName.create("urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote", "2014-01-14", "path"); + final DataSchemaNode pathSchemaNode = rpcInputSchemaNode.getDataChildByName(pathQName); + assertTrue(pathSchemaNode instanceof LeafSchemaNode); + final LeafNode<Object> pathNode = SchemaAwareBuilders.leafBuilder((LeafSchemaNode) pathSchemaNode) + .withValue(YangInstanceIdentifier.builder() + .node(QName.create("urn:opendaylight:inventory", "2013-08-19", "nodes")).build()).build(); + container.withChild(pathNode); + + final AugmentationSchemaNode augmentationSchema = requireNonNull(rpcInputSchemaNode.getAvailableAugmentations() + .iterator().next()); + final DataContainerNodeBuilder<AugmentationIdentifier, AugmentationNode> augmentationBuilder = + SchemaAwareBuilders.augmentationBuilder(augmentationSchema); + + final QName dataStoreQName = QName.create("urn:sal:restconf:event:subscription", "2014-07-08", "datastore"); + final DataSchemaNode dsSchemaNode = augmentationSchema.getDataChildByName(dataStoreQName); + assertTrue(dsSchemaNode instanceof LeafSchemaNode); + final LeafNode<Object> dsNode = SchemaAwareBuilders.leafBuilder((LeafSchemaNode) dsSchemaNode) + .withValue(datastore).build(); + augmentationBuilder.withChild(dsNode); + + final QName scopeQName = QName.create("urn:sal:restconf:event:subscription", "2014-07-08", "scope"); + final DataSchemaNode scopeSchemaNode = augmentationSchema.getDataChildByName(scopeQName); + assertTrue(scopeSchemaNode instanceof LeafSchemaNode); + final LeafNode<Object> scopeNode = SchemaAwareBuilders.leafBuilder((LeafSchemaNode) scopeSchemaNode) + .withValue(scope).build(); + augmentationBuilder.withChild(scopeNode); + + final QName outputQName = + QName.create("urn:sal:restconf:event:subscription", "2014-07-08", "notification-output-type"); + final DataSchemaNode outputSchemaNode = augmentationSchema.getDataChildByName(outputQName); + assertTrue(outputSchemaNode instanceof LeafSchemaNode); + final LeafNode<Object> outputNode = + SchemaAwareBuilders.leafBuilder((LeafSchemaNode) outputSchemaNode).withValue("XML").build(); + augmentationBuilder.withChild(outputNode); + + container.withChild(augmentationBuilder.build()); + + when(rpcDef.getInput()).thenReturn((InputSchemaNode) rpcInputSchemaNode); + when(rpcDef.getQName()).thenReturn(rpcQName); + + return new NormalizedNodeContext(InstanceIdentifierContext.ofRpcInput(schema, rpcDef, null), container.build()); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/URITest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/URITest.java new file mode 100644 index 0000000..8178674 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/URITest.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2014 Cisco 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.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import java.io.FileNotFoundException; +import java.util.Optional; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.restconf.common.context.InstanceIdentifierContext; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class URITest { + + private static EffectiveModelContext schemaContext; + private static EffectiveModelContext mountSchemaContext; + + private final DOMMountPoint mountInstance = mock(DOMMountPoint.class); + private final ControllerContext controllerContext = + TestRestconfUtils.newControllerContext(schemaContext, mountInstance); + + @BeforeClass + public static void init() throws FileNotFoundException, ReactorException { + schemaContext = TestUtils.loadSchemaContext("/full-versions/yangs"); + mountSchemaContext = TestUtils.loadSchemaContext("/test-config-data/yang2"); + } + + @Test + public void testToInstanceIdentifierList() { + InstanceIdentifierContext instanceIdentifier = controllerContext + .toInstanceIdentifier("simple-nodes:userWithoutClass/foo"); + assertEquals(instanceIdentifier.getSchemaNode().getQName().getLocalName(), "userWithoutClass"); + + instanceIdentifier = controllerContext.toInstanceIdentifier("simple-nodes:userWithoutClass/foo"); + assertEquals(instanceIdentifier.getSchemaNode().getQName().getLocalName(), "userWithoutClass"); + + instanceIdentifier = controllerContext.toInstanceIdentifier("simple-nodes:user/foo/boo"); + assertEquals(instanceIdentifier.getSchemaNode().getQName().getLocalName(), "user"); + + instanceIdentifier = controllerContext.toInstanceIdentifier("simple-nodes:user//boo"); + assertEquals(instanceIdentifier.getSchemaNode().getQName().getLocalName(), "user"); + + } + + @Test + public void testToInstanceIdentifierWithDoubleSlash() { + InstanceIdentifierContext instanceIdentifier = controllerContext + .toInstanceIdentifier("simple-nodes:food//nonalcoholic"); + assertEquals(instanceIdentifier.getSchemaNode().getQName().getLocalName(), "nonalcoholic"); + + instanceIdentifier = controllerContext + .toInstanceIdentifier("simple-nodes:userWithoutClass//"); + assertEquals(instanceIdentifier.getSchemaNode().getQName().getLocalName(), "userWithoutClass"); + + instanceIdentifier = controllerContext + .toInstanceIdentifier("simple-nodes:userWithoutClass///inner-container"); + assertEquals(instanceIdentifier.getSchemaNode().getQName().getLocalName(), "inner-container"); + } + + @Test + public void testToInstanceIdentifierListWithNullKey() { + assertThrows(RestconfDocumentedException.class, + () -> controllerContext.toInstanceIdentifier("simple-nodes:user/null/boo")); + } + + @Test + public void testToInstanceIdentifierListWithMissingKey() { + assertThrows(RestconfDocumentedException.class, + () -> controllerContext.toInstanceIdentifier("simple-nodes:user/foo")); + } + + @Test + public void testToInstanceIdentifierContainer() { + final InstanceIdentifierContext instanceIdentifier = + controllerContext.toInstanceIdentifier("simple-nodes:users"); + assertEquals(instanceIdentifier.getSchemaNode().getQName().getLocalName(), "users"); + assertTrue(instanceIdentifier.getSchemaNode() instanceof ContainerSchemaNode); + assertEquals(2, ((ContainerSchemaNode) instanceIdentifier.getSchemaNode()).getChildNodes().size()); + } + + @Test + public void testToInstanceIdentifierChoice() { + final InstanceIdentifierContext instanceIdentifier = controllerContext + .toInstanceIdentifier("simple-nodes:food/nonalcoholic"); + assertEquals(instanceIdentifier.getSchemaNode().getQName().getLocalName(), "nonalcoholic"); + } + + @Test + public void testToInstanceIdentifierChoiceException() { + assertThrows(RestconfDocumentedException.class, + () -> controllerContext.toInstanceIdentifier("simple-nodes:food/snack")); + } + + @Test + public void testToInstanceIdentifierCaseException() { + assertThrows(RestconfDocumentedException.class, + () -> controllerContext.toInstanceIdentifier("simple-nodes:food/sports-arena")); + } + + @Test + public void testToInstanceIdentifierChoiceCaseException() { + assertThrows(RestconfDocumentedException.class, + () -> controllerContext.toInstanceIdentifier("simple-nodes:food/snack/sports-arena")); + } + + @Test + public void testToInstanceIdentifierWithoutNode() { + assertThrows(RestconfDocumentedException.class, + () -> controllerContext.toInstanceIdentifier("simple-nodes")); + } + + @Test + public void testMountPointWithExternModul() { + initSchemaService(); + final InstanceIdentifierContext instanceIdentifier = controllerContext + .toInstanceIdentifier("simple-nodes:users/yang-ext:mount/test-interface2:class/student/name"); + assertEquals( + "[(urn:ietf:params:xml:ns:yang:test-interface2?revision=2014-08-01)class, " + + "(urn:ietf:params:xml:ns:yang:test-interface2?revision=2014-08-01)student, " + + "(urn:ietf:params:xml:ns:yang:test-interface2?revision=2014-08-01)student" + + "[{(urn:ietf:params:xml:ns:yang:test-interface2?revision=2014-08-01)name=name}]]", + ImmutableList.copyOf(instanceIdentifier.getInstanceIdentifier().getPathArguments()).toString()); + } + + @Test + public void testMountPointWithoutExternModul() { + initSchemaService(); + final InstanceIdentifierContext instanceIdentifier = controllerContext + .toInstanceIdentifier("simple-nodes:users/yang-ext:mount/"); + assertTrue(Iterables.isEmpty(instanceIdentifier.getInstanceIdentifier().getPathArguments())); + } + + @Test + public void testMountPointWithoutMountPointSchema() { + assertThrows(RestconfDocumentedException.class, + () -> controllerContext.toInstanceIdentifier("simple-nodes:users/yang-ext:mount/test-interface2:class")); + } + + private void initSchemaService() { + doReturn(Optional.of(FixedDOMSchemaService.of(mountSchemaContext))).when(mountInstance) + .getService(DOMSchemaService.class); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlAndJsonToCnSnInstanceIdentifierTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlAndJsonToCnSnInstanceIdentifierTest.java new file mode 100644 index 0000000..b0dd7a2 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlAndJsonToCnSnInstanceIdentifierTest.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2014 Cisco 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 java.io.FileNotFoundException; +import org.junit.BeforeClass; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class XmlAndJsonToCnSnInstanceIdentifierTest extends YangAndXmlAndDataSchemaLoader { + + @BeforeClass + public static void initialize() throws FileNotFoundException, ReactorException { + dataLoad("/instanceidentifier/yang", 4, "instance-identifier-module", "cont"); + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlAndJsonToCnSnLeafRefTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlAndJsonToCnSnLeafRefTest.java new file mode 100644 index 0000000..df13bb8 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlAndJsonToCnSnLeafRefTest.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2014 Cisco 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 java.io.FileNotFoundException; +import org.junit.BeforeClass; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class XmlAndJsonToCnSnLeafRefTest extends YangAndXmlAndDataSchemaLoader { + + final QName refContQName = QName.create("referenced:module", "2014-04-17", "cont"); + final QName refLf1QName = QName.create(this.refContQName, "lf1"); + final QName contQName = QName.create("leafref:module", "2014-04-17", "cont"); + final QName lf1QName = QName.create(this.contQName, "lf1"); + final QName lf2QName = QName.create(this.contQName, "lf2"); + final QName lf3QName = QName.create(this.contQName, "lf3"); + + @BeforeClass + public static void initialize() throws FileNotFoundException, ReactorException { + dataLoad("/leafref/yang", 2, "leafref-module", "cont"); + } + + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/YangAndXmlAndDataSchemaLoader.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/YangAndXmlAndDataSchemaLoader.java new file mode 100644 index 0000000..9192691 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/YangAndXmlAndDataSchemaLoader.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2014 Cisco 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.assertNotNull; + +import java.io.FileNotFoundException; +import java.util.Collection; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; + +public abstract class YangAndXmlAndDataSchemaLoader { + protected static Collection<? extends Module> modules; + protected static DataSchemaNode dataSchemaNode; + protected static String searchedModuleName; + protected static String searchedDataSchemaName; + protected static String schemaNodePath; + + protected static void dataLoad(final String yangPath) throws FileNotFoundException { + dataLoad(yangPath, 1, null, null); + } + + protected static void dataLoad(final String yangPath, final int modulesNumber, final String moduleName, + final String dataSchemaName) throws FileNotFoundException { + modules = TestUtils.loadSchemaContext(yangPath).getModules(); + assertEquals(modulesNumber, modules.size()); + final Module module = TestUtils.resolveModule(moduleName, modules); + searchedModuleName = module == null ? "" : module.getName(); + assertNotNull(module); + dataSchemaNode = TestUtils.resolveDataSchemaNode(dataSchemaName, module); + searchedDataSchemaName = dataSchemaNode == null ? "" : dataSchemaNode.getQName().getLocalName(); + assertNotNull(dataSchemaNode); + schemaNodePath = searchedModuleName + ":" + searchedDataSchemaName; + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/incubate/InMemoryMdsalModule.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/incubate/InMemoryMdsalModule.java new file mode 100644 index 0000000..cabdc6c --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/incubate/InMemoryMdsalModule.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2019 Red Hat, 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.incubate; + +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import javax.annotation.PreDestroy; +import javax.inject.Singleton; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.binding.dom.adapter.test.AbstractBaseDataBrokerTest; +import org.opendaylight.mdsal.binding.dom.adapter.test.AbstractConcurrentDataBrokerTest; +import org.opendaylight.mdsal.dom.api.DOMDataBroker; +import org.opendaylight.mdsal.dom.api.DOMMountPointService; +import org.opendaylight.mdsal.dom.api.DOMNotificationPublishService; +import org.opendaylight.mdsal.dom.api.DOMNotificationService; +import org.opendaylight.mdsal.dom.api.DOMRpcService; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.mdsal.dom.broker.DOMMountPointServiceImpl; +import org.opendaylight.mdsal.dom.broker.DOMNotificationRouter; +import org.opendaylight.mdsal.dom.broker.DOMRpcRouter; +import org.opendaylight.mdsal.dom.spi.DOMNotificationSubscriptionListenerRegistry; +import org.opendaylight.mdsal.dom.store.inmemory.InMemoryDOMDataStore; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider; + +/** + * Guice Module which binds the mdsal (not controller) {@link DataBroker} & Co. + * in-memory implementation suitable for tests. + * + * <p>This class is here only temporarily and it can and should be removed and + * replaced when the equivalent will be offered by the mdsal project itself; see + * <a href="https://jira.opendaylight.org/browse/MDSAL-418">MDSAL-418</a>. It is + * also copy/pasted to org.opendaylight.restconf.nb.rfc8040.test.incubate.InMemoryMdsalModule. + * + * <p>BEWARE: Do *NOT* use this module in component tests or applications mixing + * code requiring the old controller and the new mdsal {@link DataBroker} & Co. + * APIs together - because this binds a *SEPARATE* {@link InMemoryDOMDataStore}, + * and doesn't delegate to controller's InMemoryDOMDataStore. This is just fine + * for tests where all code under test already uses only the mdsal APIs. + * + * @author Michael Vorburger.ch + */ +public class InMemoryMdsalModule extends AbstractModule { + + private static final int NOTIFICATION_SERVICE_QUEUE_DEPTH = 128; + + private final AbstractBaseDataBrokerTest dataBrokerTest; + private final DOMNotificationRouter domNotificationRouter; + + public InMemoryMdsalModule() throws Exception { + dataBrokerTest = new AbstractConcurrentDataBrokerTest(true) { // NOT AbstractDataBrokerTest + }; + dataBrokerTest.setup(); + + domNotificationRouter = DOMNotificationRouter.create(NOTIFICATION_SERVICE_QUEUE_DEPTH); + } + + @Override + protected void configure() { + } + + @Provides + @Singleton + DataBroker getDataBroker() { + return dataBrokerTest.getDataBroker(); + } + + @Provides + @Singleton DOMDataBroker getDOMDataBroker() { + return dataBrokerTest.getDomBroker(); + } + + @Provides + @Singleton DOMNotificationRouter getDOMNotificationRouter() { + return dataBrokerTest.getDataBrokerTestCustomizer().getDomNotificationRouter(); + } + + @Provides + @Singleton DOMSchemaService getSchemaService() { + return dataBrokerTest.getDataBrokerTestCustomizer().getSchemaService(); + } + + @Provides + @Singleton EffectiveModelContextProvider getSchemaContextProvider() { + DOMSchemaService schemaService = dataBrokerTest.getDataBrokerTestCustomizer().getSchemaService(); + if (schemaService instanceof EffectiveModelContextProvider) { + return (EffectiveModelContextProvider) schemaService; + } + throw new IllegalStateException( + "The schema service isn't a SchemaContextProvider, it's a " + schemaService.getClass()); + } + + @Provides + @Singleton DOMMountPointService getDOMMountPoint() { + return new DOMMountPointServiceImpl(); + } + + @Provides + @Singleton DOMNotificationService getDOMNotificationService() { + return domNotificationRouter; + } + + @Provides + @Singleton DOMNotificationPublishService getDOMNotificationPublishService() { + return domNotificationRouter; + } + + @Provides + @Singleton DOMNotificationSubscriptionListenerRegistry getDOMNotificationSubscriptionListenerRegistry() { + return domNotificationRouter; + } + + @Provides + @Singleton DOMRpcService getDOMRpcService(DOMSchemaService schemaService) { + return DOMRpcRouter.newInstance(schemaService).getRpcService(); + } + + @PreDestroy + public void close() { + // TODO When moving this to mdsal, must close components to shut down Threads etc. + // but cannot do this here (in netconf) yet, because we need to change AbstractBaseDataBrokerTest & Co.. + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/incubate/InMemoryMdsalModuleTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/incubate/InMemoryMdsalModuleTest.java new file mode 100644 index 0000000..6e01ecd --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/incubate/InMemoryMdsalModuleTest.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Red Hat, 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.incubate; + +import java.util.concurrent.ExecutionException; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.opendaylight.infrautils.inject.guice.testutils.AnnotationsModule; +import org.opendaylight.infrautils.inject.guice.testutils.GuiceRule; +import org.opendaylight.mdsal.binding.api.DataBroker; + +/** + * Test for {@link InMemoryMdsalModule}. + * + * <p>This will be removed when the local {@link InMemoryMdsalModule} incubating here + * in netconf will be replaced by the one from mdsal. + * + * @author Michael Vorburger.ch + */ +public class InMemoryMdsalModuleTest { + + public @Rule GuiceRule guice = new GuiceRule(InMemoryMdsalModule.class, AnnotationsModule.class); + + @Inject DataBroker dataBroker; + + @Test public void testDataBroker() throws InterruptedException, ExecutionException { + dataBroker.newReadWriteTransaction().commit().get(); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/websockets/client/IClientMessageCallback.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/websockets/client/IClientMessageCallback.java new file mode 100644 index 0000000..a5323fb --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/websockets/client/IClientMessageCallback.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2014, 2015 Cisco 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.websockets.client; + +/** + * Created by mbobak on 1/22/14. + */ +public interface IClientMessageCallback { + + void onMessageReceived(Object message); +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/websockets/client/WebSocketClient.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/websockets/client/WebSocketClient.java new file mode 100644 index 0000000..a67a491 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/websockets/client/WebSocketClient.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2013 Cisco 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.websockets.client; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; +import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory; +import io.netty.handler.codec.http.websocketx.WebSocketVersion; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URI; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WebSocketClient { + + private static final Logger LOG = LoggerFactory.getLogger(WebSocketClient.class); + + private final URI uri; + private final Bootstrap bootstrap = new Bootstrap(); + private final WebSocketClientHandler clientHandler; + private Channel clientChannel; + private final EventLoopGroup group = new NioEventLoopGroup(); + + public WebSocketClient(final URI uri, final IClientMessageCallback clientMessageCallback) { + this.uri = uri; + clientHandler = new WebSocketClientHandler(WebSocketClientHandshakerFactory.newHandshaker(uri, + WebSocketVersion.V13, null, false, null), clientMessageCallback); + // last null could be replaced with DefaultHttpHeaders + initialize(); + } + + private void initialize() { + + String protocol = uri.getScheme(); + if (!"ws".equals(protocol)) { + throw new IllegalArgumentException("Unsupported protocol: " + protocol); + } + + bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() { + @Override + public void initChannel(final SocketChannel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast("http-codec", new HttpClientCodec()); + pipeline.addLast("aggregator", new HttpObjectAggregator(8192)); + pipeline.addLast("ws-handler", clientHandler); + } + }); + } + + public void connect() throws InterruptedException { + LOG.info("WebSocket Client connecting"); + clientChannel = bootstrap.connect(uri.getHost(), uri.getPort()).sync().channel(); + clientHandler.handshakeFuture().sync(); + } + + public void writeAndFlush(final String message) { + clientChannel.writeAndFlush(new TextWebSocketFrame(message)); + } + + public void writeAndFlush(final Object message) { + clientChannel.writeAndFlush(message); + } + + public void ping() { + clientChannel.writeAndFlush(new PingWebSocketFrame(Unpooled.copiedBuffer(new byte[] { 1, 2, 3, 4, 5, 6 }))); + } + + public void close(final String reasonText) throws InterruptedException { + CloseWebSocketFrame closeWebSocketFrame = new CloseWebSocketFrame(1000, reasonText); + clientChannel.writeAndFlush(closeWebSocketFrame); + + // WebSocketClientHandler will close the connection when the server + // responds to the CloseWebSocketFrame. + clientChannel.closeFuture().sync(); + group.shutdownGracefully(); + } + + public static void main(final String[] args) throws Exception { + URI uri; + if (args.length > 0) { + uri = new URI(args[0]); + } else { + uri = new URI("http://192.168.1.101:8181/opendaylight-inventory:nodes"); + } + IClientMessageCallback messageCallback = new ClientMessageCallback(); + WebSocketClient webSocketClient = new WebSocketClient(uri, messageCallback); + webSocketClient.connect(); + + while (true) { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + String input = br.readLine(); + if (input.equals("q")) { + LOG.info("Would you like to close stream? (Y = yes, empty = yes)\n"); + input = br.readLine(); + if (input.equals("yes") || input.isEmpty()) { + webSocketClient.close("opendaylight-inventory:nodes"); + break; + } + } + } + } + + private static class ClientMessageCallback implements IClientMessageCallback { + @Override + public void onMessageReceived(final Object message) { + if (message instanceof TextWebSocketFrame) { + LOG.info("received message {}" + ((TextWebSocketFrame) message).text()); + } + } + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/websockets/client/WebSocketClientHandler.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/websockets/client/WebSocketClientHandler.java new file mode 100644 index 0000000..19dbbc3 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/websockets/client/WebSocketClientHandler.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2013 Cisco 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.websockets.client; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; +import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import io.netty.util.CharsetUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> { + + private static final Logger LOG = LoggerFactory.getLogger(WebSocketClientHandler.class.toString()); + private final WebSocketClientHandshaker handshaker; + private ChannelPromise handshakeFuture; + private final IClientMessageCallback messageListener; + + public WebSocketClientHandler(final WebSocketClientHandshaker handshaker, final IClientMessageCallback listener) { + this.handshaker = handshaker; + this.messageListener = listener; + } + + public ChannelFuture handshakeFuture() { + return handshakeFuture; + } + + @Override + public void handlerAdded(final ChannelHandlerContext ctx) throws Exception { + handshakeFuture = ctx.newPromise(); + } + + @Override + public void channelActive(final ChannelHandlerContext ctx) throws Exception { + handshaker.handshake(ctx.channel()); + } + + @Override + public void channelInactive(final ChannelHandlerContext ctx) throws Exception { + LOG.info("WebSocket Client disconnected!"); + } + + @Override + public void channelRead0(final ChannelHandlerContext ctx, final Object msg) throws Exception { + Channel ch = ctx.channel(); + if (!handshaker.isHandshakeComplete()) { + handshaker.finishHandshake(ch, (FullHttpResponse) msg); + LOG.info("WebSocket Client connected!"); + handshakeFuture.setSuccess(); + return; + } + + if (msg instanceof FullHttpResponse) { + FullHttpResponse response = (FullHttpResponse) msg; + throw new Exception("Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + + response.content().toString(CharsetUtil.UTF_8) + ')'); + } + + messageListener.onMessageReceived(msg); + WebSocketFrame frame = (WebSocketFrame) msg; + + if (frame instanceof PongWebSocketFrame) { + LOG.info("WebSocket Client received pong"); + } else if (frame instanceof CloseWebSocketFrame) { + LOG.info("WebSocket Client received closing"); + ch.close(); + } + } + + @Override + public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception { + LOG.info("Cause: {} .", cause.toString()); + + if (!handshakeFuture.isDone()) { + handshakeFuture.setFailure(cause); + } + + ctx.close(); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/websockets/test/RestStreamTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/websockets/test/RestStreamTest.java new file mode 100644 index 0000000..0163ce0 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/websockets/test/RestStreamTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2014 Cisco 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.websockets.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import java.io.FileNotFoundException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.controller.sal.restconf.impl.test.TestUtils; +import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader; +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.BrokerFacade; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +public class RestStreamTest extends JerseyTest { + + private static EffectiveModelContext schemaContextYangsIetf; + + private BrokerFacade brokerFacade; + private RestconfImpl restconfImpl; + + @BeforeClass + public static void init() throws FileNotFoundException, ReactorException { + schemaContextYangsIetf = TestUtils.loadSchemaContext("/full-versions/yangs"); + } + + @Override + protected Application configure() { + /* enable/disable Jersey logs to console */ + // enable(TestProperties.LOG_TRAFFIC); + // enable(TestProperties.DUMP_ENTITY); + // enable(TestProperties.RECORD_LOG_LEVEL); + // set(TestProperties.RECORD_LOG_LEVEL, Level.ALL.intValue()); + + final ControllerContext controllerContext = TestRestconfUtils.newControllerContext(schemaContextYangsIetf); + brokerFacade = mock(BrokerFacade.class); + restconfImpl = RestconfImpl.newInstance(brokerFacade, controllerContext); + + ResourceConfig resourceConfig = new ResourceConfig(); + resourceConfig = resourceConfig.registerInstances(restconfImpl, new NormalizedNodeJsonBodyWriter(), + new NormalizedNodeXmlBodyWriter(), new XmlNormalizedNodeBodyReader(controllerContext), + new JsonNormalizedNodeBodyReader(controllerContext), + new RestconfDocumentedExceptionMapper(controllerContext)); + return resourceConfig; + } + + @Test + @Ignore // Sporadic failures where jersey does not correctly pass post data to XmlNormalizedNodeBodyReader.readFrom + public void testCallRpcCallGet() throws UnsupportedEncodingException, InterruptedException { + createAndSubscribe(null); + } + + @Test + @Ignore // Sporadic failures where jersey does not correctly pass post data to XmlNormalizedNodeBodyReader.readFrom + public void testCallRpcCallGetLeaves() throws UnsupportedEncodingException, InterruptedException { + createAndSubscribe("odl-leaf-nodes-only", "true"); + } + + private void createAndSubscribe(final String queryParamName, final Object... values) + throws UnsupportedEncodingException, InterruptedException { + String uri = "/operations/sal-remote:create-data-change-event-subscription"; + String rpcInput = getRpcInput(); + final Response responseWithStreamName = post(uri, MediaType.APPLICATION_XML, rpcInput); + final Document xmlResponse = responseWithStreamName.readEntity(Document.class); + assertNotNull(xmlResponse); + final Element outputElement = xmlResponse.getDocumentElement(); + assertEquals("output",outputElement.getLocalName()); + + final Node streamNameElement = outputElement.getFirstChild(); + assertEquals("stream-name",streamNameElement.getLocalName()); + assertEquals("data-change-event-subscription/ietf-interfaces:interfaces/ietf-interfaces:interface/eth0/" + + "datastore=CONFIGURATION/scope=BASE",streamNameElement.getTextContent()); + + uri = "/streams/stream/data-change-event-subscription/ietf-interfaces:interfaces/ietf-interfaces:interface/" + + "eth0/datastore=CONFIGURATION/scope=BASE"; + final Response responseWithRedirectionUri = get(uri, MediaType.APPLICATION_XML, null); + final URI websocketServerUri = responseWithRedirectionUri.getLocation(); + assertNotNull(websocketServerUri); + assertTrue(websocketServerUri.toString().matches(".*ws://localhost:[\\d]+/data-change-event-subscription/" + + "ietf-interfaces:interfaces/ietf-interfaces:interface/eth0.*")); + } + + private Response post(final String uri, final String mediaType, final String data) { + return target(uri).request(mediaType).post(Entity.entity(data, mediaType)); + } + + private Response get(final String uri, final String mediaType, final String queryParam, final Object... values) { + if (queryParam != null) { + return target(uri).queryParam(queryParam, values).request(mediaType).get(); + } else { + return target(uri).request(mediaType).get(); + } + } + + private static String getRpcInput() { + final StringBuilder sb = new StringBuilder(); + sb.append("<input xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote\">"); + sb.append("<path xmlns:int=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">/" + + "int:interfaces/int:interface[int:name='eth0']</path>"); + sb.append("</input>"); + return sb.toString(); + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/xml/to/cnsn/test/XmlAugmentedElementToCnSnTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/xml/to/cnsn/test/XmlAugmentedElementToCnSnTest.java new file mode 100644 index 0000000..2a6f7de --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/xml/to/cnsn/test/XmlAugmentedElementToCnSnTest.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2014 Cisco 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.xml.to.cnsn.test; + + +public class XmlAugmentedElementToCnSnTest { + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/xml/to/cnsn/test/XmlToCnSnTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/xml/to/cnsn/test/XmlToCnSnTest.java new file mode 100644 index 0000000..1cac018 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/xml/to/cnsn/test/XmlToCnSnTest.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014 Cisco 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.xml.to.cnsn.test; + +import java.io.FileNotFoundException; +import org.junit.BeforeClass; +import org.opendaylight.controller.sal.restconf.impl.test.YangAndXmlAndDataSchemaLoader; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class XmlToCnSnTest extends YangAndXmlAndDataSchemaLoader { + + @BeforeClass + public static void initialize() throws FileNotFoundException, ReactorException { + dataLoad("/xml-to-cnsn/leafref"); + } + +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/rest/impl/DepthAwareNormalizedNodeWriterTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/rest/impl/DepthAwareNormalizedNodeWriterTest.java new file mode 100644 index 0000000..58debcb --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/rest/impl/DepthAwareNormalizedNodeWriterTest.java @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2016 Cisco 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.netconf.sal.rest.impl; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.google.common.collect.Sets; +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; + +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class DepthAwareNormalizedNodeWriterTest { + + @Mock + private NormalizedNodeStreamWriter writer; + @Mock + private ContainerNode containerNodeData; + @Mock + private MapNode mapNodeData; + @Mock + private MapEntryNode mapEntryNodeData; + @Mock + private LeafSetNode<String> leafSetNodeData; + @Mock + private LeafSetEntryNode<String> leafSetEntryNodeData; + @Mock + private LeafNode<String> keyLeafNodeData; + @Mock + private LeafNode<String> anotherLeafNodeData; + + private NodeIdentifier containerNodeIdentifier; + private NodeIdentifier mapNodeIdentifier; + private NodeIdentifierWithPredicates mapEntryNodeIdentifier; + private NodeIdentifier leafSetNodeIdentifier; + private NodeWithValue<String> leafSetEntryNodeIdentifier; + private NodeIdentifier keyLeafNodeIdentifier; + private NodeIdentifier anotherLeafNodeIdentifier; + + private Collection<DataContainerChild> containerNodeValue; + private Collection<MapEntryNode> mapNodeValue; + private Collection<DataContainerChild> mapEntryNodeValue; + private Collection<LeafSetEntryNode<String>> leafSetNodeValue; + private String leafSetEntryNodeValue; + private String keyLeafNodeValue; + private String anotherLeafNodeValue; + + @Before + public void setUp() { + // identifiers + containerNodeIdentifier = NodeIdentifier.create(QName.create("namespace", "container")); + when(containerNodeData.getIdentifier()).thenReturn(containerNodeIdentifier); + + mapNodeIdentifier = NodeIdentifier.create(QName.create("namespace", "list")); + when(mapNodeData.getIdentifier()).thenReturn(mapNodeIdentifier); + + final QName leafSetEntryNodeQName = QName.create("namespace", "leaf-set-entry"); + leafSetEntryNodeValue = "leaf-set-value"; + leafSetEntryNodeIdentifier = new NodeWithValue<>(leafSetEntryNodeQName, leafSetEntryNodeValue); + when(leafSetEntryNodeData.getIdentifier()).thenReturn(leafSetEntryNodeIdentifier); + + leafSetNodeIdentifier = NodeIdentifier.create(QName.create("namespace", "leaf-set")); + when(leafSetNodeData.getIdentifier()).thenReturn(leafSetNodeIdentifier); + + final QName mapEntryNodeKey = QName.create("namespace", "key-field"); + keyLeafNodeIdentifier = NodeIdentifier.create(mapEntryNodeKey); + keyLeafNodeValue = "key-value"; + + mapEntryNodeIdentifier = NodeIdentifierWithPredicates.of( + QName.create("namespace", "list-entry"), mapEntryNodeKey, keyLeafNodeValue); + when(mapEntryNodeData.getIdentifier()).thenReturn(mapEntryNodeIdentifier); + when(mapEntryNodeData.findChildByArg(keyLeafNodeIdentifier)).thenReturn(Optional.of(keyLeafNodeData)); + + when(keyLeafNodeData.body()).thenReturn(keyLeafNodeValue); + when(keyLeafNodeData.getIdentifier()).thenReturn(keyLeafNodeIdentifier); + + anotherLeafNodeIdentifier = NodeIdentifier.create(QName.create("namespace", "another-field")); + anotherLeafNodeValue = "another-value"; + + when(anotherLeafNodeData.body()).thenReturn(anotherLeafNodeValue); + when(anotherLeafNodeData.getIdentifier()).thenReturn(anotherLeafNodeIdentifier); + + // values + when(leafSetEntryNodeData.body()).thenReturn(leafSetEntryNodeValue); + + leafSetNodeValue = Collections.singletonList(leafSetEntryNodeData); + when(leafSetNodeData.body()).thenReturn(leafSetNodeValue); + + containerNodeValue = Collections.singleton(leafSetNodeData); + when(containerNodeData.body()).thenReturn(containerNodeValue); + + mapEntryNodeValue = Sets.newHashSet(keyLeafNodeData, anotherLeafNodeData); + when(mapEntryNodeData.body()).thenReturn(mapEntryNodeValue); + + mapNodeValue = Collections.singleton(mapEntryNodeData); + when(mapNodeData.body()).thenReturn(mapNodeValue); + } + + /** + * Test write {@link ContainerNode} with children but write data only to depth 1 (children will not be written). + */ + @Test + public void writeContainerWithoutChildrenTest() throws Exception { + final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(writer, 1); + + depthWriter.write(containerNodeData); + + final InOrder inOrder = inOrder(writer); + inOrder.verify(writer, times(1)).startContainerNode(containerNodeIdentifier, containerNodeValue.size()); + inOrder.verify(writer, times(1)).endNode(); + verifyNoMoreInteractions(writer); + } + + /** + * Test write {@link ContainerNode} with children and write also all its children. + */ + @Test + public void writeContainerWithChildrenTest() throws Exception { + final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter( + writer, Integer.MAX_VALUE); + + depthWriter.write(containerNodeData); + + final InOrder inOrder = inOrder(writer); + inOrder.verify(writer, times(1)).startContainerNode(containerNodeIdentifier, containerNodeValue.size()); + inOrder.verify(writer, times(1)).startLeafSet(leafSetNodeIdentifier, leafSetNodeValue.size()); + inOrder.verify(writer, times(1)).startLeafSetEntryNode(leafSetEntryNodeIdentifier); + inOrder.verify(writer, times(1)).scalarValue(leafSetEntryNodeValue); + inOrder.verify(writer, times(3)).endNode(); + verifyNoMoreInteractions(writer); + } + + /** + * Test write with {@link MapNode} with children but write data only to depth 1 (children will not be written). + */ + @Test + public void writeMapNodeWithoutChildrenTest() throws Exception { + final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(writer, 1); + + depthWriter.write(mapNodeData); + + final InOrder inOrder = inOrder(writer); + inOrder.verify(writer, times(1)).startMapNode(mapNodeIdentifier, mapNodeValue.size()); + inOrder.verify(writer, times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size()); + inOrder.verify(writer, times(1)).startLeafNode(keyLeafNodeIdentifier); + inOrder.verify(writer, times(1)).scalarValue(keyLeafNodeValue); + inOrder.verify(writer, times(3)).endNode(); + verifyNoMoreInteractions(writer); + } + + /** + * Test write {@link MapNode} with children and write also all its children. + * FIXME + * Although ordered writer is used leaves are not written in expected order. + * + */ + @Ignore + @Test + public void writeMapNodeWithChildrenTest() throws Exception { + final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter( + writer, Integer.MAX_VALUE); + + depthWriter.write(mapNodeData); + + final InOrder inOrder = inOrder(writer); + inOrder.verify(writer, times(1)).startMapNode(mapNodeIdentifier, mapNodeValue.size()); + inOrder.verify(writer, times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size()); + inOrder.verify(writer, times(1)).startLeafNode(keyLeafNodeIdentifier); + inOrder.verify(writer, times(1)).scalarValue(keyLeafNodeValue); + inOrder.verify(writer, times(1)).endNode(); + inOrder.verify(writer, times(1)).startLeafNode(keyLeafNodeIdentifier); + inOrder.verify(writer, times(1)).scalarValue(keyLeafNodeValue); + inOrder.verify(writer, times(1)).endNode(); + + // FIXME this assertion is not working because leaves are not written in expected order + inOrder.verify(writer, times(1)).startLeafNode(anotherLeafNodeIdentifier); + inOrder.verify(writer, times(1)).scalarValue(anotherLeafNodeValue); + inOrder.verify(writer, times(3)).endNode(); + verifyNoMoreInteractions(writer); + } + + /** + * Test write with {@link LeafSetNode} with depth 1 (children will not be written). + */ + @Test + public void writeLeafSetNodeWithoutChildrenTest() throws Exception { + final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter( + writer, 1); + + depthWriter.write(leafSetNodeData); + + final InOrder inOrder = inOrder(writer); + inOrder.verify(writer, times(1)).startLeafSet(leafSetNodeIdentifier, leafSetNodeValue.size()); + inOrder.verify(writer, times(1)).endNode(); + verifyNoMoreInteractions(writer); + } + + /** + * Test write with {@link LeafSetNode} when all its children will be written. + */ + @Test + public void writeLeafSetNodeWithChildrenTest() throws Exception { + final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter( + writer, Integer.MAX_VALUE); + + depthWriter.write(leafSetNodeData); + + final InOrder inOrder = inOrder(writer); + inOrder.verify(writer, times(1)).startLeafSet(leafSetNodeIdentifier, leafSetNodeValue.size()); + inOrder.verify(writer, times(1)).startLeafSetEntryNode(leafSetEntryNodeIdentifier); + inOrder.verify(writer, times(1)).scalarValue(leafSetEntryNodeValue); + inOrder.verify(writer, times(2)).endNode(); + verifyNoMoreInteractions(writer); + } + + /** + * Test write with {@link LeafSetEntryNode}. + */ + @Test + public void writeLeafSetEntryNodeTest() throws Exception { + final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter( + writer, Integer.MAX_VALUE); + + depthWriter.write(leafSetEntryNodeData); + + final InOrder inOrder = inOrder(writer); + inOrder.verify(writer, times(1)).startLeafSetEntryNode(leafSetEntryNodeIdentifier); + inOrder.verify(writer, times(1)).scalarValue(leafSetEntryNodeValue); + inOrder.verify(writer, times(1)).endNode(); + verifyNoMoreInteractions(writer); + } + + /** + * Test write with {@link MapEntryNode} unordered to depth 1 to write only keys. + */ + @Test + public void writeMapEntryNodeUnorderedOnlyKeysTest() throws Exception { + final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter( + writer, false, 1); + + depthWriter.write(mapEntryNodeData); + + final InOrder inOrder = inOrder(writer); + inOrder.verify(writer, times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size()); + // write only the key + inOrder.verify(writer, times(1)).startLeafNode(keyLeafNodeIdentifier); + inOrder.verify(writer, times(1)).scalarValue(keyLeafNodeValue); + inOrder.verify(writer, times(2)).endNode(); + verifyNoMoreInteractions(writer); + } + + /** + * Test write with {@link MapEntryNode} unordered with full depth. + */ + @Test + public void writeMapEntryNodeUnorderedTest() throws Exception { + final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter( + writer, false, Integer.MAX_VALUE); + + depthWriter.write(mapEntryNodeData); + + // unordered + verify(writer, times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size()); + verify(writer, times(1)).startLeafNode(keyLeafNodeIdentifier); + verify(writer, times(1)).scalarValue(keyLeafNodeValue); + verify(writer, times(1)).startLeafNode(anotherLeafNodeIdentifier); + verify(writer, times(1)).scalarValue(anotherLeafNodeValue); + verify(writer, times(3)).endNode(); + verifyNoMoreInteractions(writer); + } + + /** + * Test write with {@link MapEntryNode} ordered with depth 1 (children will not be written). + */ + @Test + public void writeMapEntryNodeOrderedWithoutChildrenTest() throws Exception { + final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter( + writer, true, 1); + + depthWriter.write(mapEntryNodeData); + + final InOrder inOrder = inOrder(writer); + inOrder.verify(writer, times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size()); + inOrder.verify(writer, times(1)).startLeafNode(keyLeafNodeIdentifier); + inOrder.verify(writer, times(1)).scalarValue(keyLeafNodeValue); + inOrder.verify(writer, times(2)).endNode(); + verifyNoMoreInteractions(writer); + } + + /** + * Test write with {@link MapEntryNode} ordered and write also all its children. + * FIXME + * Although ordered writer is used leaves are not written in expected order. + * + */ + @Ignore + @Test + public void writeMapEntryNodeOrderedTest() throws Exception { + final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter( + writer, true, Integer.MAX_VALUE); + + depthWriter.write(mapEntryNodeData); + + final InOrder inOrder = inOrder(writer); + inOrder.verify(writer, times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size()); + inOrder.verify(writer, times(2)).startLeafNode(keyLeafNodeIdentifier); + inOrder.verify(writer, times(2)).scalarValue(keyLeafNodeValue); + inOrder.verify(writer, times(1)).endNode(); + // FIXME this assertion is not working because leaves are not written in expected order + inOrder.verify(writer, times(1)).startLeafNode(anotherLeafNodeIdentifier); + inOrder.verify(writer, times(2)).scalarValue(anotherLeafNodeValue); + inOrder.verify(writer, times(2)).endNode(); + verifyNoMoreInteractions(writer); + } +}
\ No newline at end of file diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/restconf/impl/InstanceIdentifierCodecImplTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/restconf/impl/InstanceIdentifierCodecImplTest.java new file mode 100644 index 0000000..e0d48bb --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/restconf/impl/InstanceIdentifierCodecImplTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2016 Cisco 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.netconf.sal.restconf.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.io.FileNotFoundException; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.netconf.sal.restconf.impl.RestCodec.InstanceIdentifierCodecImpl; +import org.opendaylight.restconf.common.util.IdentityValuesDTO; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; + +public class InstanceIdentifierCodecImplTest { + private static EffectiveModelContext schemaContext; + + private InstanceIdentifierCodecImpl instanceIdentifierDTO; + private YangInstanceIdentifier instanceIdentifierBadNamespace; + private YangInstanceIdentifier instanceIdentifierOKList; + private YangInstanceIdentifier instanceIdentifierOKLeafList; + + @BeforeClass + public static void init() throws FileNotFoundException { + schemaContext = YangParserTestUtils.parseYangFiles( + TestRestconfUtils.loadFiles("/restconf/parser/deserializer")); + } + + @Before + public void setUp() throws Exception { + ControllerContext controllerContext = TestRestconfUtils.newControllerContext(schemaContext); + + this.instanceIdentifierDTO = new InstanceIdentifierCodecImpl(null, controllerContext); + + final QName baseQName = QName.create("deserializer:test", "2016-06-06", "deserializer-test"); + final QName contA = QName.create(baseQName, "contA"); + final QName leafList = QName.create(baseQName, "leaf-list-A"); + + this.instanceIdentifierOKLeafList = YangInstanceIdentifier.builder() + .node(contA) + .node(new YangInstanceIdentifier.NodeWithValue<>(leafList, "instance")) + .build(); + + this.instanceIdentifierOKList = YangInstanceIdentifier.builder() + .node(NodeIdentifierWithPredicates.of( + QName.create(baseQName, "list-one-key"), + QName.create(QName.create(baseQName, "list-one-key"), "name"), "value")) + .build(); + + this.instanceIdentifierBadNamespace = YangInstanceIdentifier.builder() + .nodeWithKey(QName.create("nonexistent:module", "2016-10-17", "nonexistent-1"), + QName.create("nonexistent:module", "2016-10-17", "nonexistent"), + "value") + .build(); + } + + @Test + public void testSerializeDeserializeList() throws Exception { + final IdentityValuesDTO valuesDTO = + this.instanceIdentifierDTO.serialize(this.instanceIdentifierOKList); + + final YangInstanceIdentifier deserializedIdentifier = + this.instanceIdentifierDTO.deserialize(valuesDTO); + assertEquals(this.instanceIdentifierOKList, deserializedIdentifier); + } + + @Test + public void testSerializeDeserializeLeafList() throws Exception { + final IdentityValuesDTO valuesDTO = + this.instanceIdentifierDTO.serialize(this.instanceIdentifierOKLeafList); + + final YangInstanceIdentifier deserializedIdentifier = + this.instanceIdentifierDTO.deserialize(valuesDTO); + assertEquals(this.instanceIdentifierOKLeafList, deserializedIdentifier); + } + + @Test + public void testSerializeDeserializeBadModuleNamespace() throws Exception { + final IdentityValuesDTO valuesDTO = + this.instanceIdentifierDTO.serialize(this.instanceIdentifierBadNamespace); + assertEquals("nonexistent-1", valuesDTO.getValuesWithNamespaces().get(0).getValue()); + assertEquals("nonexistent:module", valuesDTO.getValuesWithNamespaces().get(0).getNamespace()); + + final YangInstanceIdentifier deserializedIdentifier = + this.instanceIdentifierDTO.deserialize(valuesDTO); + assertNull(deserializedIdentifier); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapterTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapterTest.java new file mode 100644 index 0000000..453f077 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapterTest.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2017 Red Hat, 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.netconf.sal.streams.listeners; + +import static java.time.Instant.EPOCH; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Optional; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.binding.api.WriteTransaction; +import org.opendaylight.mdsal.binding.dom.adapter.test.AbstractConcurrentDataBrokerTest; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.dom.api.DOMDataBroker; +import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService; +import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.PatchCont; +import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1; +import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1Builder; +import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1Key; +import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; +import org.skyscreamer.jsonassert.JSONAssert; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ListenerAdapterTest extends AbstractConcurrentDataBrokerTest { + private static final Logger LOG = LoggerFactory.getLogger(ListenerAdapterTest.class); + + private static final String JSON_NOTIF_LEAVES_CREATE = "/listener-adapter-test/notif-leaves-create.json"; + private static final String JSON_NOTIF_LEAVES_UPDATE = "/listener-adapter-test/notif-leaves-update.json"; + private static final String JSON_NOTIF_LEAVES_DEL = "/listener-adapter-test/notif-leaves-del.json"; + private static final String JSON_NOTIF_CREATE = "/listener-adapter-test/notif-create.json"; + private static final String JSON_NOTIF_UPDATE = "/listener-adapter-test/notif-update.json"; + private static final String JSON_NOTIF_DEL = "/listener-adapter-test/notif-del.json"; + private static final String JSON_NOTIF_WITHOUT_DATA_CREATE = + "/listener-adapter-test/notif-without-data-create.json"; + private static final String JSON_NOTIF_WITHOUT_DATA_UPDATE = + "/listener-adapter-test/notif-without-data-update.json"; + private static final String JSON_NOTIF_WITHOUT_DATA_DELETE = + "/listener-adapter-test/notif-without-data-del.json"; + + + private static final YangInstanceIdentifier PATCH_CONT_YIID = + YangInstanceIdentifier.create(new YangInstanceIdentifier.NodeIdentifier(PatchCont.QNAME)); + + private static EffectiveModelContext schemaContext; + + private DataBroker dataBroker; + private DOMDataBroker domDataBroker; + private ControllerContext controllerContext; + + @BeforeClass + public static void init() { + schemaContext = YangParserTestUtils.parseYangResource( + "/instanceidentifier/yang/instance-identifier-patch-module.yang"); + } + + @Before + public void setUp() throws Exception { + dataBroker = getDataBroker(); + domDataBroker = getDomBroker(); + controllerContext = TestRestconfUtils.newControllerContext(schemaContext); + } + + class ListenerAdapterTester extends ListenerAdapter { + + private String lastNotification = null; + + ListenerAdapterTester(final YangInstanceIdentifier path, final String streamName, + final NotificationOutputTypeGrouping.NotificationOutputType outputType, + final boolean leafNodesOnly, final boolean skipNotificationData) { + super(path, streamName, outputType, controllerContext); + setQueryParams(EPOCH, Optional.empty(), Optional.empty(), leafNodesOnly, skipNotificationData); + } + + @Override + protected void post(final Event event) { + this.lastNotification = event.getData(); + } + + public void assertGot(final String json) throws Exception { + long start = System.currentTimeMillis(); + while (true) { + if (lastNotification != null) { + break; + } + if (System.currentTimeMillis() - start > 1000) { + throw new Exception("TIMED OUT waiting for notification with " + json); + } + Thread.currentThread(); + Thread.sleep(200); + } + LOG.debug("Comparing {} {}", json, lastNotification); + JSONAssert.assertEquals(json, withFakeDate(lastNotification), false); + this.lastNotification = null; + } + } + + static String withFakeDate(final String in) { + JSONObject doc = new JSONObject(in); + JSONObject notification = doc.getJSONObject("notification"); + if (notification == null) { + return in; + } + notification.put("eventTime", "someDate"); + return doc.toString(); + } + + private String getNotifJson(final String path) throws IOException, URISyntaxException { + URL url = getClass().getResource(path); + byte[] bytes = Files.readAllBytes(Paths.get(url.toURI())); + return withFakeDate(new String(bytes, StandardCharsets.UTF_8)); + } + + @Test + public void testJsonNotifsLeaves() throws Exception { + ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey", + NotificationOutputTypeGrouping.NotificationOutputType.JSON, true, false); + DOMDataTreeChangeService changeService = domDataBroker.getExtensions() + .getInstance(DOMDataTreeChangeService.class); + DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID); + changeService.registerDataTreeChangeListener(root, adapter); + + WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction(); + MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea"); + InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class) + .child(MyList1.class, new MyList1Key("Althea")); + writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build()); + writeTransaction.commit(); + adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_CREATE)); + + writeTransaction = dataBroker.newWriteOnlyTransaction(); + builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha"); + writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build()); + writeTransaction.commit(); + adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_UPDATE)); + + writeTransaction = dataBroker.newWriteOnlyTransaction(); + writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid); + writeTransaction.commit(); + adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_DEL)); + } + + @Test + public void testJsonNotifs() throws Exception { + ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey", + NotificationOutputTypeGrouping.NotificationOutputType.JSON, false, false); + DOMDataTreeChangeService changeService = domDataBroker.getExtensions() + .getInstance(DOMDataTreeChangeService.class); + DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID); + changeService.registerDataTreeChangeListener(root, adapter); + + WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction(); + MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea"); + InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class) + .child(MyList1.class, new MyList1Key("Althea")); + writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build()); + writeTransaction.commit(); + adapter.assertGot(getNotifJson(JSON_NOTIF_CREATE)); + + writeTransaction = dataBroker.newWriteOnlyTransaction(); + builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha"); + writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build()); + writeTransaction.commit(); + adapter.assertGot(getNotifJson(JSON_NOTIF_UPDATE)); + + writeTransaction = dataBroker.newWriteOnlyTransaction(); + writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid); + writeTransaction.commit(); + adapter.assertGot(getNotifJson(JSON_NOTIF_DEL)); + } + + @Test + public void testJsonNotifsWithoutData() throws Exception { + ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey", + NotificationOutputTypeGrouping.NotificationOutputType.JSON, false, true); + DOMDataTreeChangeService changeService = domDataBroker.getExtensions() + .getInstance(DOMDataTreeChangeService.class); + DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID); + changeService.registerDataTreeChangeListener(root, adapter); + + WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction(); + MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea"); + InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class) + .child(MyList1.class, new MyList1Key("Althea")); + writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build()); + writeTransaction.commit(); + adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_CREATE)); + + writeTransaction = dataBroker.newWriteOnlyTransaction(); + builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha"); + writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build()); + writeTransaction.commit(); + adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_UPDATE)); + + writeTransaction = dataBroker.newWriteOnlyTransaction(); + writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid); + writeTransaction.commit(); + adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_DELETE)); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/streams/listeners/NotificationListenerTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/streams/listeners/NotificationListenerTest.java new file mode 100644 index 0000000..732c327 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/streams/listeners/NotificationListenerTest.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2016 Cisco 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.netconf.sal.streams.listeners; + +import static java.util.Objects.requireNonNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; +import org.opendaylight.controller.sal.restconf.impl.test.TestUtils; +import org.opendaylight.mdsal.dom.api.DOMNotification; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; +import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.common.XMLNamespace; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute; + +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class NotificationListenerTest { + private static final QNameModule MODULE = QNameModule.create(XMLNamespace.of("notifi:mod"), + Revision.of("2016-11-23")); + + private static EffectiveModelContext schemaContext; + + private ControllerContext controllerContext; + + @BeforeClass + public static void staticInit() throws FileNotFoundException { + schemaContext = TestUtils.loadSchemaContext("/notifications"); + } + + @Before + public void init() { + controllerContext = TestRestconfUtils.newControllerContext(schemaContext); + } + + @Test + public void notifi_leafTest() { + final Absolute schemaPathNotifi = Absolute.of(QName.create(MODULE, "notifi-leaf")); + + final DOMNotification notificationData = mock(DOMNotification.class); + + final LeafNode<String> leaf = mockLeaf(QName.create(MODULE, "lf")); + final ContainerNode notifiBody = mockCont(schemaPathNotifi.lastNodeIdentifier(), leaf); + + when(notificationData.getType()).thenReturn(schemaPathNotifi); + when(notificationData.getBody()).thenReturn(notifiBody); + + final String result = prepareJson(notificationData, schemaPathNotifi); + + assertTrue(result.contains("ietf-restconf:notification")); + assertTrue(result.contains("event-time")); + assertTrue(result.contains("notifi-module:notifi-leaf")); + assertTrue(result.contains("lf" + '"' + ":" + '"' + "value")); + } + + @Test + public void notifi_cont_leafTest() { + final Absolute schemaPathNotifi = Absolute.of(QName.create(MODULE, "notifi-cont")); + + final DOMNotification notificationData = mock(DOMNotification.class); + + final LeafNode<String> leaf = mockLeaf(QName.create(MODULE, "lf")); + final ContainerNode cont = mockCont(QName.create(MODULE, "cont"), leaf); + final ContainerNode notifiBody = mockCont(schemaPathNotifi.lastNodeIdentifier(), cont); + + when(notificationData.getType()).thenReturn(schemaPathNotifi); + when(notificationData.getBody()).thenReturn(notifiBody); + + final String result = prepareJson(notificationData, schemaPathNotifi); + + assertTrue(result.contains("ietf-restconf:notification")); + assertTrue(result.contains("event-time")); + assertTrue(result.contains("notifi-module:notifi-cont")); + assertTrue(result.contains("cont")); + assertTrue(result.contains("lf" + '"' + ":" + '"' + "value")); + } + + @Test + public void notifi_list_Test() { + final Absolute schemaPathNotifi = Absolute.of(QName.create(MODULE, "notifi-list")); + + final ContainerNode notifiBody = mockCont(schemaPathNotifi.lastNodeIdentifier(), ImmutableNodes.mapNodeBuilder() + .withNodeIdentifier(NodeIdentifier.create(QName.create(MODULE, "lst"))) + .withChild(mockMapEntry(QName.create(MODULE, "lst"), mockLeaf(QName.create(MODULE, "lf")))) + .build()); + + final DOMNotification notificationData = mock(DOMNotification.class); + when(notificationData.getType()).thenReturn(schemaPathNotifi); + when(notificationData.getBody()).thenReturn(notifiBody); + + final String result = prepareJson(notificationData, schemaPathNotifi); + + assertTrue(result.contains("ietf-restconf:notification")); + assertTrue(result.contains("event-time")); + assertTrue(result.contains("notifi-module:notifi-list")); + assertTrue(result.contains("lst")); + assertTrue(result.contains("lf" + '"' + ":" + '"' + "value")); + } + + @Test + public void notifi_grpTest() { + final Absolute schemaPathNotifi = Absolute.of(QName.create(MODULE, "notifi-grp")); + + final DOMNotification notificationData = mock(DOMNotification.class); + + final LeafNode<String> leaf = mockLeaf(QName.create(MODULE, "lf")); + final ContainerNode notifiBody = mockCont(schemaPathNotifi.lastNodeIdentifier(), leaf); + + when(notificationData.getType()).thenReturn(schemaPathNotifi); + when(notificationData.getBody()).thenReturn(notifiBody); + + final String result = prepareJson(notificationData, schemaPathNotifi); + + assertTrue(result.contains("ietf-restconf:notification")); + assertTrue(result.contains("event-time")); + assertTrue(result.contains("lf" + '"' + ":" + '"' + "value")); + } + + @Test + public void notifi_augmTest() { + final Absolute schemaPathNotifi = Absolute.of(QName.create(MODULE, "notifi-augm")); + + final DOMNotification notificationData = mock(DOMNotification.class); + + final LeafNode<String> leaf = mockLeaf(QName.create(MODULE, "lf-augm")); + final AugmentationNode augm = mockAugm(leaf); + final ContainerNode notifiBody = mockCont(schemaPathNotifi.lastNodeIdentifier(), augm); + + when(notificationData.getType()).thenReturn(schemaPathNotifi); + when(notificationData.getBody()).thenReturn(notifiBody); + + final String result = prepareJson(notificationData, schemaPathNotifi); + + assertTrue(result.contains("ietf-restconf:notification")); + assertTrue(result.contains("event-time")); + assertTrue(result.contains("lf-augm" + '"' + ":" + '"' + "value")); + } + + private static AugmentationNode mockAugm(final LeafNode<String> leaf) { + final AugmentationNode augm = mock(AugmentationNode.class); + final AugmentationIdentifier augmId = new AugmentationIdentifier(Set.of(leaf.getIdentifier().getNodeType())); + when(augm.getIdentifier()).thenReturn(augmId); + + final Collection<DataContainerChild> childs = new ArrayList<>(); + childs.add(leaf); + + when(augm.body()).thenReturn(childs); + return augm; + } + + private static MapEntryNode mockMapEntry(final QName entryQName, final LeafNode<String> leaf) { + return Builders.mapEntryBuilder() + .withNodeIdentifier(NodeIdentifierWithPredicates.of(entryQName, leaf.getIdentifier().getNodeType(), + leaf.body())) + .withChild(leaf) + .build(); + } + + private static ContainerNode mockCont(final QName contQName, final DataContainerChild child) { + return Builders.containerBuilder() + .withNodeIdentifier(NodeIdentifier.create(contQName)) + .withChild(child) + .build(); + } + + private static LeafNode<String> mockLeaf(final QName leafQName) { + return ImmutableNodes.leafNode(leafQName, "value"); + } + + private String prepareJson(final DOMNotification notificationData, final Absolute schemaPathNotifi) { + final List<NotificationListenerAdapter> listNotifi = Notificator.createNotificationListener( + List.of(schemaPathNotifi), "stream-name", NotificationOutputType.JSON.toString(), controllerContext); + final NotificationListenerAdapter notifi = listNotifi.get(0); + return requireNonNull(notifi.prepareJson(schemaContext, notificationData)); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/DatastoreIdentifierBuilderTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/DatastoreIdentifierBuilderTest.java new file mode 100644 index 0000000..f3ab6f9 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/DatastoreIdentifierBuilderTest.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2016 Cisco 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.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019; + +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; + +public class DatastoreIdentifierBuilderTest { + + @Test(expected = IllegalArgumentException.class) + public void testDatastoreIdentifierBuilder() { + final DatastoreIdentifierBuilder datastoreIdentifierBuilder = new DatastoreIdentifierBuilder(); + assertNotNull(datastoreIdentifierBuilder); + DatastoreIdentifierBuilder.getDefaultInstance(""); + } +}
\ No newline at end of file diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/ModuleBuilderTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/ModuleBuilderTest.java new file mode 100644 index 0000000..a834b15 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/ModuleBuilderTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016 Cisco 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.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019.restconf.restconf.modules; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import com.google.common.collect.ImmutableSet; +import java.util.Set; +import org.junit.Test; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019.RevisionIdentifier; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.YangIdentifier; + +public class ModuleBuilderTest { + + @Test + public void testModuleBuilder() { + final ModuleBuilder moduleBuilder = new ModuleBuilder(); + final Module.Revision revision = new Module.Revision(new RevisionIdentifier("2016-10-11")); + final YangIdentifier yangIdentifierOne = new YangIdentifier("YangIdentifier1"); + final YangIdentifier yangIdentifierTwo = new YangIdentifier("YangIdentifier2"); + final Uri namespace = new Uri("namespace"); + final Set<YangIdentifier> yangIdentifierList = ImmutableSet.of(yangIdentifierOne, yangIdentifierTwo); + final ModuleKey moduleKeyOne = new ModuleKey(yangIdentifierOne, revision); + final ModuleKey moduleKeyTwo = new ModuleKey(moduleKeyOne); + moduleBuilder.setRevision(revision); + moduleBuilder.setDeviation(yangIdentifierList); + moduleBuilder.setFeature(yangIdentifierList); + moduleBuilder.setName(yangIdentifierOne); + moduleBuilder.setNamespace(namespace); + moduleBuilder.withKey(moduleKeyOne); + final Module moduleOne = moduleBuilder.build(); + final Module moduleTwo = new ModuleBuilder(moduleOne).build(); + + assertNotNull(moduleBuilder); + assertNotNull(revision); + assertNotNull(yangIdentifierOne); + assertNotNull(yangIdentifierTwo); + assertNotNull(namespace); + assertNotNull(yangIdentifierList); + assertNotNull(moduleKeyOne); + assertNotNull(moduleKeyOne.hashCode()); + assertNotNull(moduleKeyOne.toString()); + assertNotNull(moduleBuilder.toString()); + assertNotNull(moduleBuilder.hashCode()); + + assertEquals(moduleKeyOne, moduleKeyTwo); + assertEquals(revision, moduleKeyOne.getRevision()); + assertEquals(yangIdentifierOne, moduleKeyOne.getName()); + assertEquals(revision, moduleBuilder.getRevision()); + assertEquals(yangIdentifierList, moduleBuilder.getDeviation()); + assertEquals(yangIdentifierList, moduleBuilder.getFeature()); + assertEquals(yangIdentifierOne, moduleBuilder.getName()); + assertEquals(namespace, moduleBuilder.getNamespace()); + assertEquals(moduleKeyOne, moduleBuilder.key()); + assertEquals(moduleOne.toString(), moduleTwo.toString()); + assertEquals(moduleKeyOne.toString(), moduleKeyTwo.toString()); + + assertTrue(moduleOne.equals(moduleTwo)); + assertTrue(moduleKeyOne.equals(moduleKeyTwo)); + } +} diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/ModuleRevisionBuilderTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/ModuleRevisionBuilderTest.java new file mode 100644 index 0000000..578d2dd --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/ModuleRevisionBuilderTest.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016 Cisco 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.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019.restconf.restconf.modules; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019.restconf.restconf.modules.Module.Revision; + +public class ModuleRevisionBuilderTest { + + @Test + public void testModuleRevisionBuilder() { + final ModuleRevisionBuilder moduleRevisionBuilder = new ModuleRevisionBuilder(); + assertNotNull(moduleRevisionBuilder); + final Revision revision = ModuleRevisionBuilder.getDefaultInstance(""); + assertNotNull(revision); + assertEquals("", revision.getString()); + assertEquals(null, revision.getRevisionIdentifier()); + } +}
\ No newline at end of file diff --git a/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/RevisionBuilderTest.java b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/RevisionBuilderTest.java new file mode 100644 index 0000000..5c2eba1 --- /dev/null +++ b/netconf/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/RevisionBuilderTest.java @@ -0,0 +1,49 @@ +/* + * 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.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019.restconf.restconf.modules; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019.RevisionIdentifier; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019.restconf.restconf.modules.Module.Revision; + +public class RevisionBuilderTest { + @Test + public void testEmptyString() { + final RevisionBuilder revisionBuilder = new RevisionBuilder(); + assertNotNull(revisionBuilder); + final Revision revision = RevisionBuilder.getDefaultInstance(""); + validate(revision, "", null); + } + + @Test + public void testValidDataString() { + final String dateString = "2014-04-23"; + final Revision revision = RevisionBuilder.getDefaultInstance(dateString); + validate(revision, null, new RevisionIdentifier(dateString)); + } + + @Test(expected = IllegalArgumentException.class) + public void testNullString() { + RevisionBuilder.getDefaultInstance(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testBadFormatString() { + RevisionBuilder.getDefaultInstance("badFormat"); + } + + private static void validate(final Revision revisionUnderTest, final String expectedRevisionString, + final RevisionIdentifier expectedRevisionIdentifier) { + assertNotNull(revisionUnderTest); + assertEquals(expectedRevisionString, revisionUnderTest.getString()); + assertEquals(expectedRevisionIdentifier, revisionUnderTest.getRevisionIdentifier()); + } +}
\ No newline at end of file |