blob: 088932223095768aa27743932a7e47c994a2fc0c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.configuration2.tree;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* A special test class for {@code InMemoryNodeModel} which tests the facilities for tracking nodes.
*/
public class TestInMemoryNodeModelTrackedNodes {
/** Constant for the name of a new table field. */
private static final String NEW_FIELD = "newTableField";
/** Constant for a test key. */
private static final String TEST_KEY = "someTestKey";
/** Constant for the key used by the test selector. */
private static final String SELECTOR_KEY = "tables.table(1)";
/** The root node for the test hierarchy. */
private static ImmutableNode root;
/** A default node selector initialized with a test key. */
private static NodeSelector selector;
/**
* Checks whether a fields node was correctly changed by an update operation.
*
* @param nodeFields the fields node
* @param idx the index of the changed node
*/
private static void checkedForChangedField(final ImmutableNode nodeFields, final int idx) {
assertEquals(NodeStructureHelper.fieldsLength(1), nodeFields.getChildren().size());
int childIndex = 0;
for (final ImmutableNode field : nodeFields) {
final String expName = childIndex == idx ? NEW_FIELD : NodeStructureHelper.field(1, childIndex);
checkFieldNode(field, expName);
childIndex++;
}
}
/**
* Checks whether a field node has the expected content.
*
* @param nodeField the field node to be checked
* @param name the expected name of this field
*/
private static void checkFieldNode(final ImmutableNode nodeField, final String name) {
assertEquals("field", nodeField.getNodeName());
assertEquals(1, nodeField.getChildren().size());
final ImmutableNode nodeName = nodeField.getChildren().get(0);
assertEquals("name", nodeName.getNodeName());
assertEquals(name, nodeName.getValue());
}
/**
* Tests whether a field node was added.
*
* @param nodeFields the fields node
*/
private static void checkForAddedField(final ImmutableNode nodeFields) {
assertEquals(NodeStructureHelper.fieldsLength(1) + 1, nodeFields.getChildren().size());
final ImmutableNode nodeField = nodeFields.getChildren().get(NodeStructureHelper.fieldsLength(1));
checkFieldNode(nodeField, NEW_FIELD);
}
/**
* Helper method for checking whether the expected field node was removed.
*
* @param nodeFields the fields node
* @param idx the index of the removed field
*/
private static void checkForRemovedField(final ImmutableNode nodeFields, final int idx) {
assertEquals(NodeStructureHelper.fieldsLength(1) - 1, nodeFields.getChildren().size());
final Set<String> expectedNames = new HashSet<>();
final Set<String> actualNames = new HashSet<>();
for (int i = 0; i < NodeStructureHelper.fieldsLength(1); i++) {
if (idx != i) {
expectedNames.add(NodeStructureHelper.field(1, i));
}
}
for (final ImmutableNode field : nodeFields) {
final ImmutableNode nodeName = field.getChildren().get(0);
actualNames.add(String.valueOf(nodeName.getValue()));
}
assertEquals(expectedNames, actualNames);
}
/**
* Creates a default resolver which supports arbitrary queries on a target node.
*
* @return the resolver
*/
private static NodeKeyResolver<ImmutableNode> createResolver() {
final NodeKeyResolver<ImmutableNode> resolver = NodeStructureHelper.createResolverMock();
NodeStructureHelper.prepareResolveKeyForQueries(resolver);
return resolver;
}
/**
* Prepares a mock for a resolver to handle keys for update operations. Support is limited. It is expected that only a
* single value is changed.
*
* @param resolver the {@code NodeKeyResolver} mock
*/
private static void prepareResolverForUpdateKeys(final NodeKeyResolver<ImmutableNode> resolver) {
when(resolver.resolveUpdateKey(any(), any(), any(), any())).thenAnswer(invocation -> {
final ImmutableNode root = invocation.getArgument(0, ImmutableNode.class);
final String key = invocation.getArgument(1, String.class);
final TreeData handler = invocation.getArgument(3, TreeData.class);
final List<QueryResult<ImmutableNode>> results = DefaultExpressionEngine.INSTANCE.query(root, key, handler);
assertEquals(1, results.size());
return new NodeUpdateData<>(Collections.singletonMap(results.get(0), invocation.getArgument(2)), null, null, null);
});
}
@BeforeAll
public static void setUpBeforeClass() throws Exception {
root = new ImmutableNode.Builder(1).addChild(NodeStructureHelper.ROOT_TABLES_TREE).create();
selector = new NodeSelector(SELECTOR_KEY);
}
/** The model to be tested. */
private InMemoryNodeModel model;
/**
* Helper method for testing whether a tracked node can be replaced.
*/
private void checkReplaceTrackedNode() {
final ImmutableNode newNode = new ImmutableNode.Builder().name("newNode").create();
model.replaceTrackedNode(selector, newNode);
assertSame(newNode, model.getTrackedNode(selector));
assertTrue(model.isTrackedNodeDetached(selector));
}
/**
* Checks trackChildNodes() if the passed in key has a result set which causes the operation to be aborted.
*
* @param queryResult the result set of the key
*/
private void checkTrackChildNodesNoResult(final List<ImmutableNode> queryResult) {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
when(resolver.resolveNodeKey(root, TEST_KEY, model.getNodeHandler())).thenReturn(queryResult);
final TreeData oldData = model.getTreeData();
assertTrue(model.trackChildNodes(TEST_KEY, resolver).isEmpty());
assertSame(oldData, model.getTreeData());
}
/**
* Helper method for testing trackChildNodeWithCreation() if invalid query results are generated.
*
* @param queryResult the result set of the key
*/
private void checkTrackChildNodeWithCreationInvalidKey(final List<ImmutableNode> queryResult) {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
when(resolver.resolveNodeKey(model.getRootNode(), TEST_KEY, model.getNodeHandler())).thenReturn(queryResult);
assertThrows(ConfigurationRuntimeException.class, () -> model.trackChildNodeWithCreation(TEST_KEY, "someChild", resolver));
}
/**
* Returns the fields node from the model.
*
* @return the fields node
*/
private ImmutableNode fieldsNodeFromModel() {
return NodeStructureHelper.nodeForKey(model, "tables/table(1)/fields");
}
/**
* Returns the fields node from a tracked node.
*
* @return the fields node
*/
private ImmutableNode fieldsNodeFromTrackedNode() {
return NodeStructureHelper.nodeForKey(model.getTrackedNode(selector), "fields");
}
/**
* Produces a tracked node with the default selector and executes an operation which detaches this node.
*
* @param resolver the {@code NodeKeyResolver}
*/
private void initDetachedNode(final NodeKeyResolver<ImmutableNode> resolver) {
model.trackNode(selector, resolver);
model.clearTree("tables.table(0)", resolver);
}
/**
* Prepares the resolver mock to expect a nodeKey() request.
*
* @param resolver the {@code NodeKeyResolver}
* @param node the node whose name is to be resolved
* @param key the key to be returned for this node
*/
private void prepareNodeKey(final NodeKeyResolver<ImmutableNode> resolver, final ImmutableNode node, final String key) {
final Map<ImmutableNode, String> cache = new HashMap<>();
when(resolver.nodeKey(node, cache, model.getNodeHandler())).thenReturn(key);
}
@BeforeEach
public void setUp() throws Exception {
model = new InMemoryNodeModel(root);
}
/**
* Tests an addNodes() operation on a tracked node that is detached.
*/
@Test
public void testAddNodesOnDetachedNode() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
NodeStructureHelper.prepareResolveAddKeys(resolver);
model.trackNode(selector, resolver);
initDetachedNode(resolver);
final ImmutableNode rootNode = model.getRootNode();
model.addNodes("fields", selector, Collections.singleton(NodeStructureHelper.createFieldNode(NEW_FIELD)), resolver);
assertSame(rootNode, model.getRootNode());
checkForAddedField(fieldsNodeFromTrackedNode());
}
/**
* Tests whether an addNodes() operation works on a tracked node.
*/
@Test
public void testAddNodesOnTrackedNode() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
NodeStructureHelper.prepareResolveAddKeys(resolver);
model.trackNode(selector, resolver);
model.addNodes("fields", selector, Collections.singleton(NodeStructureHelper.createFieldNode(NEW_FIELD)), resolver);
checkForAddedField(fieldsNodeFromModel());
checkForAddedField(fieldsNodeFromTrackedNode());
}
/**
* Tests an addProperty() operation on a tracked node that is detached.
*/
@Test
public void testAddPropertyOnDetachedNode() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
NodeStructureHelper.prepareResolveAddKeys(resolver);
model.trackNode(selector, resolver);
initDetachedNode(resolver);
final ImmutableNode rootNode = model.getRootNode();
model.addProperty("fields.field(-1).name", selector, Collections.singleton(NEW_FIELD), resolver);
assertSame(rootNode, model.getRootNode());
checkForAddedField(fieldsNodeFromTrackedNode());
}
/**
* Tests whether an addProperty() operation works on a tracked node.
*/
@Test
public void testAddPropertyOnTrackedNode() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
NodeStructureHelper.prepareResolveAddKeys(resolver);
model.trackNode(selector, resolver);
model.addProperty("fields.field(-1).name", selector, Collections.singleton(NEW_FIELD), resolver);
checkForAddedField(fieldsNodeFromModel());
checkForAddedField(fieldsNodeFromTrackedNode());
}
/**
* Tests a clearProperty() operation on a tracked node which is detached.
*/
@Test
public void testClearPropertyOnDetachedNode() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
initDetachedNode(resolver);
final ImmutableNode rootNode = model.getRootNode();
model.clearProperty("fields.field(0).name", selector, resolver);
assertSame(rootNode, model.getRootNode());
final ImmutableNode nodeFields = fieldsNodeFromTrackedNode();
checkForRemovedField(nodeFields, 0);
}
/**
* Tests whether clearProperty() can operate on a tracked node.
*/
@Test
public void testClearPropertyOnTrackedNode() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clearProperty("fields.field(0).name", selector, resolver);
final ImmutableNode nodeFields = fieldsNodeFromModel();
checkForRemovedField(nodeFields, 0);
}
/**
* Tests a clearTree() operation on a tracked node which is detached.
*/
@Test
public void testClearTreeOnDetachedNode() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
initDetachedNode(resolver);
final ImmutableNode rootNode = model.getRootNode();
model.clearTree("fields.field(1)", selector, resolver);
assertSame(rootNode, model.getRootNode());
final ImmutableNode nodeFields = fieldsNodeFromTrackedNode();
checkForRemovedField(nodeFields, 1);
}
/**
* Tests whether clearTree() can operate on a tracked node.
*/
@Test
public void testClearTreeOnTrackedNode() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clearTree("fields.field(1)", selector, resolver);
final ImmutableNode nodeFields = fieldsNodeFromModel();
checkForRemovedField(nodeFields, 1);
}
/**
* Tests whether a tracked node can be queried even after the model was cleared.
*/
@Test
public void testGetTrackedNodeAfterClear() {
final ImmutableNode node = NodeStructureHelper.nodeForKey(model, "tables/table(1)");
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clear(resolver);
assertSame(node, model.getTrackedNode(selector));
}
/**
* Tests whether a tracked node can be queried after the root node was changed.
*/
@Test
public void testGetTrackedNodeAfterSetRootNode() {
final ImmutableNode node = NodeStructureHelper.nodeForKey(model, "tables/table(1)");
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.setRootNode(root);
assertSame(node, model.getTrackedNode(selector));
}
/**
* Tests whether a tracked node survives updates of the node model.
*/
@Test
public void testGetTrackedNodeAfterUpdate() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clearProperty("tables.table(1).fields.field(1).name", resolver);
final ImmutableNode node = model.getTrackedNode(selector);
assertEquals(NodeStructureHelper.table(1), node.getChildren().get(0).getValue());
}
/**
* Tests whether a tracked node can be queried even if it was removed from the structure.
*/
@Test
public void testGetTrackedNodeAfterUpdateNoLongerExisting() {
final ImmutableNode node = NodeStructureHelper.nodeForKey(model, "tables/table(1)");
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
initDetachedNode(resolver);
assertSame(node, model.getTrackedNode(selector));
}
/**
* Tests whether a tracked node can be queried.
*/
@Test
public void testGetTrackedNodeExisting() {
final ImmutableNode node = NodeStructureHelper.nodeForKey(model, "tables/table(1)");
model.trackNode(selector, createResolver());
assertSame(node, model.getTrackedNode(selector));
}
/**
* Tests whether a node handler for a tracked node can be queried which is still active.
*/
@Test
public void testGetTrackedNodeHandlerActive() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
final NodeHandler<ImmutableNode> handler = model.getTrackedNodeHandler(selector);
final TrackedNodeHandler tnh = assertInstanceOf(TrackedNodeHandler.class, handler);
assertSame(model.getTrackedNode(selector), handler.getRootNode());
assertSame(model.getTreeData(), tnh.getParentHandler());
}
/**
* Tests whether a node handler for a detached tracked node can be queried.
*/
@Test
public void testGetTrackedNodeHandlerDetached() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
initDetachedNode(resolver);
final NodeHandler<ImmutableNode> handler = model.getTrackedNodeHandler(selector);
assertSame(model.getTrackedNode(selector), handler.getRootNode());
assertInstanceOf(TreeData.class, handler);
assertNotSame(model.getNodeHandler(), handler);
}
/**
* Tries to obtain a tracked node which is unknown.
*/
@Test
public void testGetTrackedNodeNonExisting() {
assertThrows(ConfigurationRuntimeException.class, () -> model.getTrackedNode(selector));
}
/**
* Tests whether a clear() operation causes nodes to be detached.
*/
@Test
public void testIsDetachedAfterClear() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clear(resolver);
assertTrue(model.isTrackedNodeDetached(selector));
}
/**
* Tests whether tracked nodes become detached when a new root node is set.
*/
@Test
public void testIsDetachedAfterSetRoot() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clearProperty("tables.table(1).fields.field(1).name", resolver);
model.setRootNode(root);
assertTrue(model.isTrackedNodeDetached(selector));
}
/**
* Tests isDetached() for a life node.
*/
@Test
public void testIsDetachedFalseAfterUpdate() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clearProperty("tables.table(1).fields.field(1).name", resolver);
assertFalse(model.isTrackedNodeDetached(selector));
}
/**
* Tests isDetached() for a node which has just been tracked.
*/
@Test
public void testIsDetachedFalseNoUpdates() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
assertFalse(model.isTrackedNodeDetached(selector));
}
/**
* Tests isDetached() for an actually detached node.
*/
@Test
public void testIsDetachedTrue() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
initDetachedNode(resolver);
assertTrue(model.isTrackedNodeDetached(selector));
}
/**
* Tests whether an active tracked node can be replaced.
*/
@Test
public void testReplaceTrackedNodeForActiveTrackedNode() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
checkReplaceTrackedNode();
}
/**
* Tests whether a detached tracked node can be replaced.
*/
@Test
public void testReplaceTrackedNodeForDetachedNode() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
initDetachedNode(resolver);
checkReplaceTrackedNode();
}
/**
* Tries to replace a tracked node with a null node.
*/
@Test
public void testReplaceTrackedNodeNull() {
model.trackNode(selector, createResolver());
assertThrows(IllegalArgumentException.class, () -> model.replaceTrackedNode(selector, null));
}
/**
* Tests whether tracked nodes can be created from a key.
*/
@Test
public void testSelectAndTrackNodes() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
final String nodeKey1 = "tables/table(0)";
final String nodeKey2 = "tables/table(1)";
final ImmutableNode node1 = NodeStructureHelper.nodeForKey(root, nodeKey1);
final ImmutableNode node2 = NodeStructureHelper.nodeForKey(root, nodeKey2);
when(resolver.resolveNodeKey(root, TEST_KEY, model.getNodeHandler())).thenReturn(Arrays.asList(node1, node2));
prepareNodeKey(resolver, node1, nodeKey1);
prepareNodeKey(resolver, node2, nodeKey2);
final Collection<NodeSelector> selectors = model.selectAndTrackNodes(TEST_KEY, resolver);
final Iterator<NodeSelector> it = selectors.iterator();
NodeSelector sel = it.next();
assertEquals(new NodeSelector(nodeKey1), sel);
assertSame(node1, model.getTrackedNode(sel));
sel = it.next();
assertEquals(new NodeSelector(nodeKey2), sel);
assertSame(node2, model.getTrackedNode(sel));
assertFalse(it.hasNext());
}
/**
* Tests whether selectAndTrackNodes() works for nodes that are already tracked.
*/
@Test
public void testSelectAndTrackNodesNodeAlreadyTracked() {
NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
resolver = createResolver();
final ImmutableNode node = model.getTrackedNode(selector);
when(resolver.resolveNodeKey(root, TEST_KEY, model.getNodeHandler())).thenReturn(Collections.singletonList(node));
prepareNodeKey(resolver, node, SELECTOR_KEY);
final Collection<NodeSelector> selectors = model.selectAndTrackNodes(TEST_KEY, resolver);
assertEquals(1, selectors.size());
assertEquals(selector, selectors.iterator().next());
model.untrackNode(selector);
assertSame(node, model.getTrackedNode(selector));
}
/**
* Tests selectAndTrackNodes() if the key does not select any nodes.
*/
@Test
public void testSelectAndTrackNodesNoSelection() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
when(resolver.resolveNodeKey(root, TEST_KEY, model.getNodeHandler())).thenReturn(Collections.<ImmutableNode>emptyList());
assertTrue(model.selectAndTrackNodes(TEST_KEY, resolver).isEmpty());
}
/**
* Tests a setProperty() operation on a tracked node that is detached.
*/
@Test
public void testSetPropertyOnDetachedNode() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
prepareResolverForUpdateKeys(resolver);
model.trackNode(selector, resolver);
initDetachedNode(resolver);
final ImmutableNode rootNode = model.getRootNode();
model.setProperty("fields.field(0).name", selector, NEW_FIELD, resolver);
assertSame(rootNode, model.getRootNode());
checkedForChangedField(fieldsNodeFromTrackedNode(), 0);
}
/**
* Tests whether a setProperty() operation works on a tracked node.
*/
@Test
public void testSetPropertyOnTrackedNode() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
prepareResolverForUpdateKeys(resolver);
model.trackNode(selector, resolver);
model.setProperty("fields.field(0).name", selector, NEW_FIELD, resolver);
checkedForChangedField(fieldsNodeFromModel(), 0);
checkedForChangedField(fieldsNodeFromTrackedNode(), 0);
}
/**
* Tests whether all children of a node can be tracked at once.
*/
@Test
public void testTrackChildNodes() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
final ImmutableNode node = NodeStructureHelper.nodeForKey(root, "tables");
final String[] keys = new String[node.getChildren().size()];
for (int i = 0; i < keys.length; i++) {
final ImmutableNode child = node.getChildren().get(i);
keys[i] = String.format("%s.%s(%d)", node.getNodeName(), child.getNodeName(), i);
prepareNodeKey(resolver, child, keys[i]);
}
when(resolver.resolveNodeKey(root, TEST_KEY, model.getNodeHandler())).thenReturn(Collections.singletonList(node));
final Collection<NodeSelector> selectors = model.trackChildNodes(TEST_KEY, resolver);
assertEquals(node.getChildren().size(), selectors.size());
int idx = 0;
for (final NodeSelector sel : selectors) {
assertEquals(new NodeSelector(keys[idx]), sel);
assertEquals(node.getChildren().get(idx), model.getTrackedNode(sel), "Wrong tracked node for " + sel);
idx++;
}
}
/**
* Tests trackChildNodes() for a key that returns more than a single result.
*/
@Test
public void testTrackChildNodesMultipleResults() {
checkTrackChildNodesNoResult(
Arrays.asList(NodeStructureHelper.nodeForKey(root, "tables/table(0)"), NodeStructureHelper.nodeForKey(root, "tables/table(1)")));
}
/**
* Tests trackChildNodes() for a key pointing to a node with no children.
*/
@Test
public void testTrackChildNodesNodeWithNoChildren() {
checkTrackChildNodesNoResult(Collections.singletonList(NodeStructureHelper.nodeForKey(root, "tables/table(0)/name")));
}
/**
* Tests trackChildNodes() for a key that does not return any results.
*/
@Test
public void testTrackChildNodesNoResults() {
checkTrackChildNodesNoResult(Collections.<ImmutableNode>emptyList());
}
/**
* Tests whether an existing child of a selected node can be tracked.
*/
@Test
public void testTrackChildNodeWithCreationExisting() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
final String childName = "name";
final String parentKey = "tables/table(0)";
final String childKey = parentKey + "/" + childName;
final ImmutableNode node = NodeStructureHelper.nodeForKey(model, parentKey);
final ImmutableNode child = NodeStructureHelper.nodeForKey(node, childName);
when(resolver.resolveNodeKey(root, TEST_KEY, model.getNodeHandler())).thenReturn(Collections.singletonList(node));
prepareNodeKey(resolver, child, childKey);
final NodeSelector childSelector = model.trackChildNodeWithCreation(TEST_KEY, childName, resolver);
assertEquals(new NodeSelector(childKey), childSelector);
assertSame(child, model.getTrackedNode(childSelector));
}
/**
* Tests trackChildNodeWithCreation() if the passed in key selects multiple nodes.
*/
@Test
public void testTrackChildNodeWithCreationMultipleResults() {
final List<ImmutableNode> nodes = Arrays.asList(NodeStructureHelper.nodeForKey(root, "tables/table(0)"),
NodeStructureHelper.nodeForKey(root, "tables/table(1)"));
checkTrackChildNodeWithCreationInvalidKey(nodes);
}
/**
* Tests whether a child node to be tracked is created if necessary.
*/
@Test
public void testTrackChildNodeWithCreationNonExisting() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
final String childName = "space";
final String parentKey = "tables/table(0)";
final String childKey = parentKey + "/" + childName;
final ImmutableNode node = NodeStructureHelper.nodeForKey(model, parentKey);
when(resolver.resolveNodeKey(root, TEST_KEY, model.getNodeHandler())).thenReturn(Collections.singletonList(node));
when(resolver.nodeKey(any(), eq(new HashMap<>()), any())).thenReturn(childKey);
final NodeSelector childSelector = model.trackChildNodeWithCreation(TEST_KEY, childName, resolver);
assertEquals(new NodeSelector(childKey), childSelector);
final ImmutableNode child = model.getTrackedNode(childSelector);
assertEquals(childName, child.getNodeName());
assertNull(child.getValue());
final ImmutableNode parent = model.getNodeHandler().getParent(child);
assertEquals("table", parent.getNodeName());
assertEquals(child, NodeStructureHelper.nodeForKey(model, childKey));
}
/**
* Tests trackChildNodeWithCreation() if the passed in key does not select a node.
*/
@Test
public void testTrackChildNodeWithCreationNoResults() {
checkTrackChildNodeWithCreationInvalidKey(new ArrayList<>());
}
/**
* Tests whether a tracked node is handled correctly if an operation is executed on this node which causes the node to
* be detached. In this case, the node should be cleared (it makes no sense to use the last defined node instance).
*/
@Test
public void testTrackedNodeClearedInOperation() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clearTree(null, selector, resolver);
assertTrue(model.isTrackedNodeDetached(selector));
final ImmutableNode node = model.getTrackedNode(selector);
assertEquals("table", node.getNodeName());
assertFalse(model.getNodeHandler().isDefined(node));
}
/**
* Tries to call trackNode() with a key that selects multiple results.
*/
@Test
public void testTrackNodeKeyMultipleResults() {
final NodeSelector nodeSelector = new NodeSelector("tables.table.fields.field.name");
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
assertThrows(ConfigurationRuntimeException.class, () -> model.trackNode(nodeSelector, resolver));
}
/**
* Tries to call trackNode() with a key that does not yield any results.
*/
@Test
public void testTrackNodeKeyNoResults() {
final NodeSelector nodeSelector = new NodeSelector("tables.unknown");
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
assertThrows(ConfigurationRuntimeException.class, () -> model.trackNode(nodeSelector, resolver));
}
/**
* Tests whether a single node can be tracked multiple times.
*/
@Test
public void testTrackNodeMultipleTimes() {
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.trackNode(selector, resolver);
model.untrackNode(selector);
assertNotNull(model.getTrackedNode(selector));
}
/**
* Tests whether tracking of a node can be stopped.
*/
@Test
public void testUntrackNode() {
model.trackNode(selector, createResolver());
model.untrackNode(selector);
assertThrows(ConfigurationRuntimeException.class, () -> model.getTrackedNode(selector));
}
/**
* Tries to stop tracking of a node which is not tracked.
*/
@Test
public void testUntrackNodeNonExisting() {
assertThrows(ConfigurationRuntimeException.class, () -> model.untrackNode(selector));
}
}