Skip to content

Commit 55977c0

Browse files
committed
Added support for test containers for the ArcadeDB database
1 parent 8c8da7a commit 55977c0

File tree

5 files changed

+271
-0
lines changed

5 files changed

+271
-0
lines changed

modules/arcadedb/build.gradle

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
description = "Testcontainers :: Arcadedb"
2+
3+
dependencies {
4+
api project(":testcontainers")
5+
6+
api "com.arcadedb:arcadedb-engine:25.3.2"
7+
api "com.arcadedb:arcadedb-network:25.3.2"
8+
testImplementation 'org.assertj:assertj-core:3.27.4'
9+
testImplementation 'org.apache.tinkerpop:gremlin-driver:3.7.3'
10+
testImplementation "com.arcadedb:arcadedb-gremlin:25.3.2"
11+
}
12+
13+
tasks.japicmp {
14+
classExcludes = [
15+
"org.testcontainers.containers.ArcadeDBContainer"
16+
]
17+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package org.testcontainers.containers;
2+
3+
import com.arcadedb.remote.RemoteDatabase;
4+
import com.arcadedb.remote.RemoteServer;
5+
import com.github.dockerjava.api.command.InspectContainerResponse;
6+
import lombok.Getter;
7+
import lombok.NonNull;
8+
import org.apache.commons.io.IOUtils;
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
11+
import org.testcontainers.containers.wait.strategy.Wait;
12+
import org.testcontainers.utility.DockerImageName;
13+
14+
import java.io.IOException;
15+
import java.net.URL;
16+
import java.nio.charset.StandardCharsets;
17+
import java.util.Optional;
18+
19+
/**
20+
* Testcontainers implementation for ArcadeDB.
21+
* <p>
22+
* Supported image: {@code arcadedb} with JDK version up to 17
23+
* <p>
24+
* Exposed ports:
25+
* <ul>
26+
* <li>Database: 2424</li>
27+
* <li>Remote database and Studio: 2480</li>
28+
* </ul>
29+
*/
30+
public class ArcadeDBContainer extends GenericContainer<ArcadeDBContainer> {
31+
32+
private static final Logger LOGGER = LoggerFactory.getLogger(ArcadeDBContainer.class);
33+
34+
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("arcadedata/arcadedb");
35+
36+
private static final String DEFAULT_SERVER_PASSWORD = "playwithdata";
37+
38+
private static final String DEFAULT_DATABASE_NAME = "testcontainers";
39+
40+
private static final int DEFAULT_BINARY_PORT = 2424;
41+
42+
private static final int DEFAULT_HTTP_PORT = 2480;
43+
44+
@Getter
45+
private String databaseName;
46+
47+
private String serverPassword;
48+
49+
private int serverPort;
50+
51+
private Optional<String> scriptPath = Optional.empty();
52+
53+
private RemoteServer remoteServer;
54+
private RemoteDatabase database;
55+
56+
public ArcadeDBContainer(@NonNull String dockerImageName) {
57+
this(DockerImageName.parse(dockerImageName));
58+
}
59+
60+
public ArcadeDBContainer(final DockerImageName dockerImageName) {
61+
super(dockerImageName);
62+
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
63+
64+
serverPort = DEFAULT_HTTP_PORT;
65+
serverPassword = DEFAULT_SERVER_PASSWORD;
66+
databaseName = DEFAULT_DATABASE_NAME;
67+
68+
waitStrategy = Wait.forLogMessage(".*ArcadeDB Server started.*", 1);
69+
70+
addExposedPorts(DEFAULT_BINARY_PORT, DEFAULT_HTTP_PORT);
71+
}
72+
73+
@Override
74+
protected void configure() {
75+
final String javaOpts = String.format("-Darcadedb.server.rootPassword=%s", serverPassword);
76+
77+
addEnv("JAVA_OPTS", javaOpts);
78+
}
79+
80+
public synchronized RemoteDatabase getDatabase() {
81+
82+
final String host = getHost();
83+
final Integer port = getMappedPort(serverPort);
84+
if (remoteServer == null) {
85+
try {
86+
remoteServer = new RemoteServer(host, port, "root", serverPassword);
87+
} catch (Exception e) {
88+
final String msg = String.format("Could not connect to server %s:%d with user 'root' due to %s",
89+
host, port, e.getMessage());
90+
LOGGER.error(msg, e);
91+
throw new IllegalStateException(msg, e);
92+
}
93+
}
94+
95+
if (!remoteServer.exists(getDatabaseName())) {
96+
remoteServer.create(getDatabaseName());
97+
}
98+
99+
if (database != null && database.isOpen()) {
100+
return database;
101+
}
102+
103+
try {
104+
database = new RemoteDatabase(host, port, getDatabaseName(), "root", serverPassword);
105+
scriptPath.ifPresent(path -> loadScript(path, database));
106+
return database;
107+
} catch (Exception e) {
108+
final String msg = String.format("Could not connect to database %s on server %s:%d due to %s",
109+
getDatabaseName(), host, port, e.getMessage());
110+
LOGGER.error(msg, e);
111+
throw new IllegalStateException(msg, e);
112+
}
113+
}
114+
115+
public ArcadeDBContainer withDatabaseName(final String databaseName) {
116+
this.databaseName = databaseName;
117+
return self();
118+
}
119+
120+
public ArcadeDBContainer withServerPassword(final String serverPassword) {
121+
this.serverPassword = serverPassword;
122+
return self();
123+
}
124+
125+
public ArcadeDBContainer withServerPort(final int serverPort) {
126+
this.serverPort = serverPort;
127+
return self();
128+
}
129+
130+
public ArcadeDBContainer withScriptPath(String scriptPath) {
131+
this.scriptPath = Optional.of(scriptPath);
132+
return self();
133+
}
134+
135+
@Override
136+
protected void containerIsStarted(InspectContainerResponse containerInfo) {
137+
final String host = getHost();
138+
final Integer port = getMappedPort(serverPort);
139+
140+
try {
141+
remoteServer = new RemoteServer(host, port, "root", serverPassword);
142+
} catch (Exception e) {
143+
final String msg = String.format("Could not connect to server %s:%d with user 'root' due to %s",
144+
host, port, e.getMessage());
145+
LOGGER.error(msg, e);
146+
throw new IllegalStateException(msg, e);
147+
}
148+
}
149+
150+
private void loadScript(String path, RemoteDatabase db) {
151+
try {
152+
URL resource = getClass().getClassLoader().getResource(path);
153+
154+
if (resource == null) {
155+
LOGGER.warn("Could not load classpath init script: {}", scriptPath);
156+
throw new RuntimeException(
157+
"Could not load classpath init script: " + scriptPath + ". Resource not found."
158+
);
159+
}
160+
161+
String script = IOUtils.toString(resource, StandardCharsets.UTF_8);
162+
163+
db.command("sqlscript", script);
164+
} catch (IOException e) {
165+
LOGGER.warn("Could not load classpath init script: {}", scriptPath);
166+
throw new RuntimeException("Could not load classpath init script: " + scriptPath, e);
167+
} catch (UnsupportedOperationException e) {
168+
LOGGER.error("Error while executing init script: {}", scriptPath, e);
169+
throw new RuntimeException("Error while executing init script: " + scriptPath, e);
170+
}
171+
}
172+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.testcontainers.containers;
2+
3+
import com.arcadedb.remote.RemoteDatabase;
4+
import org.junit.Test;
5+
import org.testcontainers.utility.DockerImageName;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
9+
public class ArcadeDBContainerTest {
10+
11+
private static final DockerImageName ARCADEDB_IMAGE = DockerImageName.parse("arcadedata/arcadedb:24.4.1")
12+
.asCompatibleSubstituteFor("arcadedb");
13+
14+
@Test
15+
public void shouldReturnTheSameSession() {
16+
try ( // container {
17+
ArcadeDBContainer arcadedb = new ArcadeDBContainer("arcadedata/arcadedb:25.3.2")
18+
// }
19+
) {
20+
arcadedb.start();
21+
22+
final RemoteDatabase database = arcadedb.getDatabase();
23+
final RemoteDatabase database2 = arcadedb.getDatabase();
24+
25+
assertThat(database).isSameAs(database2);
26+
}
27+
}
28+
29+
@Test
30+
public void shouldInitializeWithCommands() {
31+
try (ArcadeDBContainer arcadedb = new ArcadeDBContainer(ARCADEDB_IMAGE)) {
32+
arcadedb.start();
33+
34+
final RemoteDatabase db = arcadedb.getDatabase();
35+
36+
db.command("sql", "create vertex type Person");
37+
db.command("sql", "INSERT INTO Person set name='john'");
38+
db.command("sql", "INSERT INTO Person set name='jane'");
39+
40+
assertThat(db.query("sql", "SELECT FROM Person").stream()).hasSize(2);
41+
}
42+
}
43+
44+
@Test
45+
public void shouldInitializeDatabaseFromScript() {
46+
try (
47+
ArcadeDBContainer arcadedb = new ArcadeDBContainer(ARCADEDB_IMAGE)
48+
.withScriptPath("initscript.sql")
49+
.withDatabaseName("persons")
50+
) {
51+
arcadedb.start();
52+
53+
final RemoteDatabase database = arcadedb.getDatabase();
54+
55+
assertThat(database.query("sql", "SELECT FROM Person").stream()).hasSize(4);
56+
}
57+
}
58+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CREATE VERTEX Type Person;
2+
3+
INSERT INTO Person set name="john";
4+
INSERT INTO Person set name="paul";
5+
INSERT INTO Person set name="luke";
6+
INSERT INTO Person set name="albert";
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<configuration>
2+
3+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
4+
<!-- encoders are assigned the type
5+
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
6+
<encoder>
7+
<pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
8+
</encoder>
9+
</appender>
10+
11+
<root level="INFO">
12+
<appender-ref ref="STDOUT"/>
13+
</root>
14+
15+
<logger name="org.testcontainers" level="INFO"/>
16+
<logger name="org.testcontainers.shaded" level="WARN"/>
17+
18+
</configuration>

0 commit comments

Comments
 (0)