Skip to content

Test using user and role for tests #6252

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

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
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
71 changes: 67 additions & 4 deletions packages/graphql/jest.global-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@ const neo4j = require("neo4j-driver");

const TZ = "Etc/UTC";
const INT_TEST_DB_NAME = "neo4jgraphqlinttestdatabase";
const INT_TEST_ROLE_NAME = "neo4jgraphqlinttestrole";
const INT_TEST_USER_NAME = "neo4jgraphqlinttestuser";

const cypherDropData = `MATCH (n) DETACH DELETE n`;
const cypherDropIndexes = `CALL apoc.schema.assert({},{},true) YIELD label, key RETURN *`;

const cypherCreateUser = `CREATE USER ${INT_TEST_USER_NAME} IF NOT EXISTS SET PASSWORD 'password' CHANGE NOT REQUIRED`;
const cypherCreateRole = `CREATE ROLE ${INT_TEST_ROLE_NAME} IF NOT EXISTS`;
const cypherGrantRole = `GRANT ROLE ${INT_TEST_ROLE_NAME} TO ${INT_TEST_USER_NAME}`;

const cypherDropUser = `DROP USER ${INT_TEST_USER_NAME}`;
const cypherDropRole = `DROP ROLE ${INT_TEST_ROLE_NAME}`;

