Skip to content

Migrate to using Testcontainers #1333

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

Merged
merged 4 commits into from
Nov 23, 2022
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
17 changes: 8 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,18 @@ To use the driver in a project, please use the released driver via Maven Central

#### Running Tests and Creating a Package

Our test setup requires a bit of infrastructure to run.
We rely mostly on [`testkit`](https://github.com/neo4j-drivers/testkit).
Testkit is a tooling that is used to run integration tests for all of the drivers we provide.

Some older tests still rely on [`boltkit`](https://github.com/neo4j-drivers/boltkit), the predecessor to Testkit.
If `boltkit` is not installed, then all tests that requires `boltkit` will be ignored and will not be executed.

In case you want or can verify contributions with unit tests alone, use the following command to skip all integration and Testkit tests:

The following command may be used to unit test and install artifacts without running integration tests:
```
mvn clean install -DskipITs -P skip-testkit
```

Integration tests have the following prerequisites:
- Docker
- [`Testkit`](https://github.com/neo4j-drivers/testkit)

Testkit that is a tooling that is used to run integration tests for all official Neo4j drivers.
It can be executed using Docker during Maven build and in such case does not require additional setup. See the instructions below for more details.

There are 2 ways of running Testkit tests:
1. Using the `testkit-tests` module of this project.
2. Manually cloning Testkit and running it directly.
Expand Down
24 changes: 24 additions & 0 deletions driver/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>org.junit.support</groupId>
<artifactId>testng-engine</artifactId>
</dependency>
<dependency>
<groupId>org.rauschig</groupId>
<artifactId>jarchivelib</artifactId>
Expand All @@ -86,6 +90,26 @@
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>neo4j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams-tck</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
Expand Down Expand Up @@ -243,6 +244,7 @@ void connectionUsedForTransactionReturnedToThePoolWhenTransactionRolledBack() {

@Test
void connectionUsedForTransactionReturnedToThePoolWhenTransactionFailsToCommitted() {
assumeTrue(neo4j.isNeo4j44OrEarlier());
try (Session session = driver.session()) {
session.run("CREATE CONSTRAINT ON (book:Library) ASSERT exists(book.isbn)");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ void shouldRecoverFromDownedServer() throws Throwable {
sessionGrabber.start();

// When
neo4j.forceRestartDb();
neo4j.stopProxy();
neo4j.startProxy();

// Then we accept a hump with failing sessions, but demand that failures stop as soon as the server is back up.
sessionGrabber.assertSessionsAvailableWithin(120);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,8 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.neo4j.driver.AuthTokens.basic;
import static org.neo4j.driver.AuthTokens.custom;
import static org.neo4j.driver.Values.ofValue;
import static org.neo4j.driver.Values.parameters;
import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING;
import static org.neo4j.driver.util.Neo4jRunner.PASSWORD;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
Expand All @@ -44,58 +39,18 @@
import org.neo4j.driver.Value;
import org.neo4j.driver.exceptions.AuthenticationException;
import org.neo4j.driver.exceptions.SecurityException;
import org.neo4j.driver.internal.security.InternalAuthToken;
import org.neo4j.driver.internal.util.DisabledOnNeo4jWith;
import org.neo4j.driver.internal.util.Neo4jFeature;
import org.neo4j.driver.util.DatabaseExtension;
import org.neo4j.driver.util.Neo4jSettings;
import org.neo4j.driver.util.ParallelizableIT;

@ParallelizableIT
class CredentialsIT {
@RegisterExtension
static final DatabaseExtension neo4j = new DatabaseExtension();

@Test
@DisabledOnNeo4jWith(Neo4jFeature.BOLT_V4)
// This feature is removed in 4.0
void shouldBePossibleToChangePassword() throws Exception {
String newPassword = "secret";
String tmpDataDir = Files.createTempDirectory(Paths.get("target"), "tmp")
.toAbsolutePath()
.toString()
.replace("\\", "/");

neo4j.restartDb(Neo4jSettings.TEST_SETTINGS.updateWith(Neo4jSettings.DATA_DIR, tmpDataDir));

AuthToken authToken = new InternalAuthToken(parameters(
"scheme", "basic",
"principal", "neo4j",
"credentials", "neo4j",
"new_credentials", newPassword)
.asMap(ofValue()));

// change the password
try (Driver driver = GraphDatabase.driver(neo4j.uri(), authToken);
Session session = driver.session()) {
session.run("RETURN 1").consume();
}

// verify old password does not work
final Driver badDriver = GraphDatabase.driver(CredentialsIT.neo4j.uri(), basic("neo4j", PASSWORD));
assertThrows(AuthenticationException.class, badDriver::verifyConnectivity);

// verify new password works
try (Driver driver = GraphDatabase.driver(CredentialsIT.neo4j.uri(), AuthTokens.basic("neo4j", newPassword));
Session session = driver.session()) {
session.run("RETURN 2").consume();
}
}

@Test
void basicCredentialsShouldWork() {
// When & Then
try (Driver driver = GraphDatabase.driver(neo4j.uri(), basic("neo4j", PASSWORD));
try (Driver driver = GraphDatabase.driver(neo4j.uri(), basic("neo4j", neo4j.adminPassword()));
Session session = driver.session()) {
Value single = session.run("RETURN 1").single().get(0);
assertThat(single.asLong(), equalTo(1L));
Expand All @@ -105,7 +60,8 @@ void basicCredentialsShouldWork() {
@Test
void shouldGetHelpfulErrorOnInvalidCredentials() {
SecurityException e = assertThrows(SecurityException.class, () -> {
try (Driver driver = GraphDatabase.driver(neo4j.uri(), basic("thisisnotthepassword", PASSWORD));
try (Driver driver =
GraphDatabase.driver(neo4j.uri(), basic("thisisnotthepassword", neo4j.adminPassword()));
Session session = driver.session()) {
session.run("RETURN 1");
}
Expand All @@ -116,7 +72,7 @@ void shouldGetHelpfulErrorOnInvalidCredentials() {
@Test
void shouldBeAbleToProvideRealmWithBasicAuth() {
// When & Then
try (Driver driver = GraphDatabase.driver(neo4j.uri(), basic("neo4j", PASSWORD, "native"));
try (Driver driver = GraphDatabase.driver(neo4j.uri(), basic("neo4j", neo4j.adminPassword(), "native"));
Session session = driver.session()) {
Value single = session.run("CREATE () RETURN 1").single().get(0);
assertThat(single.asLong(), equalTo(1L));
Expand All @@ -126,7 +82,8 @@ void shouldBeAbleToProvideRealmWithBasicAuth() {
@Test
void shouldBeAbleToConnectWithCustomToken() {
// When & Then
try (Driver driver = GraphDatabase.driver(neo4j.uri(), custom("neo4j", PASSWORD, "native", "basic"));
try (Driver driver =
GraphDatabase.driver(neo4j.uri(), custom("neo4j", neo4j.adminPassword(), "native", "basic"));
Session session = driver.session()) {
Value single = session.run("CREATE () RETURN 1").single().get(0);
assertThat(single.asLong(), equalTo(1L));
Expand All @@ -138,7 +95,8 @@ void shouldBeAbleToConnectWithCustomTokenWithAdditionalParameters() {
Map<String, Object> params = singletonMap("secret", 16);

// When & Then
try (Driver driver = GraphDatabase.driver(neo4j.uri(), custom("neo4j", PASSWORD, "native", "basic", params));
try (Driver driver = GraphDatabase.driver(
neo4j.uri(), custom("neo4j", neo4j.adminPassword(), "native", "basic", params));
Session session = driver.session()) {
Value single = session.run("CREATE () RETURN 1").single().get(0);
assertThat(single.asLong(), equalTo(1L));
Expand All @@ -147,12 +105,12 @@ void shouldBeAbleToConnectWithCustomTokenWithAdditionalParameters() {

@Test
void directDriverShouldFailEarlyOnWrongCredentials() {
testDriverFailureOnWrongCredentials("bolt://localhost:" + neo4j.boltPort());
testDriverFailureOnWrongCredentials(neo4j.uri().toString());
}

@Test
void routingDriverShouldFailEarlyOnWrongCredentials() {
testDriverFailureOnWrongCredentials("neo4j://localhost:" + neo4j.boltPort());
testDriverFailureOnWrongCredentials(neo4j.uri().toString());
}

private void testDriverFailureOnWrongCredentials(String uri) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,11 @@
import static org.neo4j.driver.internal.util.Matchers.directDriverWithAddress;

import java.net.URI;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.internal.BoltServerAddress;
import org.neo4j.driver.util.DatabaseExtension;
import org.neo4j.driver.util.ParallelizableIT;
Expand Down Expand Up @@ -87,17 +84,4 @@ void shouldRegisterSingleServer() {
// Then
assertThat(driver, is(directDriverWithAddress(address)));
}

@Test
void shouldConnectIPv6Uri() {
// Given
try (Driver driver = GraphDatabase.driver("bolt://[::1]:" + neo4j.boltPort(), neo4j.authToken());
Session session = driver.session()) {
// When
Result result = session.run("RETURN 1");

// Then
assertThat(result.single().get(0).asInt(), CoreMatchers.equalTo(1));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import static org.neo4j.driver.Config.TrustStrategy.trustAllCertificates;

import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.neo4j.driver.Config;
Expand Down Expand Up @@ -81,7 +83,9 @@ void shouldOperateWithoutEncryptionWhenItIsAlsoDisabledInTheDatabase() {
}

private void testMatchingEncryption(BoltTlsLevel tlsLevel, boolean driverEncrypted, String scheme) {
neo4j.restartDb(Neo4jSettings.TEST_SETTINGS.updateWith(Neo4jSettings.BOLT_TLS_LEVEL, tlsLevel.toString()));
Map<String, String> tlsConfig = new HashMap<>();
tlsConfig.put(Neo4jSettings.BOLT_TLS_LEVEL, tlsLevel.toString());
neo4j.deleteAndStartNeo4j(tlsConfig);
Config config;

if (scheme.equals(Scheme.BOLT_LOW_TRUST_URI_SCHEME)) {
Expand All @@ -107,7 +111,9 @@ private void testMatchingEncryption(BoltTlsLevel tlsLevel, boolean driverEncrypt
}

private void testMismatchingEncryption(BoltTlsLevel tlsLevel, boolean driverEncrypted) {
neo4j.restartDb(Neo4jSettings.TEST_SETTINGS.updateWith(Neo4jSettings.BOLT_TLS_LEVEL, tlsLevel.toString()));
Map<String, String> tlsConfig = new HashMap<>();
tlsConfig.put(Neo4jSettings.BOLT_TLS_LEVEL, tlsLevel.toString());
neo4j.deleteAndStartNeo4j(tlsConfig);
Config config = newConfig(driverEncrypted);

ServiceUnavailableException e = assertThrows(
Expand Down
43 changes: 22 additions & 21 deletions driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,32 +152,33 @@ void shouldExplainConnectionError() {

@Test
void shouldHandleFailureAtRunTime() {
String label = UUID.randomUUID().toString(); // avoid clashes with other tests

// given
Transaction tx = session.beginTransaction();
tx.run("CREATE CONSTRAINT ON (a:`" + label + "`) ASSERT a.name IS UNIQUE");
tx.commit();

// and
Transaction anotherTx = session.beginTransaction();

// then expect
ClientException e =
assertThrows(ClientException.class, () -> anotherTx.run("CREATE INDEX ON :`" + label + "`(name)"));
anotherTx.rollback();
assertThat(e.getMessage(), containsString(label));
assertThat(e.getMessage(), containsString("name"));
if (session.isNeo4j44OrEarlier()) {
String label = UUID.randomUUID().toString(); // avoid clashes with other tests

// given
Transaction tx = session.beginTransaction();
tx.run("CREATE CONSTRAINT ON (a:`" + label + "`) ASSERT a.name IS UNIQUE");
tx.commit();

// and
Transaction anotherTx = session.beginTransaction();

// then expect
ClientException e =
assertThrows(ClientException.class, () -> anotherTx.run("CREATE INDEX ON :`" + label + "`(name)"));
anotherTx.rollback();
assertThat(e.getMessage(), containsString(label));
assertThat(e.getMessage(), containsString("name"));
}
}

@Test
void shouldGetHelpfulErrorWhenTryingToConnectToHttpPort() throws Throwable {
// the http server needs some time to start up
Thread.sleep(2000);

void shouldGetHelpfulErrorWhenTryingToConnectToHttpPort() {
Config config = Config.builder().withoutEncryption().build();

final Driver driver = GraphDatabase.driver("bolt://localhost:" + session.httpPort(), config);
URI boltUri = session.uri();
URI uri = URI.create(String.format("%s://%s:%d", boltUri.getScheme(), boltUri.getHost(), session.httpPort()));
final Driver driver = GraphDatabase.driver(uri, config);
ClientException e = assertThrows(ClientException.class, driver::verifyConnectivity);
assertEquals(
"Server responded HTTP. Make sure you are not trying to connect to the http endpoint "
Expand Down
22 changes: 14 additions & 8 deletions driver/src/test/java/org/neo4j/driver/integration/LoadCSVIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,12 @@
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.util.DatabaseExtension;
import org.neo4j.driver.util.Neo4jSettings;
import org.neo4j.driver.util.ParallelizableIT;

@ParallelizableIT
class LoadCSVIT {
@RegisterExtension
static final DatabaseExtension neo4j =
new DatabaseExtension(Neo4jSettings.TEST_SETTINGS.without(Neo4jSettings.IMPORT_DIR));
static final DatabaseExtension neo4j = new DatabaseExtension();

@Test
void shouldLoadCSV() throws Throwable {
Expand All @@ -47,13 +45,21 @@ void shouldLoadCSV() throws Throwable {
String csvFileUrl = createLocalIrisData(session);

// When
Result result = session.run(
"USING PERIODIC COMMIT 40\n" + "LOAD CSV WITH HEADERS FROM $csvFileUrl AS l\n"
String query = neo4j.isNeo4j44OrEarlier()
? "USING PERIODIC COMMIT 40\n" + "LOAD CSV WITH HEADERS FROM $csvFileUrl AS l\n"
+ "MATCH (c:Class {name: l.class_name})\n"
+ "CREATE (s:Sample {sepal_length: l.sepal_length, sepal_width: l.sepal_width, petal_length: l.petal_length, petal_width: l.petal_width})\n"
+ "CREATE (c)<-[:HAS_CLASS]-(s) "
+ "RETURN count(*) AS c",
parameters("csvFileUrl", csvFileUrl));
+ "RETURN count(*) AS c"
: "LOAD CSV WITH HEADERS FROM $csvFileUrl AS l\n" + "CALL {\n"
+ "WITH l\n"
+ "MATCH (c:Class {name: l.class_name})\n"
+ "CREATE (s:Sample {sepal_length: l.sepal_length, sepal_width: l.sepal_width, petal_length: l.petal_length, petal_width: l.petal_width})\n"
+ "CREATE (c)<-[:HAS_CLASS]-(s)"
+ "} IN TRANSACTIONS\n"
+ "RETURN count(*) AS c";

Result result = session.run(query, parameters("csvFileUrl", csvFileUrl));

// Then
assertThat(result.next().get("c").asInt(), equalTo(150));
Expand All @@ -66,7 +72,7 @@ private String createLocalIrisData(Session session) throws IOException {
session.run("CREATE (c:Class {name: $className}) RETURN c", parameters("className", className));
}

return neo4j.putTmpFile("iris", ".csv", IRIS_DATA).toExternalForm();
return neo4j.addImportFile("iris", ".csv", IRIS_DATA);
}

private static String[] IRIS_CLASS_NAMES = new String[] {"Iris-setosa", "Iris-versicolor", "Iris-virginica"};
Expand Down
Loading