Skip to content
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
4 changes: 4 additions & 0 deletions docs/modules/neo4j.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ npm install @testcontainers/neo4j --save-dev
<!--codeinclude-->
[Configure APOC:](../../packages/modules/neo4j/src/neo4j-container.test.ts) inside_block:apoc
<!--/codeinclude-->

<!--codeinclude-->
[Configure other supported plugins:](../../packages/modules/neo4j/src/neo4j-container.test.ts) inside_block:pluginsList
<!--/codeinclude-->
2 changes: 1 addition & 1 deletion packages/modules/neo4j/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { Neo4jContainer, StartedNeo4jContainer } from "./neo4j-container";
export { Neo4jContainer, Neo4jPlugin, StartedNeo4jContainer } from "./neo4j-container";
32 changes: 31 additions & 1 deletion packages/modules/neo4j/src/neo4j-container.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import neo4j from "neo4j-driver";
import { Neo4jContainer } from "./neo4j-container";
import { Neo4jContainer, Neo4jPlugin } from "./neo4j-container";

describe("Neo4jContainer", { timeout: 180_000 }, () => {
// createNode {
Expand Down Expand Up @@ -83,4 +83,34 @@ describe("Neo4jContainer", { timeout: 180_000 }, () => {
await container.stop();
});
// }

// pluginsList {
it("should work with plugin list", async () => {
const container = await new Neo4jContainer("neo4j:5.26.5")
.withPlugins([Neo4jPlugin.APOC_EXTENDED, Neo4jPlugin.GRAPH_DATA_SCIENCE])
.withStartupTimeout(120_000)
.start();
const driver = neo4j.driver(
container.getBoltUri(),
neo4j.auth.basic(container.getUsername(), container.getPassword())
);

const session = driver.session();

// Monitor methods are only available in extended Apoc.
const result = await session.run("CALL apoc.monitor.ids()");
expect(result.records[0].get("nodeIds")).toEqual({ high: 0, low: 0 });

// Insert one node.
await session.run("CREATE (a:Person {name: $name}) RETURN a", { name: "Some dude" });

// Monitor result should reflect increase in data.
const result2 = await session.run("CALL apoc.monitor.ids()");
expect(result2.records[0].get("nodeIds")).toEqual({ high: 0, low: 1 });

await session.close();
await driver.close();
await container.stop();
});
// }
});
45 changes: 36 additions & 9 deletions packages/modules/neo4j/src/neo4j-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,28 @@ const BOLT_PORT = 7687;
const HTTP_PORT = 7474;
const USERNAME = "neo4j";

export enum Neo4jPlugin {
APOC = "apoc",
APOC_EXTENDED = "apoc-extended",
BLOOM = "bloom",
GEN_AI = "genai",
GRAPHQL = "graphql",
GRAPH_ALGORITHMS = "graph-algorithms",
GRAPH_DATA_SCIENCE = "graph-data-science",
NEO_SEMANTICS = "n10s",
}

const pluginWhitelists: Partial<Record<Neo4jPlugin, string>> = {
[Neo4jPlugin.APOC]: "apoc.*",
[Neo4jPlugin.APOC_EXTENDED]: "apoc.*",
[Neo4jPlugin.BLOOM]: "bloom.*",
[Neo4jPlugin.GRAPH_DATA_SCIENCE]: "gds.*",
};

export class Neo4jContainer extends GenericContainer {
private password = "pass123!@#WORD";
private apoc = false;
private ttl?: number;
private plugins: Neo4jPlugin[] = [];

constructor(image = "neo4j:4.4.12") {
super(image);
Expand All @@ -23,11 +41,12 @@ export class Neo4jContainer extends GenericContainer {
}

public withApoc(): this {
this.apoc = true;
this.withEnvironment({
NEO4JLABS_PLUGINS: '["apoc"]',
NEO4J_dbms_security_procedures_unrestricted: "apoc.*",
});
this.plugins.push(Neo4jPlugin.APOC);
return this;
}

public withPlugins(plugins: Neo4jPlugin[]): this {
this.plugins = plugins;
return this;
}

Expand All @@ -44,10 +63,18 @@ export class Neo4jContainer extends GenericContainer {
NEO4J_apoc_ttl_schedule: this.ttl.toString(),
});
}
if (this.apoc) {

if (this.plugins.length > 0) {
const whitelists: string = this.plugins
.map((plugin) => (plugin in pluginWhitelists ? pluginWhitelists[plugin] : null))
.filter((whitelisted) => whitelisted !== null)
.join(",");

this.withEnvironment({
NEO4JLABS_PLUGINS: '["apoc"]',
NEO4J_dbms_security_procedures_unrestricted: "apoc.*",
NEO4JLABS_PLUGINS: JSON.stringify(this.plugins), // Older variant for older images.
NEO4J_PLUGINS: JSON.stringify(this.plugins),
NEO4J_dbms_security_procedures_unrestricted: whitelists,
NEO4J_dbms_security_procedures_whitelist: whitelists,
});
}
return new StartedNeo4jContainer(await super.start(), this.password);
Expand Down