/* * 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.restconf.utils.mapping; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.google.common.collect.Sets; import java.util.AbstractMap; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils; import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException; import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag; import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType; import org.opendaylight.restconf.Draft18; import org.opendaylight.restconf.Draft18.IetfYangLibrary; import org.opendaylight.restconf.Draft18.MonitoringModule; import org.opendaylight.restconf.Draft18.RestconfModule; import org.opendaylight.restconf.utils.schema.context.RestconfSchemaUtil; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil; 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.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.api.schema.MapNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.schema.nodes.AbstractImmutableDataContainerAttrNode; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; 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.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; /** * Unit tests for {@link RestconfMappingNodeUtil} */ public class RestconfMappingNodeUtilTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Mock private ListSchemaNode mockStreamList; @Mock private LeafSchemaNode leafName; @Mock private LeafSchemaNode leafDescription; @Mock private LeafSchemaNode leafReplaySupport; @Mock private LeafSchemaNode leafReplayLog; @Mock private LeafSchemaNode leafEvents; private static Set modules; private static SchemaContext schemaContext; private static Set modulesRest; private Set allStreamChildNodes; @BeforeClass public static void loadTestSchemaContextAndModules() throws Exception { RestconfMappingNodeUtilTest.schemaContext = TestRestconfUtils.loadSchemaContext( "/modules/restconf-module-testing"); RestconfMappingNodeUtilTest.modules = TestRestconfUtils.loadSchemaContext("/modules").getModules(); RestconfMappingNodeUtilTest.modulesRest = TestRestconfUtils.loadSchemaContext("/modules/restconf-module-testing").getModules(); } @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); when(this.leafName.getQName()).thenReturn(QName.create("", RestconfMappingNodeConstants.NAME)); when(this.leafDescription.getQName()).thenReturn(QName.create("", RestconfMappingNodeConstants.DESCRIPTION)); when(this.leafReplaySupport.getQName()).thenReturn( QName.create("", RestconfMappingNodeConstants.REPLAY_SUPPORT)); when(this.leafReplayLog.getQName()).thenReturn(QName.create(RestconfMappingNodeConstants.REPLAY_LOG)); when(this.leafEvents.getQName()).thenReturn(QName.create("", RestconfMappingNodeConstants.EVENTS)); this.allStreamChildNodes = Sets.newHashSet( this.leafName, this.leafDescription, this.leafReplaySupport, this.leafReplayLog, this.leafEvents); } /** * Test of writing modules into {@link RestconfModule#MODULE_LIST_SCHEMA_NODE} and checking if modules were * correctly written. */ @Test public void restconfMappingNodeTest() { // write modules into list module in Restconf final Module ietfYangLibMod = schemaContext.findModuleByNamespaceAndRevision(IetfYangLibrary.URI_MODULE, IetfYangLibrary.DATE); final NormalizedNode>> modules = RestconfMappingNodeUtil.mapModulesByIetfYangLibraryYang(RestconfMappingNodeUtilTest.modules, ietfYangLibMod, schemaContext, "1"); // verify loaded modules verifyLoadedModules((ContainerNode) modules); } /** * Positive test of writing one stream to {@link MonitoringModule#STREAM_LIST_SCHEMA_NODE} and checking if stream * was correctly written. */ @Test public void toStreamEntryNodeTest() { // test stream name final String stream1 = "stream-1"; // get list stream node from Restconf module final ListSchemaNode listStream = (ListSchemaNode) RestconfSchemaUtil.getRestconfSchemaNode( getTestingRestconfModule("ietf-restconf"), MonitoringModule.STREAM_LIST_SCHEMA_NODE); // write stream to list stream node final MapEntryNode mapEntryNode = RestconfMappingNodeUtil.toStreamEntryNode(stream1, listStream); // verify verifyStream(stream1, mapEntryNode); } /** * Try to map streams when {@link MonitoringModule#STREAM_LIST_SCHEMA_NODE} is null. * Test is expected to fail catching IllegalStateException. */ @Test public void toStreamEntryNodeNullListStreamNegativeTest() { this.thrown.expect(IllegalStateException.class); RestconfMappingNodeUtil.toStreamEntryNode("stream-1", null); } /** * Test trying to map streams to {@link MonitoringModule#STREAM_LIST_SCHEMA_NODE} which is not of type list. * Test is expected to fail with IllegalStateException. */ @Test public void toStreamEntryNodeIllegalListStreamNegativeTest() { this.thrown.expect(IllegalStateException.class); RestconfMappingNodeUtil.toStreamEntryNode("stream-1", mock(LeafSchemaNode.class)); } /** * Test case with target {@link MonitoringModule#STREAM_LIST_SCHEMA_NODE} which does not contain any child nodes. * Test is catching RestconfDocumentedException and error type, error tag and error status code are * compared to expected values. */ @Test public void toStreamEntryNodeSchemaNodeWithoutChildsNegativeTest() { final ListSchemaNode mockListNode = mock(ListSchemaNode.class); when(mockListNode.getChildNodes()).thenReturn(Collections.EMPTY_SET); try { RestconfMappingNodeUtil.toStreamEntryNode("stream-1", mockListNode); fail("Test should fail due to no child nodes in" + MonitoringModule.STREAM_LIST_SCHEMA_NODE + " node"); } catch (final RestconfDocumentedException e) { assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType()); assertEquals("Error tag is not correct", ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag()); assertEquals("Error status code is not correct", 404, e.getErrors().get(0).getErrorTag().getStatusCode()); } } /** * Test case when target list stream does not contain child with name {@link RestconfMappingNodeConstants#NAME}. * Test is catching RestconfDocumentedException and error type, error tag and error status code are * compared to expected values. */ @Test public void toStreamEntryNodeMissingStreamNameNegativeTest() { prepareMockListWithMissingLeaf(this.leafName); try { RestconfMappingNodeUtil.toStreamEntryNode("stream-1", this.mockStreamList); fail("Test should fail due to missing " + RestconfMappingNodeConstants.NAME + " node in " + MonitoringModule.STREAM_LIST_SCHEMA_NODE); } catch (final RestconfDocumentedException e) { assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType()); assertEquals("Error tag is not correct", ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag()); assertEquals("Error status code is not correct", 404, e.getErrors().get(0).getErrorTag().getStatusCode()); } } /** * Test case when target list stream does not contain child with name * {@link RestconfMappingNodeConstants#DESCRIPTION}. Test is catching RestconfDocumentedException and * checking error type and error tag. */ @Test public void toStreamEntryNodeMissingStreamDescriptionNegativeTest() { prepareMockListWithMissingLeaf(this.leafDescription); try { RestconfMappingNodeUtil.toStreamEntryNode("stream-1", this.mockStreamList); fail("Test should fail due to missing " + RestconfMappingNodeConstants.DESCRIPTION + " node in " + MonitoringModule.STREAM_LIST_SCHEMA_NODE); } catch (final RestconfDocumentedException e) { assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType()); assertEquals("Error tag is not correct", ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag()); assertEquals("Error status code is not correct", 404, e.getErrors().get(0).getErrorTag().getStatusCode()); } } /** * Test case when target list stream does not contain child with name * {@link RestconfMappingNodeConstants#REPLAY_SUPPORT}. Test is catching RestconfDocumentedException * and checking error type and error tag. */ @Test public void toStreamEntryNodeMissingStreamReplaySupportNegativeTest() { prepareMockListWithMissingLeaf(this.leafReplaySupport); try { RestconfMappingNodeUtil.toStreamEntryNode("stream-1", this.mockStreamList); fail("Test should fail due to missing " + RestconfMappingNodeConstants.REPLAY_SUPPORT + " node in " + MonitoringModule.STREAM_LIST_SCHEMA_NODE); } catch (final RestconfDocumentedException e) { assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType()); assertEquals("Error tag is not correct", ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag()); assertEquals("Error status code is not correct", 404, e.getErrors().get(0).getErrorTag().getStatusCode()); } } /** * Test case when target list stream does not contain child with name * {@link RestconfMappingNodeConstants#REPLAY_LOG}. Test is catching RestconfDocumentedException and * checking error type and error tag. */ @Test public void toStreamEntryNodeMissingStreamReplayLogNegativeTest() { prepareMockListWithMissingLeaf(this.leafReplayLog); try { RestconfMappingNodeUtil.toStreamEntryNode("stream-1", this.mockStreamList); fail("Test should fail due to missing " + RestconfMappingNodeConstants.REPLAY_LOG + " node in " + MonitoringModule.STREAM_LIST_SCHEMA_NODE); } catch (final RestconfDocumentedException e) { assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType()); assertEquals("Error tag is not correct", ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag()); assertEquals("Error status code is not correct", 404, e.getErrors().get(0).getErrorTag().getStatusCode()); } } /** * Test case when target list stream does not contain child with name {@link RestconfMappingNodeConstants#EVENTS}. * Test is catching RestconfDocumentedException and checking error type, error tag and error status * code. */ @Test public void toStreamEntryNodeMissingStreamEventsNegativeTest() { prepareMockListWithMissingLeaf(this.leafEvents); try { RestconfMappingNodeUtil.toStreamEntryNode("stream-1", this.mockStreamList); fail("Test should fail due to missing " + RestconfMappingNodeConstants.EVENTS + " node in " + MonitoringModule.STREAM_LIST_SCHEMA_NODE); } catch (final RestconfDocumentedException e) { assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType()); assertEquals("Error tag is not correct", ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag()); assertEquals("Error status code is not correct", 404, e.getErrors().get(0).getErrorTag().getStatusCode()); } } /** * Test case when target list stream contains child with name {@link RestconfMappingNodeConstants#NAME}. Test is * expecting IllegalStateException. */ @Test public void toStreamEntryNodeStreamNameNegativeTest() { prepareMockListWithIllegalLeaf(this.leafName); this.thrown.expect(IllegalStateException.class); RestconfMappingNodeUtil.toStreamEntryNode("stream-1", this.mockStreamList); } /** * Test case when target list stream contains child with name {@link RestconfMappingNodeConstants#DESCRIPTION}. * Test is expecting IllegalStateException. */ @Test public void toStreamEntryNodeStreamDescriptionNegativeTest() { prepareMockListWithIllegalLeaf(this.leafDescription); this.thrown.expect(IllegalStateException.class); RestconfMappingNodeUtil.toStreamEntryNode("stream-1", this.mockStreamList); } /** * Test case when target list stream contains child with name {@link RestconfMappingNodeConstants#REPLAY_SUPPORT}. * Test is expecting IllegalStateException. */ @Test public void toStreamEntryNodeStreamReplaySupportNegativeTest() { prepareMockListWithIllegalLeaf(this.leafReplaySupport); this.thrown.expect(IllegalStateException.class); RestconfMappingNodeUtil.toStreamEntryNode("stream-1", this.mockStreamList); } /** * Test case when target list stream contains child with name {@link RestconfMappingNodeConstants#REPLAY_LOG}. * Test is expecting IllegalStateException. */ @Test public void toStreamEntryNodeStreamReplayLogNegativeTest() { prepareMockListWithIllegalLeaf(this.leafReplayLog); this.thrown.expect(IllegalStateException.class); RestconfMappingNodeUtil.toStreamEntryNode("stream-1", this.mockStreamList); } /** * Test case when target list stream contains child with name {@link RestconfMappingNodeConstants#EVENTS}. Test is * expecting IllegalStateException. */ @Test public void toStreamEntryNodeStreamEventsNegativeTest() { prepareMockListWithIllegalLeaf(this.leafEvents); this.thrown.expect(IllegalStateException.class); RestconfMappingNodeUtil.toStreamEntryNode("stream-1", this.mockStreamList); } /** * Verify loaded modules * * @param containerNode * - modules */ private void verifyLoadedModules(final ContainerNode containerNode) { final Map loadedModules = new HashMap<>(); for (final DataContainerChild child : containerNode.getValue()) { if (child instanceof LeafNode) { assertEquals(IetfYangLibrary.MODULE_SET_ID_LEAF_QNAME, ((LeafNode) child).getNodeType()); } if (child instanceof MapNode) { assertEquals(IetfYangLibrary.MODULE_QNAME_LIST, ((MapNode) child).getNodeType()); for (final MapEntryNode mapEntryNode : ((MapNode) child).getValue()) { String name = ""; String revision = ""; for (final DataContainerChild dataContainerChild : mapEntryNode .getValue()) { switch (dataContainerChild.getNodeType().getLocalName()) { case IetfYangLibrary.SPECIFIC_MODULE_NAME_LEAF: name = String.valueOf(((LeafNode) dataContainerChild).getValue()); break; case IetfYangLibrary.SPECIFIC_MODULE_REVISION_LEAF: revision = String.valueOf(((LeafNode) dataContainerChild).getValue()); break; } } loadedModules.put(name, revision); } } } verifyLoadedModules(RestconfMappingNodeUtilTest.modulesRest, loadedModules); } /** * Verify if correct modules were loaded into Restconf module by comparison with modules from * SchemaContext. * @param expectedModules Modules from SchemaContext * @param loadedModules Loaded modules into Restconf module */ private final void verifyLoadedModules(final Set expectedModules, final Map loadedModules) { assertEquals("Number of loaded modules is not as expected", expectedModules.size(), loadedModules.size()); for (final Module m : expectedModules) { final String name = m.getName(); final String revision = loadedModules.get(name); assertNotNull("Expected module not found", revision); assertEquals("Not correct revision of loaded module", SimpleDateFormatUtil.getRevisionFormat().format(m.getRevision()), revision); loadedModules.remove(name); } } /** * Verify if a stream was correctly written into {@link MonitoringModule#STREAM_LIST_SCHEMA_NODE} node in Restconf * module. * @param streamName Expected stream name * @param streamNode Writetn strem node from Restconf module */ private final void verifyStream(final String streamName, final MapEntryNode streamNode) { assertNotNull("Stream node can not be null", streamNode); final Iterator entries = ((AbstractImmutableDataContainerAttrNode) streamNode) .getChildren().entrySet().iterator(); boolean notAllowedKey = false; while (entries.hasNext()) { final Entry e = ((AbstractMap.SimpleImmutableEntry) entries.next()); final String key = ((YangInstanceIdentifier.NodeIdentifier) e.getKey()).getNodeType().getLocalName(); switch (key) { case RestconfMappingNodeConstants.NAME : assertEquals("Stream name value is not as expected", streamName, ((LeafNode) e.getValue()).getValue()); break; case RestconfMappingNodeConstants.DESCRIPTION : assertEquals("Stream description value is not as expected", RestconfMappingStreamConstants.DESCRIPTION, ((LeafNode) e.getValue()).getValue()); break; case RestconfMappingNodeConstants.REPLAY_SUPPORT : assertEquals("Stream replay support value is not as expected", RestconfMappingStreamConstants.REPLAY_SUPPORT, ((LeafNode) e.getValue()).getValue()); break; case RestconfMappingNodeConstants.REPLAY_LOG : assertEquals("Stream replay log value is not as expected", RestconfMappingStreamConstants.REPLAY_LOG, ((LeafNode) e.getValue()).getValue()); break; case RestconfMappingNodeConstants.EVENTS : assertEquals("Stream events value is not as expected", RestconfMappingStreamConstants.EVENTS, ((LeafNode) e.getValue()).getValue()); break; default: notAllowedKey = true; break; } } assertFalse("Not allowed key in list stream found", notAllowedKey); } /** * There are multiple testing Restconf modules for different test cases. It is possible to distinguish them by * name or by namespace. This method is looking for Restconf test module by its name. * @param s Testing Restconf module name * @return Restconf module */ private Module getTestingRestconfModule(final String s) { return RestconfMappingNodeUtilTest.schemaContext.findModuleByName( s, Draft18.RestconfModule.IETF_RESTCONF_QNAME.getRevision()); } /** * Updates {@link this#mockStreamList} to NOT contains specified leaf. * @param leaf Leaf to be missing */ private void prepareMockListWithMissingLeaf(final LeafSchemaNode leaf) { // prepare set of leaf without selected leaf final Set childLeafs = new HashSet<>(this.allStreamChildNodes); childLeafs.remove(leaf); // mock list leaf nodes when(this.mockStreamList.getChildNodes()).thenReturn(childLeafs); } /** * Updates {@link this#mockStreamList} to contains specified leaf which is not of type {@link LeafSchemaNode}. * @param leaf Leaf to be changes */ private void prepareMockListWithIllegalLeaf(final LeafSchemaNode leaf) { // prepare set of leaf without selected leaf final Set childLeafs = new HashSet<>(this.allStreamChildNodes); childLeafs.remove(leaf); // add leaf-list with the same local name as removed leaf final String localName = leaf.getQName().getLocalName(); final LeafListSchemaNode mockLeafList = mock(LeafListSchemaNode.class); when(mockLeafList.getQName()).thenReturn(QName.create("", localName)); childLeafs.add(mockLeafList); // mock list leaf nodes when(this.mockStreamList.getChildNodes()).thenReturn(childLeafs); } }