parts, Node startParent, Session session, String target)
@@ -128,11 +130,10 @@ public void run(Config config, Node node, HopContext context) throws RepositoryE
}
final Node effectiveParent = descriptor.getParent();
- final String absolutePath = StringUtils.stripEnd(effectiveParent.getPath(), "/") + '/' + descriptor.getNewChildName();
- context.info("Moving node from {} to {}", node.getPath(), absolutePath);
+ context.info("Moving node from {} to {}", node.getPath(), descriptor.absolutePath);
final String nextSiblingName = getNextSiblingName(node, parent, descriptor.parent);
- node.getSession().move(node.getPath(), absolutePath);
+ node.getSession().move(node.getPath(), descriptor.absolutePath);
if (nextSiblingName != null) {
effectiveParent.orderBefore(descriptor.newChildName, nextSiblingName);
@@ -172,7 +173,22 @@ public static class NewNodeDescriptor {
private final Node parent;
private final String newChildName;
+ private final String absolutePath;
private final boolean targetExists;
+ private final Node nodeToRemove;
+
+ /**
+ * Removes the replaced node if conflict was set to FORCE.
+ *
+ * Usually required to be called before the action is set to execute
+ *
+ * @throws RepositoryException if the removal fails
+ */
+ public void removeReplacedNode() throws RepositoryException {
+ if (nodeToRemove != null) {
+ nodeToRemove.remove();
+ }
+ }
}
@AllArgsConstructor
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/CopyNodeTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/CopyNodeTest.java
new file mode 100644
index 0000000..5763698
--- /dev/null
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/CopyNodeTest.java
@@ -0,0 +1,228 @@
+package com.swisscom.aem.tools.impl.hops;
+
+import static com.swisscom.aem.tools.testsupport.AemUtil.childNames;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+
+import com.swisscom.aem.tools.jcrhopper.HopperException;
+import com.swisscom.aem.tools.jcrhopper.Runner;
+import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
+import com.swisscom.aem.tools.jcrhopper.config.ConflictResolution;
+import com.swisscom.aem.tools.jcrhopper.config.RunHandler;
+import com.swisscom.aem.tools.jcrhopper.config.Script;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.testing.mock.aem.junit5.JcrOakAemContext;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(AemContextExtension.class)
+class CopyNodeTest {
+
+ public final AemContext context = new JcrOakAemContext();
+ private RunnerBuilder builder;
+ private Session session;
+
+ @BeforeEach
+ public void setUp() {
+ context.create().resource("/content", "prop1", 1, "prop2", "value2");
+ context.create().resource("/content/child-1", "jcr:primaryType", "cq:Page");
+ context.create().resource("/content/child-2");
+ context.create().resource("/content/third-child");
+ context.create().resource("/content/other-child");
+ context.create().resource("/existing");
+ context.create().resource("/existing/existing-child");
+
+ builder = Runner.builder().addHop(new CopyNode(), new CreateChildNode());
+ session = context.resourceResolver().adaptTo(Session.class);
+ }
+
+ @Test
+ public void copy_empty() throws RepositoryException, HopperException {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("content2")
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+
+ final ResourceResolver resolver = context.resourceResolver();
+ assertEquals(
+ Arrays.asList("child-1", "child-2", "third-child", "other-child", "new-child"),
+ childNames(resolver.getResource("/content2"))
+ );
+
+ assertEquals(
+ new HashSet<>(Arrays.asList("prop1", "prop2", "jcr:primaryType")),
+ resolver.getResource("/content2").getValueMap().keySet()
+ );
+
+ assertEquals("cq:Page", session.getNode("/content2/child-1").getPrimaryNodeType().getName());
+ }
+
+ @Test
+ public void copy_inside() throws RepositoryException, HopperException {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("content/child-2/content2")
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+
+ final ResourceResolver resolver = context.resourceResolver();
+ assertEquals(
+ Arrays.asList("child-1", "child-2", "third-child", "other-child", "new-child"),
+ childNames(resolver.getResource("/content/child-2/content2"))
+ );
+
+ assertEquals(
+ new HashSet<>(Arrays.asList("prop1", "prop2", "jcr:primaryType")),
+ resolver.getResource("/content/child-2/content2").getValueMap().keySet()
+ );
+
+ assertEquals("cq:Page", session.getNode("/content/child-2/content2/child-1").getPrimaryNodeType().getName());
+ }
+
+ @Test
+ public void copy_root() {
+ assertThrows(HopperException.class, () -> {
+ builder.build(new Script(new CopyNode.Config().withNewName("test-child"))).run(session.getRootNode(), true);
+ });
+ }
+
+ @Test
+ public void copy_existing_ignore() throws RepositoryException, HopperException {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("existing")
+ .withConflict(ConflictResolution.IGNORE)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+
+ final ResourceResolver resolver = context.resourceResolver();
+ assertEquals(Collections.singletonList("existing-child"), childNames(resolver.getResource("/existing")));
+ }
+
+ @Test
+ public void copy_existing_force() throws RepositoryException, HopperException {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("existing")
+ .withConflict(ConflictResolution.FORCE)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+
+ final ResourceResolver resolver = context.resourceResolver();
+ assertEquals(
+ Arrays.asList("child-1", "child-2", "third-child", "other-child", "new-child"),
+ childNames(resolver.getResource("/existing"))
+ );
+
+ assertEquals(
+ new HashSet<>(Arrays.asList("prop1", "prop2", "jcr:primaryType")),
+ resolver.getResource("/existing").getValueMap().keySet()
+ );
+
+ assertEquals("cq:Page", session.getNode("/existing/child-1").getPrimaryNodeType().getName());
+ }
+
+ @Test
+ public void copy_existing_throw() {
+ assertThrows(HopperException.class, () -> {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("existing")
+ .withConflict(ConflictResolution.THROW)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+ });
+ }
+
+ @Test
+ public void copy_existingInside_ignore() throws RepositoryException, HopperException {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("content/child-1")
+ .withConflict(ConflictResolution.IGNORE)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+
+ final ResourceResolver resolver = context.resourceResolver();
+ assertEquals(Collections.emptyList(), childNames(resolver.getResource("/content/child-1")));
+
+ assertEquals("cq:Page", session.getNode("/content/child-1").getPrimaryNodeType().getName());
+ }
+
+ @Test
+ public void copy_existingInside_force() throws RepositoryException, HopperException {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("content/child-1")
+ .withConflict(ConflictResolution.FORCE)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+
+ final ResourceResolver resolver = context.resourceResolver();
+ assertEquals(
+ Arrays.asList("child-1", "child-2", "third-child", "other-child", "new-child"),
+ childNames(resolver.getResource("/content/child-1"))
+ );
+
+ assertEquals(
+ new HashSet<>(Arrays.asList("prop1", "prop2", "jcr:primaryType")),
+ resolver.getResource("/content/child-1").getValueMap().keySet()
+ );
+
+ assertEquals("cq:Page", session.getNode("/content/child-1/child-1").getPrimaryNodeType().getName());
+ }
+
+ @Test
+ public void copy_existingInside_throw() {
+ assertThrows(HopperException.class, () -> {
+ builder
+ .build(
+ new Script(
+ new CopyNode.Config()
+ .withNewName("content/child-1")
+ .withConflict(ConflictResolution.THROW)
+ .withHops(Collections.singletonList(new CreateChildNode.Config().withName("new-child")))
+ )
+ )
+ .run(session.getNode("/content"), true);
+ });
+ }
+}
From 214f936676915937872ac3eb7756f75cb60c3f8e Mon Sep 17 00:00:00 2001
From: Raphael Schweikert
Date: Sat, 2 Nov 2024 19:31:48 +0100
Subject: [PATCH 08/13] fix(runner): allow each to loop over primitive and
nested arrays
---
src/main/java/com/swisscom/aem/tools/impl/hops/Each.java | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/main/java/com/swisscom/aem/tools/impl/hops/Each.java b/src/main/java/com/swisscom/aem/tools/impl/hops/Each.java
index 1cc9744..fa523f9 100644
--- a/src/main/java/com/swisscom/aem/tools/impl/hops/Each.java
+++ b/src/main/java/com/swisscom/aem/tools/impl/hops/Each.java
@@ -5,6 +5,7 @@
import com.swisscom.aem.tools.jcrhopper.config.HopConfig;
import com.swisscom.aem.tools.jcrhopper.context.HopContext;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.lang.reflect.Array;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
@@ -37,8 +38,8 @@ public void run(Config config, Node node, HopContext context) throws RepositoryE
runWith(config, ((Iterator>) items).next(), index++, node, context);
}
} else if (items.getClass().isArray()) {
- for (Object item : (Object[]) items) {
- runWith(config, item, index++, node, context);
+ for (index = 0; index < Array.getLength(items); index++) {
+ runWith(config, Array.get(items, index), index, node, context);
}
} else {
runWith(config, items, index, node, context);
From 87a9b6c0d30010e4a3b802b21da0598923d5219e Mon Sep 17 00:00:00 2001
From: Raphael Schweikert
Date: Sat, 2 Nov 2024 19:32:10 +0100
Subject: [PATCH 09/13] test(runner): add tests for declare hop
---
.../aem/tools/impl/hops/ChildNodesTest.java | 1 -
.../aem/tools/impl/hops/CopyNodeTest.java | 2 -
.../aem/tools/impl/hops/DeclareTest.java | 90 +++++++++++++++++++
3 files changed, 90 insertions(+), 3 deletions(-)
create mode 100644 src/test/java/com/swisscom/aem/tools/impl/hops/DeclareTest.java
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/ChildNodesTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/ChildNodesTest.java
index 5bdf2d0..f72429e 100644
--- a/src/test/java/com/swisscom/aem/tools/impl/hops/ChildNodesTest.java
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/ChildNodesTest.java
@@ -13,7 +13,6 @@
import io.wcm.testing.mock.aem.junit5.AemContext;
import io.wcm.testing.mock.aem.junit5.AemContextExtension;
import io.wcm.testing.mock.aem.junit5.JcrOakAemContext;
-import java.util.Collection;
import java.util.Collections;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/CopyNodeTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/CopyNodeTest.java
index 5763698..abd2bdd 100644
--- a/src/test/java/com/swisscom/aem/tools/impl/hops/CopyNodeTest.java
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/CopyNodeTest.java
@@ -3,13 +3,11 @@
import static com.swisscom.aem.tools.testsupport.AemUtil.childNames;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.Mockito.mock;
import com.swisscom.aem.tools.jcrhopper.HopperException;
import com.swisscom.aem.tools.jcrhopper.Runner;
import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
import com.swisscom.aem.tools.jcrhopper.config.ConflictResolution;
-import com.swisscom.aem.tools.jcrhopper.config.RunHandler;
import com.swisscom.aem.tools.jcrhopper.config.Script;
import io.wcm.testing.mock.aem.junit5.AemContext;
import io.wcm.testing.mock.aem.junit5.AemContextExtension;
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/DeclareTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/DeclareTest.java
new file mode 100644
index 0000000..daec83c
--- /dev/null
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/DeclareTest.java
@@ -0,0 +1,90 @@
+package com.swisscom.aem.tools.impl.hops;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import com.swisscom.aem.tools.jcrhopper.HopperException;
+import com.swisscom.aem.tools.jcrhopper.Runner;
+import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
+import com.swisscom.aem.tools.jcrhopper.config.RunHandler;
+import com.swisscom.aem.tools.jcrhopper.config.Script;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.testing.mock.aem.junit5.JcrMockAemContext;
+import java.util.Arrays;
+import java.util.Collections;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(AemContextExtension.class)
+class DeclareTest {
+
+ public final AemContext context = new JcrMockAemContext();
+ private RunnerBuilder builder;
+ private RunHandler mockRunHandler;
+
+ @BeforeEach
+ public void setUp() {
+ mockRunHandler = mock(RunHandler.class);
+ builder = Runner.builder().addHop(new Declare(), new RunScript(), new Each()).runHandler(mockRunHandler);
+ }
+
+ @Test
+ public void declare_basic() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Declare.Config().withDeclarations(Collections.singletonMap("var1", "1")),
+ new RunScript.Config().withExtension("jexl").withCode("writer.print(var1)")
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("1");
+ }
+
+ @Test
+ public void declare_node() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Declare.Config().withDeclarations(Collections.singletonMap("node", "{'path': 1}")),
+ new RunScript.Config().withExtension("jexl").withCode("writer.print(node.path)")
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("/");
+ }
+
+ @Test
+ public void declare_override() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Declare.Config().withDeclarations(Collections.singletonMap("var1", "1")),
+ new RunScript.Config().withExtension("jexl").withCode("writer.print(`outer var1 before: ${var1}`)"),
+ new Each.Config()
+ .withExpression("[1]")
+ .withAssumeNodes(false)
+ .withHops(
+ Arrays.asList(
+ new Declare.Config().withDeclarations(Collections.singletonMap("var1", "2")),
+ new RunScript.Config()
+ .withExtension("jexl")
+ .withCode("writer.print(`inner var1: ${var1}`)")
+ )
+ ),
+ new RunScript.Config().withExtension("jexl").withCode("writer.print(`outer var1 after: ${var1}`)")
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("outer var1 before: 1");
+ verify(mockRunHandler).print("inner var1: 2");
+ verify(mockRunHandler).print("outer var1 after: 1");
+ }
+}
From a47fd3a83a6103878bd0628313befc490856c574 Mon Sep 17 00:00:00 2001
From: Raphael Schweikert
Date: Sun, 3 Nov 2024 10:30:38 +0100
Subject: [PATCH 10/13] fix(runner): ensure `each` skips iterations on
non-nodes when assumeNodes is true
---
.../swisscom/aem/tools/impl/hops/Each.java | 2 +
.../aem/tools/impl/hops/EachTest.java | 152 ++++++++++++++++++
2 files changed, 154 insertions(+)
create mode 100644 src/test/java/com/swisscom/aem/tools/impl/hops/EachTest.java
diff --git a/src/main/java/com/swisscom/aem/tools/impl/hops/Each.java b/src/main/java/com/swisscom/aem/tools/impl/hops/Each.java
index fa523f9..ff4e644 100644
--- a/src/main/java/com/swisscom/aem/tools/impl/hops/Each.java
+++ b/src/main/java/com/swisscom/aem/tools/impl/hops/Each.java
@@ -58,6 +58,8 @@ private void runWith(Config config, Object item, int index, Node initialNode, Ho
node = (Node) item;
} else if (item instanceof String) {
node = context.getJcrFunctions().resolve((String) item);
+ } else {
+ node = null;
}
if (node == null) {
context.error("Iteration item {} could not be resolved as node", item);
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/EachTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/EachTest.java
new file mode 100644
index 0000000..fc7bc09
--- /dev/null
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/EachTest.java
@@ -0,0 +1,152 @@
+package com.swisscom.aem.tools.impl.hops;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import com.swisscom.aem.tools.jcrhopper.HopperException;
+import com.swisscom.aem.tools.jcrhopper.Runner;
+import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
+import com.swisscom.aem.tools.jcrhopper.config.RunHandler;
+import com.swisscom.aem.tools.jcrhopper.config.Script;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.testing.mock.aem.junit5.JcrMockAemContext;
+import java.util.Collections;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(AemContextExtension.class)
+class EachTest {
+
+ public final AemContext context = new JcrMockAemContext();
+ private RunnerBuilder builder;
+ private RunHandler mockRunHandler;
+
+ @BeforeEach
+ public void setUp() {
+ context.create().resource("/content");
+ context.create().resource("/content/child-1");
+ context.create().resource("/content/child-2");
+ context.create().resource("/content/third-child");
+ context.create().resource("/content/other-child");
+
+ mockRunHandler = mock(RunHandler.class);
+ builder = Runner.builder().addHop(new Each(), new RunScript()).runHandler(mockRunHandler);
+ }
+
+ @Test
+ public void basic() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Each.Config()
+ .withExpression("['en-gb', 'en-us', 'en-ie']")
+ .withIterator("lang")
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config()
+ .withExtension("js")
+ .withCode(
+ "var items = lang.split('-');\nwriter.print(items[1].toUpperCase() + ': ' + items[0]);"
+ )
+ )
+ )
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("GB: en");
+ verify(mockRunHandler).print("US: en");
+ verify(mockRunHandler).print("IE: en");
+ }
+
+ @Test
+ public void primitive() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Each.Config()
+ .withExpression("[1, 2, 3, 4]")
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config().withExtension("js").withCode("writer.print(item*100);")
+ )
+ )
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("100");
+ verify(mockRunHandler).print("200");
+ verify(mockRunHandler).print("300");
+ verify(mockRunHandler).print("400");
+ }
+
+ @Test
+ public void node_string() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Each.Config()
+ .withExpression("[jcr:resolve(node, '/content/child-2'), jcr:resolve(node, '/content/third-child')]")
+ .withAssumeNodes(true)
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config().withExtension("js").withCode("writer.print(node.path);")
+ )
+ )
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("/content/child-2");
+ verify(mockRunHandler).print("/content/third-child");
+ }
+
+ @Test
+ public void node_refs() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Each.Config()
+ .withExpression("['/content', '/content/child-1', '/content/other-child']")
+ .withAssumeNodes(true)
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config().withExtension("js").withCode("writer.print(node.path);")
+ )
+ )
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("/content");
+ verify(mockRunHandler).print("/content/child-1");
+ verify(mockRunHandler).print("/content/other-child");
+ }
+
+ @Test
+ public void node_invalid() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new Each.Config()
+ .withExpression("[1, 2]")
+ .withAssumeNodes(true)
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config().withExtension("js").withCode("writer.print(node.path);")
+ )
+ )
+ )
+ )
+ .run(context.resourceResolver().getResource("/").adaptTo(Node.class), true);
+
+ verify(mockRunHandler, never()).print(any());
+ }
+}
From 3abaa01a2b4d4d74e13274b272ab96a9cbcee912 Mon Sep 17 00:00:00 2001
From: Raphael Schweikert
Date: Sun, 3 Nov 2024 10:38:17 +0100
Subject: [PATCH 11/13] test(runner): add tests for `filterNode` hop
---
.../aem/tools/impl/hops/FilterNodeTest.java | 77 +++++++++++++++++++
1 file changed, 77 insertions(+)
create mode 100644 src/test/java/com/swisscom/aem/tools/impl/hops/FilterNodeTest.java
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/FilterNodeTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/FilterNodeTest.java
new file mode 100644
index 0000000..3bc20ca
--- /dev/null
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/FilterNodeTest.java
@@ -0,0 +1,77 @@
+package com.swisscom.aem.tools.impl.hops;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import com.swisscom.aem.tools.jcrhopper.HopperException;
+import com.swisscom.aem.tools.jcrhopper.Runner;
+import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
+import com.swisscom.aem.tools.jcrhopper.config.RunHandler;
+import com.swisscom.aem.tools.jcrhopper.config.Script;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.testing.mock.aem.junit5.JcrMockAemContext;
+import java.util.Collections;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(AemContextExtension.class)
+class FilterNodeTest {
+
+ public final AemContext context = new JcrMockAemContext();
+ private RunnerBuilder builder;
+ private RunHandler mockRunHandler;
+
+ @BeforeEach
+ public void setUp() {
+ context.create().resource("/child-1");
+ context.create().resource("/other-child");
+
+ mockRunHandler = mock(RunHandler.class);
+ builder = Runner.builder().addHop(new FilterNode(), new RunScript()).runHandler(mockRunHandler).addUtil("str", StringUtils.class);
+ }
+
+ @Test
+ public void matches() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new FilterNode.Config()
+ .withExpression("str:startsWith(node.name, 'child-')")
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config().withExtension("jexl").withCode("writer.print(node.path)")
+ )
+ )
+ )
+ )
+ .run(context.resourceResolver().getResource("/child-1").adaptTo(Node.class), true);
+
+ verify(mockRunHandler).print("/child-1");
+ }
+
+ @Test
+ public void doesNotMatch() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ new FilterNode.Config()
+ .withExpression("str:startsWith(node.name, 'child-')")
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config().withExtension("jexl").withCode("writer.print(node.path)")
+ )
+ )
+ )
+ )
+ .run(context.resourceResolver().getResource("/other-child").adaptTo(Node.class), true);
+
+ verify(mockRunHandler, never()).print(any());
+ }
+}
From e92bd6ec4895f10cd264a9fe0bd0553d283f8f40 Mon Sep 17 00:00:00 2001
From: Raphael Schweikert
Date: Sun, 3 Nov 2024 19:15:26 +0100
Subject: [PATCH 12/13] chore(runner): add convenience Script constructor
takes a log level and script hops as varargs
useful for unit tests
---
.../aem/tools/impl/hops/NodeQuery.java | 2 +-
.../aem/tools/jcrhopper/config/Script.java | 10 ++++
.../aem/tools/impl/hops/ChildNodesTest.java | 18 ++-----
.../aem/tools/jcrhopper/RunnerTest.java | 48 ++++++++-----------
.../jcrhopper/osgi/RunnerServiceTest.java | 6 +--
5 files changed, 38 insertions(+), 46 deletions(-)
diff --git a/src/main/java/com/swisscom/aem/tools/impl/hops/NodeQuery.java b/src/main/java/com/swisscom/aem/tools/impl/hops/NodeQuery.java
index e7cdc40..11b597a 100644
--- a/src/main/java/com/swisscom/aem/tools/impl/hops/NodeQuery.java
+++ b/src/main/java/com/swisscom/aem/tools/impl/hops/NodeQuery.java
@@ -33,7 +33,7 @@ public void run(Config config, Node node, HopContext context) throws RepositoryE
final Map resultVars = new HashMap<>();
final String statement = context.evaluateTemplate(config.query);
resultVars.put("query", statement);
- context.trace("Running JCR query: {}", statement);
+ context.trace("Running {} JCR query: {}", config.queryType, statement);
final QueryResult result = getQueryResult(config, node, context, statement);
final RowIterator rowIterator = result.getRows();
final String[] selectors = result.getSelectorNames();
diff --git a/src/main/java/com/swisscom/aem/tools/jcrhopper/config/Script.java b/src/main/java/com/swisscom/aem/tools/jcrhopper/config/Script.java
index 680cef0..750c892 100644
--- a/src/main/java/com/swisscom/aem/tools/jcrhopper/config/Script.java
+++ b/src/main/java/com/swisscom/aem/tools/jcrhopper/config/Script.java
@@ -48,6 +48,16 @@ public Script(HopConfig... hops) {
this(Arrays.asList(hops));
}
+ /**
+ * Create a script with the given hops and log level.
+ *
+ * @param logLevel the log level verbosity for printed messages
+ * @param hops the hops to configure for this script
+ */
+ public Script(LogLevel logLevel, HopConfig... hops) {
+ this(Arrays.asList(hops), logLevel);
+ }
+
/**
* Create a script with the given hops and log level.
*
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/ChildNodesTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/ChildNodesTest.java
index f72429e..5a64370 100644
--- a/src/test/java/com/swisscom/aem/tools/impl/hops/ChildNodesTest.java
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/ChildNodesTest.java
@@ -13,7 +13,6 @@
import io.wcm.testing.mock.aem.junit5.AemContext;
import io.wcm.testing.mock.aem.junit5.AemContextExtension;
import io.wcm.testing.mock.aem.junit5.JcrOakAemContext;
-import java.util.Collections;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.junit.jupiter.api.BeforeEach;
@@ -43,7 +42,7 @@ public void setUp() {
@Test
public void iterate_all() throws RepositoryException, HopperException {
- builder.build(new Script(Collections.singletonList(new ChildNodes.Config()), LogLevel.DEBUG)).run(session.getNode("/content"), true);
+ builder.build(new Script(LogLevel.DEBUG, new ChildNodes.Config())).run(session.getNode("/content"), true);
verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-1 on /content", null, null);
verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-2 on /content", null, null);
@@ -53,9 +52,7 @@ public void iterate_all() throws RepositoryException, HopperException {
@Test
public void iterate_wildcard() throws RepositoryException, HopperException {
- builder
- .build(new Script(Collections.singletonList(new ChildNodes.Config().withNamePattern("*")), LogLevel.DEBUG))
- .run(session.getNode("/content"), true);
+ builder.build(new Script(LogLevel.DEBUG, new ChildNodes.Config().withNamePattern("*"))).run(session.getNode("/content"), true);
verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-1 on /content", null, null);
verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-2 on /content", null, null);
@@ -65,9 +62,7 @@ public void iterate_wildcard() throws RepositoryException, HopperException {
@Test
public void iterate_pattern() throws RepositoryException, HopperException {
- builder
- .build(new Script(Collections.singletonList(new ChildNodes.Config().withNamePattern("child-*")), LogLevel.DEBUG))
- .run(session.getNode("/content"), true);
+ builder.build(new Script(LogLevel.DEBUG, new ChildNodes.Config().withNamePattern("child-*"))).run(session.getNode("/content"), true);
verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-1 on /content", null, null);
verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-2 on /content", null, null);
@@ -78,12 +73,7 @@ public void iterate_pattern() throws RepositoryException, HopperException {
@Test
public void iterate_union() throws RepositoryException, HopperException {
builder
- .build(
- new Script(
- Collections.singletonList(new ChildNodes.Config().withNamePattern("child-* | third-child")),
- LogLevel.DEBUG
- )
- )
+ .build(new Script(LogLevel.DEBUG, new ChildNodes.Config().withNamePattern("child-* | third-child")))
.run(session.getNode("/content"), true);
verify(mockRunHandler).log(LogLevel.DEBUG, "Found child node child-1 on /content", null, null);
diff --git a/src/test/java/com/swisscom/aem/tools/jcrhopper/RunnerTest.java b/src/test/java/com/swisscom/aem/tools/jcrhopper/RunnerTest.java
index 83f3e24..34ab949 100644
--- a/src/test/java/com/swisscom/aem/tools/jcrhopper/RunnerTest.java
+++ b/src/test/java/com/swisscom/aem/tools/jcrhopper/RunnerTest.java
@@ -71,17 +71,15 @@ class RunnerTest {
public void simple() throws RepositoryException, HopperException {
final Runner runner = RUNNER_BUILDER.build(
new Script(
- Arrays.asList(
- new SetProperty.Config().withPropertyName("test").withValue("true"),
- new CreateChildNode.Config()
- .withName("cool-item")
- .withHops(
- Collections.singletonList(
- new SetProperty.Config().withPropertyName("TestProp").withValue("'TestValue'")
- )
+ LogLevel.TRACE,
+ new SetProperty.Config().withPropertyName("test").withValue("true"),
+ new CreateChildNode.Config()
+ .withName("cool-item")
+ .withHops(
+ Collections.singletonList(
+ new SetProperty.Config().withPropertyName("TestProp").withValue("'TestValue'")
)
- ),
- LogLevel.TRACE
+ )
)
);
@@ -260,25 +258,21 @@ public void query() throws RepositoryException, HopperException {
final Runner runner = RUNNER_BUILDER.build(
new Script(
- Arrays.asList(
- new NodeQuery.Config()
- .withQuery("SELECT * FROM [nt:unstructured] as n WHERE NAME(n) = 'test-item'")
- .withCounterName("item")
- .withHops(
- Arrays.asList(
- new SetProperty.Config().withPropertyName("index").withValue("item"),
- new ChildNodes.Config()
- .withHops(
- Collections.singletonList(
- new SetProperty.Config()
- .withPropertyName("query")
- .withValue("query")
- )
+ LogLevel.TRACE,
+ new NodeQuery.Config()
+ .withQuery("SELECT * FROM [nt:unstructured] as n WHERE NAME(n) = 'test-item'")
+ .withCounterName("item")
+ .withHops(
+ Arrays.asList(
+ new SetProperty.Config().withPropertyName("index").withValue("item"),
+ new ChildNodes.Config()
+ .withHops(
+ Collections.singletonList(
+ new SetProperty.Config().withPropertyName("query").withValue("query")
)
- )
+ )
)
- ),
- LogLevel.TRACE
+ )
)
);
diff --git a/src/test/java/com/swisscom/aem/tools/jcrhopper/osgi/RunnerServiceTest.java b/src/test/java/com/swisscom/aem/tools/jcrhopper/osgi/RunnerServiceTest.java
index cb002d2..687232a 100644
--- a/src/test/java/com/swisscom/aem/tools/jcrhopper/osgi/RunnerServiceTest.java
+++ b/src/test/java/com/swisscom/aem/tools/jcrhopper/osgi/RunnerServiceTest.java
@@ -52,10 +52,8 @@ public void builder_basic() throws HopperException, RepositoryException {
final Runner runner = runnerBuilder.build(
new Script(
- Collections.singletonList(
- new ResolveNode.Config().withName("/test").withHops(Collections.singletonList(new ChildNodes.Config()))
- ),
- LogLevel.DEBUG
+ LogLevel.DEBUG,
+ new ResolveNode.Config().withName("/test").withHops(Collections.singletonList(new ChildNodes.Config()))
)
);
From 79f0d5b15002e768378c3df51b2c87db5e5611d5 Mon Sep 17 00:00:00 2001
From: Raphael Schweikert
Date: Sun, 3 Nov 2024 20:06:20 +0100
Subject: [PATCH 13/13] chore(runner): add tests for `nodeQuery` hop
---
.../aem/tools/impl/hops/NodeQuery.java | 5 +-
.../aem/tools/impl/hops/NodeQueryTest.java | 295 ++++++++++++++++++
2 files changed, 298 insertions(+), 2 deletions(-)
create mode 100644 src/test/java/com/swisscom/aem/tools/impl/hops/NodeQueryTest.java
diff --git a/src/main/java/com/swisscom/aem/tools/impl/hops/NodeQuery.java b/src/main/java/com/swisscom/aem/tools/impl/hops/NodeQuery.java
index 11b597a..9ded62c 100644
--- a/src/main/java/com/swisscom/aem/tools/impl/hops/NodeQuery.java
+++ b/src/main/java/com/swisscom/aem/tools/impl/hops/NodeQuery.java
@@ -71,7 +71,8 @@ private String getSelectorName(Config config, Logger context, String... selector
return selectorName;
}
- private QueryResult getQueryResult(Config config, Node node, HopContext context, String statement) throws RepositoryException {
+ private QueryResult getQueryResult(Config config, Node node, HopContext context, String statement)
+ throws RepositoryException, HopperException {
final QueryManager qm = node.getSession().getWorkspace().getQueryManager();
final Query query = qm.createQuery(statement, config.queryType);
if (config.limit > 0) {
@@ -86,7 +87,7 @@ private QueryResult getQueryResult(Config config, Node node, HopContext context,
if (value != null) {
query.bindValue(bindVar, context.getJcrFunctions().valueFromObject(value));
} else {
- context.error("Could not bind placeholder {} as there is no known variable for it", bindVar);
+ throw new HopperException("Could not bind placeholder " + bindVar + " as there is no known variable for it");
}
}
return query.execute();
diff --git a/src/test/java/com/swisscom/aem/tools/impl/hops/NodeQueryTest.java b/src/test/java/com/swisscom/aem/tools/impl/hops/NodeQueryTest.java
new file mode 100644
index 0000000..34f4d40
--- /dev/null
+++ b/src/test/java/com/swisscom/aem/tools/impl/hops/NodeQueryTest.java
@@ -0,0 +1,295 @@
+package com.swisscom.aem.tools.impl.hops;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.startsWith;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import com.swisscom.aem.tools.jcrhopper.HopperException;
+import com.swisscom.aem.tools.jcrhopper.Runner;
+import com.swisscom.aem.tools.jcrhopper.RunnerBuilder;
+import com.swisscom.aem.tools.jcrhopper.config.LogLevel;
+import com.swisscom.aem.tools.jcrhopper.config.RunHandler;
+import com.swisscom.aem.tools.jcrhopper.config.Script;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.testing.mock.aem.junit5.JcrOakAemContext;
+import java.util.Collections;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(AemContextExtension.class)
+class NodeQueryTest {
+
+ public final AemContext context = new JcrOakAemContext();
+ private RunnerBuilder builder;
+ private Session session;
+ private RunHandler mockRunHandler;
+
+ @BeforeEach
+ public void setUp() throws RepositoryException {
+ context.create().resource("/content", "jcr:primaryType", "sling:Folder");
+ context.create().resource("/content/child", "jcr:primaryType", "cq:Page");
+ context.create().resource("/content/child/jcr:content", "jcr:primaryType", "cq:PageContent", "pageId", 1);
+ context.create().resource("/content/child/one", "jcr:primaryType", "cq:Page");
+ context.create().resource("/content/child/one/jcr:content", "jcr:primaryType", "cq:PageContent", "pageId", 2);
+
+ mockRunHandler = mock(RunHandler.class);
+ builder = Runner.builder().addHop(new NodeQuery(), new Declare(), new RunScript()).runHandler(mockRunHandler);
+ session = context.resourceResolver().adaptTo(Session.class);
+ session.save();
+ }
+
+ @Test
+ public void xpath() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new NodeQuery.Config().withQueryType("xpath").withQuery("/jcr:root/content//element(*, cq:Page)")
+ )
+ )
+ .run(session, true);
+
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found node /content/child for xpath query /jcr:root/content//e…", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found node /content/child/one for xpath query /jcr:root/content//e…", null, null);
+ verify(mockRunHandler).log(LogLevel.INFO, "Processed 2 nodes from xpath query /jcr:root/content//element(*, cq:Page)", null, null);
+ }
+
+ @Test
+ public void sql2() throws HopperException, RepositoryException {
+ builder
+ .build(new Script(LogLevel.DEBUG, new NodeQuery.Config().withQueryType(Query.JCR_SQL2).withQuery("SELECT * FROM [cq:Page]")))
+ .run(session, true);
+
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found node /content/child for JCR-SQL2 query SELECT * FROM [cq:Pa…", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found node /content/child/one for JCR-SQL2 query SELECT * FROM [cq:Pa…", null, null);
+ verify(mockRunHandler).log(LogLevel.INFO, "Processed 2 nodes from JCR-SQL2 query SELECT * FROM [cq:Page]", null, null);
+ }
+
+ @Test
+ public void sql2_preparedPlaceholder() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new Declare.Config().withDeclarations(Collections.singletonMap("pageId", "1")),
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery("SELECT * FROM [cq:PageContent] WHERE pageId = $pageId")
+ )
+ )
+ .run(session, true);
+
+ verify(mockRunHandler).log(
+ LogLevel.DEBUG,
+ "Found node /content/child/jcr:content for JCR-SQL2 query SELECT * FROM [cq:Pa…",
+ null,
+ null
+ );
+ verify(mockRunHandler, never()).log(
+ LogLevel.DEBUG,
+ "Found node /content/child/one/jcr:content for JCR-SQL2 query SELECT * FROM [cq:Pa…",
+ null,
+ null
+ );
+ verify(mockRunHandler).log(
+ LogLevel.INFO,
+ "Processed 1 nodes from JCR-SQL2 query SELECT * FROM [cq:PageContent] WHERE pageId = $pageId",
+ null,
+ null
+ );
+ }
+
+ @Test
+ public void sql2_preparedPlaceholder_missing() {
+ assertThrows(HopperException.class, () ->
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery("SELECT * FROM [cq:PageContent] WHERE pageId = $pageId")
+ )
+ )
+ .run(session, true)
+ );
+ }
+
+ @Test
+ public void sql2_templateExpression() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new Declare.Config().withDeclarations(Collections.singletonMap("pageId", "2")),
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery("SELECT * FROM [cq:PageContent] WHERE pageId = ${pageId}")
+ )
+ )
+ .run(session, true);
+
+ verify(mockRunHandler).log(
+ LogLevel.DEBUG,
+ "Found node /content/child/one/jcr:content for JCR-SQL2 query SELECT * FROM [cq:Pa…",
+ null,
+ null
+ );
+ verify(mockRunHandler, never()).log(
+ LogLevel.DEBUG,
+ "Found node /content/child/jcr:content for JCR-SQL2 query SELECT * FROM [cq:Pa…",
+ null,
+ null
+ );
+ verify(mockRunHandler).log(
+ LogLevel.INFO,
+ "Processed 1 nodes from JCR-SQL2 query SELECT * FROM [cq:PageContent] WHERE pageId = 2",
+ null,
+ null
+ );
+ }
+
+ @Test
+ public void sql2_implicitJoin() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery(
+ "SELECT * FROM [cq:Page] AS page INNER JOIN [cq:PageContent] AS pageContent ON ISCHILDNODE(pageContent, page)"
+ )
+ )
+ )
+ .run(session, true);
+
+ verify(mockRunHandler).log(any(), startsWith("There are 2 selectors in the JCR-SQL2 query. "), any(), any());
+ verify(mockRunHandler).log(
+ LogLevel.DEBUG,
+ "Found node /content/child/jcr:content for JCR-SQL2 query SELECT * FROM [cq:Pa…",
+ null,
+ null
+ );
+ verify(mockRunHandler).log(
+ LogLevel.DEBUG,
+ "Found node /content/child/one/jcr:content for JCR-SQL2 query SELECT * FROM [cq:Pa…",
+ null,
+ null
+ );
+ verify(mockRunHandler).log(
+ LogLevel.INFO,
+ "Processed 2 nodes from JCR-SQL2 query SELECT * FROM [cq:Page] AS page INNER JOIN [cq:PageContent] AS pageContent ON ISCHILDNODE(pageContent, page)",
+ null,
+ null
+ );
+ }
+
+ @Test
+ public void sql2_explicitJoin() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery(
+ "SELECT * FROM [cq:Page] AS page INNER JOIN [cq:PageContent] AS pageContent ON ISCHILDNODE(pageContent, page)"
+ )
+ .withSelectorName("page")
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config()
+ .withCode(
+ "writer.print('#' + counter + ' page: ' + page.getPath() + ', pageContent: ' + pageContent.getPath())"
+ )
+ )
+ )
+ )
+ )
+ .run(session, true);
+
+ verify(mockRunHandler, never()).log(any(), startsWith("There are 2 selectors in the JCR-SQL2 query. "), any(), any());
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found node /content/child for JCR-SQL2 query SELECT * FROM [cq:Pa…", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found node /content/child/one for JCR-SQL2 query SELECT * FROM [cq:Pa…", null, null);
+ verify(mockRunHandler).log(
+ LogLevel.INFO,
+ "Processed 2 nodes from JCR-SQL2 query SELECT * FROM [cq:Page] AS page INNER JOIN [cq:PageContent] AS pageContent ON ISCHILDNODE(pageContent, page)",
+ null,
+ null
+ );
+
+ verify(mockRunHandler).print("#0 page: /content/child, pageContent: /content/child/jcr:content");
+ verify(mockRunHandler).print("#1 page: /content/child/one, pageContent: /content/child/one/jcr:content");
+ }
+
+ @Test
+ public void sql2_invalidSelector() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery("SELECT * FROM [cq:Page]")
+ .withSelectorName("test1")
+ )
+ )
+ .run(session, true);
+ });
+ }
+
+ @Test
+ public void sql2_customCounter() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery("SELECT * FROM [cq:Page]")
+ .withCounterName("cnt")
+ .withHops(
+ Collections.singletonList(
+ new RunScript.Config()
+ .withExtension("jexl")
+ .withCode("writer.print('#' + (cnt+1) + ' node: ' + node.getPath())")
+ )
+ )
+ )
+ )
+ .run(session, true);
+
+ verify(mockRunHandler).print("#1 node: /content/child");
+ verify(mockRunHandler).print("#2 node: /content/child/one");
+ }
+
+ @Test
+ public void sql2_limitAndOffset() throws HopperException, RepositoryException {
+ builder
+ .build(
+ new Script(
+ LogLevel.DEBUG,
+ new NodeQuery.Config()
+ .withQueryType(Query.JCR_SQL2)
+ .withQuery("SELECT * FROM [cq:Page]")
+ .withLimit(1)
+ .withOffset(1)
+ )
+ )
+ .run(session, true);
+
+ verify(mockRunHandler, never()).log(LogLevel.DEBUG, "Found node /content/child for JCR-SQL2 query SELECT * FROM [cq:Pa…", null, null);
+ verify(mockRunHandler).log(LogLevel.DEBUG, "Found node /content/child/one for JCR-SQL2 query SELECT * FROM [cq:Pa…", null, null);
+ verify(mockRunHandler).log(LogLevel.INFO, "Processed 1 nodes from JCR-SQL2 query SELECT * FROM [cq:Page]", null, null);
+ }
+}