Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add additional convenience methods for Neo4j containers #1246

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions docs/modules/databases/neo4j.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,76 @@ public class ExampleTest {

You are not limited to Unit tests and can of course use an instance of the Neo4j Testcontainer in vanilla Java code as well.

## Additional features

### Disable authentication

Authentication can be disabled:

```java
@Testcontainers
public class ExampleTest {

@Container
Neo4jContainer neo4jContainer = new Neo4jContainer()
.withoutAuthentication();
}
```

### Neo4j-Configuration

Neo4j's Docker image needs Neo4j configuration options in a dedicated format.
The container takes care of that and you can configure the database with standard options like the following:

```java
@Testcontainers
public class ExampleTest {

@Container
Neo4jContainer neo4jContainer = new Neo4jContainer()
.withNeo4jConfig("dbms.security.procedures.unrestricted", "apoc.*,algo.*");
}
```

### Add custom plugins

Custom plugins, like APOC, can be copied over to the container from any classpath or host resource like this:

```java
@Testcontainers
public class ExampleTest {

@Container
Neo4jContainer neo4jContainer = new Neo4jContainer()
.withPlugins(MountableFile.forClasspathResource("/apoc-3.5.0.1-all.jar"));
}
```

Whole directories work as well:

```java
@Testcontainers
public class ExampleTest {

@Container
Neo4jContainer neo4jContainer = new Neo4jContainer()
.withPlugins(MountableFile.forClasspathResource("/my-plugins"));
}
```

### Start the container with a predefined database

If you have an existing database (`graph.db`) you want to work with, copy it over to the container like this:

```java
@Testcontainers
public class ExampleTest {

@Container
Neo4jContainer neo4jContainer = new Neo4jContainer()
.withDatabase(MountableFile.forClasspathResource("/test-graph.db"));
}
```

## Choose your Neo4j license

Expand Down
26 changes: 26 additions & 0 deletions modules/neo4j/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
description = "TestContainers :: Neo4j"

def generatedResourcesDir = new File(project.buildDir, "generated-resources")
def customNeo4jPluginDestinationDir = new File(generatedResourcesDir, "custom-plugins")

sourceSets {
customNeo4jPlugin {
java {
srcDir 'src/custom-neo4j-plugin'
}
}
test {
resources {
srcDir generatedResourcesDir
}
}
}

task customNeo4jPluginJar(type: Jar) {
from sourceSets.customNeo4jPlugin.output
archiveName = "hello-world.jar"
destinationDir = customNeo4jPluginDestinationDir
}

test.dependsOn customNeo4jPluginJar

dependencies {
customNeo4jPluginCompileOnly "org.neo4j:neo4j:3.5.3"

compile project(":testcontainers")

testCompile "org.neo4j.driver:neo4j-java-driver:1.7.2"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ac.simons.neo4j.demos.plugins;

import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.UserFunction;

public class HelloWorld {

@UserFunction("ac.simons.helloWorld")
@Description("Simple Hello World")
public String helloWorld(@Name("name") String name) {

return "Hello, " + name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
import org.testcontainers.containers.wait.strategy.WaitStrategy;
import org.testcontainers.utility.LicenseAcceptance;
import org.testcontainers.utility.MountableFile;

/**
* Testcontainer for Neo4j.
Expand Down Expand Up @@ -164,10 +165,76 @@ public S withAdminPassword(final String adminPassword) {
return self();
}

/**
* Disables authentication.
*
* @return This container.
*/
public S withoutAuthentication() {
return withAdminPassword(null);
}

/**
* Copies an existing {@code graph.db} folder into the container. This can either be a classpath resource or a
* host resource. Please have a look at the factory methods in {@link MountableFile}.
* <br>
* If you want to map your database into the container instead of copying them, please use {@code #withClasspathResourceMapping},
* but this will only work when your test does not run in a container itself.
* <br>
* Mapping would work like this:
* <pre>
* &#64;Container
* private static final Neo4jContainer databaseServer = new Neo4jContainer&lt;&gt;()
* .withClasspathResourceMapping("/test-graph.db", "/data/databases/graph.db", BindMode.READ_WRITE);
* </pre>
*
* @param graphDb The graph.db folder to copy into the container
* @return This container.
*/
public S withDatabase(MountableFile graphDb) {
return withCopyFileToContainer(graphDb, "/data/databases/graph.db");
}

/**
* Adds plugins to the given directory to the container. If {@code plugins} denotes a directory, than all of that
* directory is mapped to Neo4j's plugins. Otherwise, single resources are copied over.
* <br>
* If you want to map your plugins into the container instead of copying them, please use {@code #withClasspathResourceMapping},
* but this will only work when your test does not run in a container itself.
*
* @param plugins
* @return This container.
*/
public S withPlugins(MountableFile plugins) {
return withCopyFileToContainer(plugins, "/var/lib/neo4j/plugins/");
}

/**
* Adds Neo4j configuration properties to the container. The properties can be added as in the official Neo4j
* configuration, the method automatically translate them into the format required by the Neo4j container.
*
* @param key The key to configure, i.e. {@code dbms.security.procedures.unrestricted}
* @param value The value to set
* @return This container.
*/
public S withNeo4jConfig(String key, String value) {

addEnv(formatConfigurationKey(key), value);
return self();
}

/**
* @return The admin password for the <code>neo4j</code> account or literal <code>null</code> if auth is disabled.
*/
public String getAdminPassword() {
return adminPassword;
}

private static String formatConfigurationKey(String plainConfigKey) {
final String prefix = "NEO4J_";

return String.format("%s%s", prefix, plainConfigKey
.replaceAll("_", "__")
.replaceAll("\\.", "_"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
import org.neo4j.driver.v1.AuthTokens;
import org.neo4j.driver.v1.Driver;
import org.neo4j.driver.v1.GraphDatabase;
import org.neo4j.driver.v1.Record;
import org.neo4j.driver.v1.Session;
import org.neo4j.driver.v1.StatementResult;
import org.testcontainers.utility.MountableFile;

/**
* Tests of functionality special to the Neo4jContainer.
Expand All @@ -26,7 +29,7 @@ public class Neo4jContainerTest {
public void shouldDisableAuthentication() {

try (
Neo4jContainer neo4jContainer = new Neo4jContainer().withAdminPassword(null);
Neo4jContainer neo4jContainer = new Neo4jContainer().withoutAuthentication();
) {
neo4jContainer.start();
try (Driver driver = getDriver(neo4jContainer);
Expand All @@ -38,6 +41,63 @@ public void shouldDisableAuthentication() {
}
}

@Test
public void shouldCopyDatabase() {
try (
Neo4jContainer neo4jContainer = new Neo4jContainer()
.withDatabase(MountableFile.forClasspathResource("/test-graph.db"));
) {
neo4jContainer.start();
try (
Driver driver = getDriver(neo4jContainer);
Session session = driver.session()
) {
StatementResult result = session.run("MATCH (t:Thing) RETURN t");
assertThat(result.list().stream().map(r -> r.get("t").get("name").asString()))
.containsExactlyInAnyOrder("Thing", "Thing 2", "Thing 3", "A box");
}
}
}

@Test
public void shouldCopyPlugins() {
try (
Neo4jContainer neo4jContainer = new Neo4jContainer()
.withPlugins(MountableFile.forClasspathResource("/custom-plugins"));
) {
neo4jContainer.start();
try (
Driver driver = getDriver(neo4jContainer);
Session session = driver.session()
) {
assertThatCustomPluginWasCopied(session);
}
}
}

@Test
public void shouldCopyPlugin() {
try (
Neo4jContainer neo4jContainer = new Neo4jContainer()
.withPlugins(MountableFile.forClasspathResource("/custom-plugins/hello-world.jar"));
) {
neo4jContainer.start();
try (
Driver driver = getDriver(neo4jContainer);
Session session = driver.session()
) {
assertThatCustomPluginWasCopied(session);
}
}
}

private static void assertThatCustomPluginWasCopied(Session session) {
StatementResult result = session.run("RETURN ac.simons.helloWorld('Testcontainers') AS greeting");
Record singleRecord = result.single();
assertThat(singleRecord).isNotNull();
assertThat(singleRecord.get("greeting").asString()).isEqualTo("Hello, Testcontainers");
}

@Test
public void shouldCheckEnterpriseLicense() {
assumeThat(Neo4jContainerTest.class.getResource(ACCEPTANCE_FILE_LOCATION)).isNull();
Expand Down Expand Up @@ -71,6 +131,19 @@ public void shouldRunEnterprise() {
}
}

@Test
public void shouldAddConfigToEnvironment() {

Neo4jContainer neo4jContainer = new Neo4jContainer()
.withNeo4jConfig("dbms.security.procedures.unrestricted", "apoc.*,algo.*")
.withNeo4jConfig("dbms.tx_log.rotation.size", "42M");

assertThat(neo4jContainer.getEnvMap())
.containsEntry("NEO4J_dbms_security_procedures_unrestricted", "apoc.*,algo.*");
assertThat(neo4jContainer.getEnvMap())
.containsEntry("NEO4J_dbms_tx__log_rotation_size", "42M");
}

private static Driver getDriver(Neo4jContainer container) {

AuthToken authToken = AuthTokens.none();
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.