diff --git a/e2e/tests/api-driven/src/hasuraTriggers/hasuraTriggers.feature b/e2e/tests/api-driven/src/hasuraTriggers/hasuraTriggers.feature index ab3c9b8e0f..ae01bbc2bb 100644 --- a/e2e/tests/api-driven/src/hasuraTriggers/hasuraTriggers.feature +++ b/e2e/tests/api-driven/src/hasuraTriggers/hasuraTriggers.feature @@ -2,11 +2,18 @@ Feature: Database triggers @regression @add-user-trigger Scenario: Adding a user to Planx - with Templates team - Given the Templates team exists + Given the "Templates" team exists When a new user is added Then they are granted access to the Templates team And have the teamEditor role + @regression @add-user-trigger + Scenario: Adding a user to Planx - with Templates team + Given the "Templates" team exists + Given the "Demo" team exists + When a new demoUser is added + Then they are not granted access to the Templates team + @regression @add-user-trigger Scenario: Adding a user to Planx - without Templates team Given the Templates team does not exist diff --git a/e2e/tests/api-driven/src/hasuraTriggers/helpers.ts b/e2e/tests/api-driven/src/hasuraTriggers/helpers.ts index 1d63422601..afd4d15fa9 100644 --- a/e2e/tests/api-driven/src/hasuraTriggers/helpers.ts +++ b/e2e/tests/api-driven/src/hasuraTriggers/helpers.ts @@ -1,6 +1,48 @@ +import { TEST_EMAIL } from "../../../ui-driven/src/helpers/globalHelpers"; import { $admin } from "../client"; +import { safely } from "../globalHelpers"; +import gql from "graphql-tag"; export const cleanup = async () => { await $admin.user._destroyAll(); await $admin.team._destroyAll(); }; + +export async function createDemoUser(demoTeamId: number) { + const variables = { + firstName: "Test", + lastName: "Test", + email: TEST_EMAIL, + teamId: demoTeamId, + role: "demoUser", + }; + + const response = await safely(() => + $admin.client.request<{ insertUsersOne: { id: number } }>( + gql` + mutation CreateAndAddUserToTeam( + $email: String! + $firstName: String! + $lastName: String! + $teamId: Int! + $role: user_roles_enum! + ) { + insertUsersOne: insert_users_one( + object: { + email: $email + first_name: $firstName + last_name: $lastName + teams: { data: { role: $role, team_id: $teamId } } + } + ) { + id + } + } + `, + variables, + ), + ); + + const userId = response.insertUsersOne.id; + return userId; +} diff --git a/e2e/tests/api-driven/src/hasuraTriggers/steps.ts b/e2e/tests/api-driven/src/hasuraTriggers/steps.ts index e76d02c591..8fc34686f7 100644 --- a/e2e/tests/api-driven/src/hasuraTriggers/steps.ts +++ b/e2e/tests/api-driven/src/hasuraTriggers/steps.ts @@ -1,5 +1,5 @@ import { After, Given, Then, When, World } from "@cucumber/cucumber"; -import { cleanup } from "./helpers"; +import { cleanup, createDemoUser } from "./helpers"; import { User } from "@opensystemslab/planx-core/types"; import { $admin } from "../client"; import assert from "assert"; @@ -8,18 +8,20 @@ import { createTeam, createUser } from "../globalHelpers"; export class CustomWorld extends World { user!: User; templatesTeamId!: number; + demoTeamId!: number; } After("@add-user-trigger", async function () { await cleanup(); }); -Given("the Templates team exists", async function (this) { - const templatesTeamId = await createTeam({ slug: "templates" }); +Given("the {string} team exists", async function (this, input) { + const teamSlug = input.toLowerCase(); + const teamId = await createTeam({ slug: teamSlug }); - assert.ok(templatesTeamId, "Templates team is not defined"); + assert.ok(teamId, `${teamSlug} team is not defined`); - this.templatesTeamId = templatesTeamId; + this[`${teamSlug}TeamId`] = teamId; }); Given("the Templates team does not exist", async function (this) { @@ -41,6 +43,15 @@ When("a new user is added", async function (this) { this.user = user; }); +When("a new demoUser is added", async function (this) { + const userId = await createDemoUser(this.demoTeamId); + const demoUser = await $admin.user.getById(userId); + + assert.ok(demoUser, "Demo user is not defined"); + + this.user = demoUser; +}); + Then( "they are granted access to the Templates team", async function (this) { @@ -57,6 +68,9 @@ Then("have the teamEditor role", async function (this) { Then( "they are not granted access to the Templates team", async function (this) { - assert.strictEqual(this.user.teams.length, 0); + const userTeamSlugs = this.user.teams.map((userTeam) => userTeam.team.slug); + const isTemplateTeamMember = userTeamSlugs.includes("templates"); + + assert.strictEqual(isTemplateTeamMember, false); }, ); diff --git a/hasura.planx.uk/migrations/1730277860890_run_sql_migration/down.sql b/hasura.planx.uk/migrations/1730277860890_run_sql_migration/down.sql new file mode 100644 index 0000000000..a85b933f20 --- /dev/null +++ b/hasura.planx.uk/migrations/1730277860890_run_sql_migration/down.sql @@ -0,0 +1,22 @@ +-- Previous definition from 1696884217237_grant_new_user_template_team_access/up.sql + +CREATE OR REPLACE FUNCTION grant_new_user_template_team_access() RETURNS trigger AS $$ +DECLARE + templates_team_id INT; +BEGIN + SELECT id INTO templates_team_id FROM teams WHERE slug = 'templates'; + IF templates_team_id IS NOT NULL THEN + INSERT INTO team_members (user_id, team_id, role) VALUES (NEW.id, templates_team_id, 'teamEditor'); + END IF; + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER grant_new_user_template_team_access ON users; + +CREATE TRIGGER grant_new_user_template_team_access AFTER INSERT ON users + FOR EACH ROW EXECUTE PROCEDURE grant_new_user_template_team_access(); + +COMMENT ON TRIGGER grant_new_user_template_team_access ON users +IS 'Automatically grant all new users teamEditor access to the shared Templates team'; \ No newline at end of file diff --git a/hasura.planx.uk/migrations/1730277860890_run_sql_migration/up.sql b/hasura.planx.uk/migrations/1730277860890_run_sql_migration/up.sql new file mode 100644 index 0000000000..21ddf49432 --- /dev/null +++ b/hasura.planx.uk/migrations/1730277860890_run_sql_migration/up.sql @@ -0,0 +1,36 @@ +CREATE OR REPLACE FUNCTION grant_new_user_template_access() RETURNS trigger AS $$ +DECLARE + templates_team_id INT; + is_demo_user BOOLEAN; +BEGIN + SELECT EXISTS ( + SELECT 1 + FROM team_members + WHERE user_id = NEW.id + AND role = 'demoUser' + ) INTO is_demo_user; + + -- Demo user should not get access as a teamEditor for the templates team... + IF is_demo_user THEN + RETURN NULL; + END IF; + + -- ...but all other users should + SELECT id INTO templates_team_id FROM teams WHERE slug = 'templates'; + IF templates_team_id IS NOT NULL THEN + INSERT INTO team_members (user_id, team_id, role) + VALUES (NEW.id, templates_team_id, 'teamEditor'); + END IF; + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS grant_new_user_template_team_access ON users; + +CREATE CONSTRAINT TRIGGER grant_new_user_template_team_access +AFTER INSERT ON users +DEFERRABLE INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION grant_new_user_template_access(); + +COMMENT ON TRIGGER grant_new_user_template_team_access ON users IS 'Automatically grant all new users teamEditor access to the shared Templates team (apart from users with the demoUser role)'; \ No newline at end of file