diff --git a/docs/modules/neo4j.md b/docs/modules/neo4j.md index 63bae2ec1..a1776745c 100644 --- a/docs/modules/neo4j.md +++ b/docs/modules/neo4j.md @@ -21,3 +21,7 @@ npm install @testcontainers/neo4j --save-dev [Configure APOC:](../../packages/modules/neo4j/src/neo4j-container.test.ts) inside_block:apoc + + +[Configure other supported plugins:](../../packages/modules/neo4j/src/neo4j-container.test.ts) inside_block:pluginsList + diff --git a/packages/modules/neo4j/src/index.ts b/packages/modules/neo4j/src/index.ts index 643eebd0f..3e5326437 100644 --- a/packages/modules/neo4j/src/index.ts +++ b/packages/modules/neo4j/src/index.ts @@ -1 +1 @@ -export { Neo4jContainer, StartedNeo4jContainer } from "./neo4j-container"; +export { Neo4jContainer, Neo4jPlugin, StartedNeo4jContainer } from "./neo4j-container"; diff --git a/packages/modules/neo4j/src/neo4j-container.test.ts b/packages/modules/neo4j/src/neo4j-container.test.ts index fea0a0e2f..bafdffbdc 100755 --- a/packages/modules/neo4j/src/neo4j-container.test.ts +++ b/packages/modules/neo4j/src/neo4j-container.test.ts @@ -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 { @@ -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(); + }); + // } }); diff --git a/packages/modules/neo4j/src/neo4j-container.ts b/packages/modules/neo4j/src/neo4j-container.ts index 79cae90e2..03ba0c3e6 100755 --- a/packages/modules/neo4j/src/neo4j-container.ts +++ b/packages/modules/neo4j/src/neo4j-container.ts @@ -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> = { + [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); @@ -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; } @@ -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);