module.exports = async function globalSetup() {
process.env.NODE_ENV = "test";

Expand All @@ -32,11 +41,11 @@ module.exports = async function globalSetup() {
await session.run(cypherCreateDb);
} catch (error) {
if (
error.message.includes(
"This is an administration command and it should be executed against the system database"
) ||
error.message.includes("This is an administration command and it should be executed against the system database") ||
error.message.includes("Unsupported administration command") ||
error.message.includes("Unable to route write operation to leader for database 'system'")
error.message.includes("Unable to route write operation to leader for database 'system'") ||
error.message.includes("CREATE DATABASE is not supported") ||
error.message.includes("DROP DATABASE is not supported")
) {
console.log(
`\nJest /packages/graphql setup: Will NOT create a separate integration test database as the command is not supported in the current environment.`
Expand All @@ -47,6 +56,49 @@ module.exports = async function globalSetup() {
);
await dropDataAndIndexes(session);
}
} finally {
if (session) await session.close();
}

// Some tests use different DBs, so using "*" for now
const dbName = "*"

const cypherGrants = [
`GRANT ACCESS ON DATABASE * TO ${INT_TEST_ROLE_NAME}`,
`GRANT SHOW CONSTRAINT ON DATABASE ${dbName} TO ${INT_TEST_ROLE_NAME}`,
`GRANT SHOW INDEX ON DATABASE ${dbName} TO ${INT_TEST_ROLE_NAME}`,
`GRANT MATCH {*} ON GRAPH ${dbName} TO ${INT_TEST_ROLE_NAME}`,
`GRANT EXECUTE PROCEDURE * ON DBMS TO ${INT_TEST_ROLE_NAME}`,
`GRANT EXECUTE FUNCTION * ON DBMS TO ${INT_TEST_ROLE_NAME}`,
`GRANT WRITE ON GRAPH ${dbName} TO ${INT_TEST_ROLE_NAME}`,
`GRANT NAME MANAGEMENT ON DATABASE ${dbName} TO ${INT_TEST_ROLE_NAME}`,
]

try {
session = driver.session();

await dropUserAndRole(session);
await createUserAndRole(session);

for (const cypherGrant of cypherGrants) {
await session.run(cypherGrant);
}
} catch (error) {
if (
error.message.includes("Permission has not been granted for CREATE USER")
) {
console.log(
`\nJest /packages/graphql setup: Will NOT create a separate integration test user as the command is not supported in the current environment.`
);
}

if (
error.message.includes("Permission has not been granted for CREATE ROLE")
) {
console.log(
`\nJest /packages/graphql setup: Will NOT create a separate integration test role as the command is not supported in the current environment.`
);
}
} finally {
if (session) await session.close();
if (driver) await driver.close();
Expand All @@ -57,3 +109,14 @@ async function dropDataAndIndexes(session) {
await session.run(cypherDropData);
await session.run(cypherDropIndexes);
}

async function dropUserAndRole(session) {
await session.run(cypherDropUser);
await session.run(cypherDropRole);
}

async function createUserAndRole(session) {
await session.run(cypherCreateUser);
await session.run(cypherCreateRole);
await session.run(cypherGrantRole);
}
65 changes: 56 additions & 9 deletions packages/graphql/tests/utils/tests-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class TestHelper {
private neo4jGraphQL: Neo4jGraphQL | undefined;
private uniqueTypes: UniqueType[] = [];
private driver: neo4j.Driver | undefined;
private graphQLDriver: neo4j.Driver | undefined;

private lock: boolean = false; // Lock to avoid race condition between initNeo4jGraphQL

Expand Down Expand Up @@ -74,8 +75,16 @@ export class TestHelper {
throw new Error("Error in getSubscriptionEngine. CDC is not enabled in test database");
}

let driver: neo4j.Driver | undefined;

try {
driver = await this.getGraphQLDriver();
} catch {
driver = await this.getDriver();
}

this.subscriptionEngine = new Neo4jGraphQLSubscriptionsCDCEngine({
driver: await this.getDriver(),
driver,
pollTime: 100,
queryConfig: {
database: this.database,
Expand All @@ -91,7 +100,15 @@ export class TestHelper {
throw new Error("Neo4jGraphQL already initialized. Did you forget calling .close()?");
}
this.lock = true;
const driver = await this.getDriver();

let driver: neo4j.Driver | undefined;

try {
driver = await this.getGraphQLDriver();
} catch {
driver = await this.getDriver();
}

this.neo4jGraphQL = new Neo4jGraphQL({
...options,
driver,
Expand Down Expand Up @@ -164,7 +181,7 @@ export class TestHelper {
}

public async close(preClose?: () => Promise<void>): Promise<void> {
if (!this.driver) {
if (!this.driver && !this.graphQLDriver) {
this.reset();
throw new Error("Closing unopened testHelper. Did you forget to call initNeo4jGraphQL?");
}
Expand All @@ -184,7 +201,14 @@ export class TestHelper {

/** Use this if using graphql() directly. If possible, use .runGraphQL */
public async getContextValue(options?: Record<string, unknown>): Promise<Neo4jGraphQLContext> {
const driver = await this.getDriver();
let driver: neo4j.Driver | undefined;

try {
driver = await this.getGraphQLDriver();
} catch {
driver = await this.getDriver();
}

return {
executionContext: driver,
sessionConfig: { database: this.database },
Expand All @@ -198,10 +222,6 @@ export class TestHelper {
}
const { NEO_USER = "neo4j", NEO_PASSWORD = "password", NEO_URL = "neo4j://localhost:7687/neo4j" } = process.env;

// if (process.env.NEO_WAIT) {
// await util.promisify(setTimeout)(Number(process.env.NEO_WAIT));
// }

const auth = neo4j.auth.basic(NEO_USER, NEO_PASSWORD);
const driver = neo4j.driver(NEO_URL, auth);
try {
Expand All @@ -216,11 +236,37 @@ export class TestHelper {
return this.driver;
}

public async getGraphQLDriver(): Promise<neo4j.Driver> {
if (this.graphQLDriver) {
return this.graphQLDriver;
}
const { NEO_PASSWORD = "password", NEO_URL = "neo4j://localhost:7687/neo4j" } = process.env;

const auth = neo4j.auth.basic("neo4jgraphqlinttestuser", NEO_PASSWORD);
const driver = neo4j.driver(NEO_URL, auth);
try {
this._database = await this.checkConnectivity(driver);
} catch (error: any) {
await driver.close();
throw new Error(`Could not connect to neo4j @ ${NEO_URL}, Error: ${error.message}`);
}

this.graphQLDriver = driver;

return this.graphQLDriver;
}

/** Use only for tests needing a session, for normal tests use `.runGraphQL` instead.
* Note that sessions will **not** be cleaned up with `testHelper.close`
* */
public async getSession(options?: Record<string, unknown>): Promise<neo4j.Session> {
const driver = await this.getDriver();
let driver: neo4j.Driver | undefined;

try {
driver = await this.getGraphQLDriver();
} catch {
driver = await this.getDriver();
}

const appliedOptions = { ...options, database: this.database };
return driver.session(appliedOptions);
Expand Down Expand Up @@ -274,6 +320,7 @@ export class TestHelper {

private reset() {
this.driver = undefined;
this.graphQLDriver = undefined;
this.uniqueTypes = [];
this.neo4jGraphQL = undefined;
this.lock = false;
Expand Down
Loading