diff --git a/e2e/tests/api-driven/package.json b/e2e/tests/api-driven/package.json index 175920ce60..803682bfee 100644 --- a/e2e/tests/api-driven/package.json +++ b/e2e/tests/api-driven/package.json @@ -6,7 +6,7 @@ }, "dependencies": { "@cucumber/cucumber": "^9.3.0", - "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#17f3bf5", + "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#f2a22d3", "axios": "^1.4.0", "dotenv": "^16.3.1", "dotenv-expand": "^10.0.0", diff --git a/e2e/tests/api-driven/pnpm-lock.yaml b/e2e/tests/api-driven/pnpm-lock.yaml index b2ba3da222..e85f71a359 100644 --- a/e2e/tests/api-driven/pnpm-lock.yaml +++ b/e2e/tests/api-driven/pnpm-lock.yaml @@ -9,8 +9,8 @@ dependencies: specifier: ^9.3.0 version: 9.3.0 '@opensystemslab/planx-core': - specifier: git+https://github.com/theopensystemslab/planx-core#17f3bf5 - version: github.com/theopensystemslab/planx-core/17f3bf5 + specifier: git+https://github.com/theopensystemslab/planx-core#f2a22d3 + version: github.com/theopensystemslab/planx-core/f2a22d3 axios: specifier: ^1.4.0 version: 1.4.0 @@ -94,6 +94,13 @@ packages: regenerator-runtime: 0.14.0 dev: false + /@babel/runtime@7.22.15: + resolution: {integrity: sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.0 + dev: false + /@babel/runtime@7.22.6: resolution: {integrity: sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==} engines: {node: '>=6.9.0'} @@ -494,8 +501,8 @@ packages: resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} dev: false - /@mui/base@5.0.0-beta.14(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Je/9JzzYObsuLCIClgE8XvXNFb55IEz8n2NtStUfASfNiVrwiR8t6VVFFuhofehkyTIN34tq1qbBaOjCnOovBw==} + /@mui/base@5.0.0-beta.16(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-OYxhC81c9bO0wobGcM8rrY5bRwpCXAI21BL0P2wz/2vTv4ek7ALz9+U5M8wgdmtRNUhmCmAB4L2WRwFRf5Cd8Q==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 @@ -505,25 +512,23 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.11 - '@emotion/is-prop-valid': 1.2.1 + '@babel/runtime': 7.22.15 '@floating-ui/react-dom': 2.0.2(react-dom@18.2.0)(react@18.2.0) '@mui/types': 7.2.4 - '@mui/utils': 5.14.8(react@18.2.0) + '@mui/utils': 5.14.10(react@18.2.0) '@popperjs/core': 2.11.8 clsx: 2.0.0 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-is: 18.2.0 dev: false - /@mui/core-downloads-tracker@5.14.8: - resolution: {integrity: sha512-8V7ZOC/lKkM03TRHqaThQFIq6bWPnj7L/ZWPh0ymldYFFyh8XdF0ywTgafsofDNYT4StlNknbaTjVHBma3SNjQ==} + /@mui/core-downloads-tracker@5.14.10: + resolution: {integrity: sha512-kPHu/NhZq1k+vSZR5wq3AyUfD4bnfWAeuKpps0+8PS7ZHQ2Lyv1cXJh+PlFdCIOa0PK98rk3JPwMzS8BMhdHwQ==} dev: false - /@mui/material@5.14.8(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-fqvDGGF1pXwOOL/f0Gw+KHo/67hasRpf2ApTIJkbuONOk9AUb2jnYMEqCWmL2sUcbbE3ShMbHl8N7HPSsRv1/A==} + /@mui/material@5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-ejFMppnO+lzBXpzju+N4SSz0Mhmi5sihXUGcr5FxpgB6bfUP0Lpe32O0Sw/3s8xlmLEvG1fqVT0rRyAVMlCA+A==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -539,14 +544,14 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.22.15 '@emotion/react': 11.11.1(react@18.2.0) '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(react@18.2.0) - '@mui/base': 5.0.0-beta.14(react-dom@18.2.0)(react@18.2.0) - '@mui/core-downloads-tracker': 5.14.8 - '@mui/system': 5.14.8(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0) + '@mui/base': 5.0.0-beta.16(react-dom@18.2.0)(react@18.2.0) + '@mui/core-downloads-tracker': 5.14.10 + '@mui/system': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0) '@mui/types': 7.2.4 - '@mui/utils': 5.14.8(react@18.2.0) + '@mui/utils': 5.14.10(react@18.2.0) '@types/react-transition-group': 4.4.6 clsx: 2.0.0 csstype: 3.1.2 @@ -557,8 +562,8 @@ packages: react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) dev: false - /@mui/private-theming@5.14.8(react@18.2.0): - resolution: {integrity: sha512-iBzpcl3Mh92XaYpYPdgzzRxNGkjpoDz8rf8/q5m+EBPowFEHV+CCS9hC0Q2pOKLW3VFFikA7w/GHt7n++40JGQ==} + /@mui/private-theming@5.14.10(react@18.2.0): + resolution: {integrity: sha512-f67xOj3H06wWDT9xBg7hVL/HSKNF+HG1Kx0Pm23skkbEqD2Ef2Lif64e5nPdmWVv+7cISCYtSuE2aeuzrZe78w==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 @@ -567,14 +572,14 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.11 - '@mui/utils': 5.14.8(react@18.2.0) + '@babel/runtime': 7.22.15 + '@mui/utils': 5.14.10(react@18.2.0) prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/styled-engine@5.14.8(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0): - resolution: {integrity: sha512-LGwOav/Y40PZWZ2yDk4beUoRlc57Vg+Vpxi9V9BBtT2ESAucCgFobkt+T8eVLMWF9huUou5pwKgLSU5pF90hBg==} + /@mui/styled-engine@5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0): + resolution: {integrity: sha512-EJckxmQHrsBvDbFu1trJkvjNw/1R7jfNarnqPSnL+jEQawCkQIqVELWLrlOa611TFtxSJGkdUfCFXeJC203HVg==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.4.1 @@ -586,7 +591,7 @@ packages: '@emotion/styled': optional: true dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.22.15 '@emotion/cache': 11.11.0 '@emotion/react': 11.11.1(react@18.2.0) '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(react@18.2.0) @@ -595,8 +600,8 @@ packages: react: 18.2.0 dev: false - /@mui/system@5.14.8(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0): - resolution: {integrity: sha512-Dxnasv7Pj5hYe4ZZFKJZu4ufKm6cxpitWt3A+qMPps22YhqyeEqgDBq/HsAB3GOjqDP40fTAvQvS/Hguf4SJuw==} + /@mui/system@5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0): + resolution: {integrity: sha512-QQmtTG/R4gjmLiL5ECQ7kRxLKDm8aKKD7seGZfbINtRVJDyFhKChA1a+K2bfqIAaBo1EMDv+6FWNT1Q5cRKjFA==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -611,13 +616,13 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.22.15 '@emotion/react': 11.11.1(react@18.2.0) '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(react@18.2.0) - '@mui/private-theming': 5.14.8(react@18.2.0) - '@mui/styled-engine': 5.14.8(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0) + '@mui/private-theming': 5.14.10(react@18.2.0) + '@mui/styled-engine': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0) '@mui/types': 7.2.4 - '@mui/utils': 5.14.8(react@18.2.0) + '@mui/utils': 5.14.10(react@18.2.0) clsx: 2.0.0 csstype: 3.1.2 prop-types: 15.8.1 @@ -633,15 +638,18 @@ packages: optional: true dev: false - /@mui/utils@5.14.8(react@18.2.0): - resolution: {integrity: sha512-1Ls2FfyY2yVSz9NEqedh3J8JAbbZAnUWkOWLE2f4/Hc4T5UWHMfzBLLrCqExfqyfyU+uXYJPGeNIsky6f8Gh5Q==} + /@mui/utils@5.14.10(react@18.2.0): + resolution: {integrity: sha512-Rn+vYQX7FxkcW0riDX/clNUwKuOJFH45HiULxwmpgnzQoQr3A0lb+QYwaZ+FAkZrR7qLoHKmLQlcItu6LT0y/Q==} engines: {node: '>=12.0.0'} peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.22.15 '@types/prop-types': 15.7.5 - '@types/react-is': 18.2.1 prop-types: 15.8.1 react: 18.2.0 react-is: 18.2.0 @@ -740,12 +748,6 @@ packages: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} dev: false - /@types/react-is@18.2.1: - resolution: {integrity: sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==} - dependencies: - '@types/react': 18.2.18 - dev: false - /@types/react-transition-group@4.4.6: resolution: {integrity: sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==} dependencies: @@ -1122,7 +1124,7 @@ packages: /dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.22.15 csstype: 3.1.2 dev: false @@ -2127,7 +2129,7 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.22.15 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -2503,6 +2505,11 @@ packages: hasBin: true dev: false + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + dev: false + /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true @@ -2596,8 +2603,8 @@ packages: resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==} dev: false - github.com/theopensystemslab/planx-core/17f3bf5: - resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/17f3bf5} + github.com/theopensystemslab/planx-core/f2a22d3: + resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/f2a22d3} name: '@opensystemslab/planx-core' version: 1.0.0 prepare: true @@ -2605,7 +2612,7 @@ packages: dependencies: '@emotion/react': 11.11.1(react@18.2.0) '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(react@18.2.0) - '@mui/material': 5.14.8(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0) + '@mui/material': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0) '@types/geojson': 7946.0.10 ajv: 8.12.0 ajv-formats: 2.1.1(ajv@8.12.0) @@ -2630,7 +2637,7 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) type-fest: 4.3.1 - uuid: 9.0.0 + uuid: 9.0.1 zod: 3.22.2 transitivePeerDependencies: - '@types/react' diff --git a/e2e/tests/api-driven/src/client.ts b/e2e/tests/api-driven/src/client.ts index 78ac350f86..19513cadf6 100644 --- a/e2e/tests/api-driven/src/client.ts +++ b/e2e/tests/api-driven/src/client.ts @@ -1,6 +1,5 @@ import assert from "node:assert"; import { CoreDomainClient } from "@opensystemslab/planx-core"; -import { TEST_EMAIL } from "../../ui-driven/src/helpers"; import { buildJWT } from "./jwt"; // check env variables are defined @@ -18,10 +17,10 @@ export const $admin = new CoreDomainClient({ }); /** - * Get client authorised to the permissions level of the test user + * Get client authorised to the permissions level of the provided user */ -export const getClient = async () => { - const jwt = await buildJWT(TEST_EMAIL); +export const getClient = async (email: string) => { + const jwt = await buildJWT(email); if (!jwt) throw Error("Unable to generate JWT for test user"); const client = new CoreDomainClient({ diff --git a/e2e/tests/api-driven/src/helpers.ts b/e2e/tests/api-driven/src/helpers.ts index 9d73abd164..bcffab077f 100644 --- a/e2e/tests/api-driven/src/helpers.ts +++ b/e2e/tests/api-driven/src/helpers.ts @@ -1,23 +1,43 @@ import { TEST_EMAIL } from "../../ui-driven/src/helpers"; import { $admin } from "./client"; -export async function createTeam() { - return $admin.team.create({ +export function createTeam(args?: Partial[0]>) { + return safely(() => $admin.team.create({ name: "E2E Test Team", slug: "E2E", logo: "https://raw.githubusercontent.com/theopensystemslab/planx-team-logos/main/planx-testing.svg", primaryColor: "#444444", submissionEmail: TEST_EMAIL, homepage: "planx.uk", - }); + ...args, + })); } -export async function createUser() { - return $admin.user.create({ +export function createUser(args?: Partial[0]>) { + return safely(() => $admin.user.create({ firstName: "Test", lastName: "Test", email: TEST_EMAIL, - }); + ...args, + })); +} + +export function createFlow(args: Omit[0], "data">) { + return safely(() => $admin.flow.create({ + data: { dummy: "flowData "}, + ...args, + })) +} + +/** + * Error handling boilerplate for client functions + */ +export function safely ReturnType>(callback: T) { + const result = callback(); + if (!result) { + throw new Error("Error setting up E2E test"); + } + return result; } export async function tearDownTestContext({ @@ -56,6 +76,6 @@ export async function tearDownTestContext({ } } -export async function getTestUser() { - return await $admin.user.getByEmail(TEST_EMAIL); +export async function getUser(email: string) { + return await $admin.user.getByEmail(email); } diff --git a/e2e/tests/api-driven/src/permissions/helpers.ts b/e2e/tests/api-driven/src/permissions/helpers.ts new file mode 100644 index 0000000000..fda926a129 --- /dev/null +++ b/e2e/tests/api-driven/src/permissions/helpers.ts @@ -0,0 +1,61 @@ +import { $admin, getClient } from "../client"; +import { CustomWorld } from "./steps"; +import { queries } from "./queries"; +import { createFlow, createTeam, createUser } from "../helpers"; + +interface PerformGQLQueryArgs { + world: CustomWorld; + action: string; + table: string; +} + +export const addUserToTeam = async (userId: number, teamId: number) => { + await $admin.team.addMember({ + userId, + teamId, + role: "teamEditor", + }); +}; + +export const cleanupPermissionsTest = async () => { + await $admin.flow._destroyAll(); + await $admin.team._destroyAll(); + await $admin.user._destroyAll(); +} + +export const setupPermissionsTest = async () => { + const teamId1 = await createTeam({ name: "E2E Team 1", slug: "e2e-team1" }); + const teamId2 = await createTeam({ name: "E2E Team 2", slug: "e2e-team2" }); + const user1 = { + id: await createUser({ email: "e2e-user-1@opensystemslab.io" }), + email: "e2e-user-1@opensystemslab.io", + } + const user2 = { + id: await createUser({ email: "e2e-user-2@opensystemslab.io" }), + email: "e2e-user-2@opensystemslab.io", + } + const team1Flow = await createFlow({ teamId: teamId1, slug: "team-1-flow" }) + const team2Flow = await createFlow({ teamId: teamId2, slug: "team-2-flow" }) + + const world = { + teamId1, + teamId2, + user1, + user2, + team1Flow, + team2Flow, + }; + + return world; +} + +export const performGQLQuery = async ({ + world, + action, + table, +}: PerformGQLQueryArgs) => { + const query = queries[table][action]; + const client = (await getClient(world.activeUser.email)).client; + const result = await client.request(query, { teamId1: world.teamId1 }); + return result; +}; diff --git a/e2e/tests/api-driven/src/permissions/queries/flows.ts b/e2e/tests/api-driven/src/permissions/queries/flows.ts new file mode 100644 index 0000000000..48833249f1 --- /dev/null +++ b/e2e/tests/api-driven/src/permissions/queries/flows.ts @@ -0,0 +1,11 @@ +import { gql } from "graphql-request"; + +export const INSERT_FLOW_QUERY = gql` + mutation InsertFlowE2E ($teamId1: Int) { + insert_flows(objects: {data: "{hello: 'world'}", slug: "e2e-test-flow", team_id: $teamId1 }) { + returning { + id + } + } +} +` \ No newline at end of file diff --git a/e2e/tests/api-driven/src/permissions/queries/index.ts b/e2e/tests/api-driven/src/permissions/queries/index.ts new file mode 100644 index 0000000000..3a04074c62 --- /dev/null +++ b/e2e/tests/api-driven/src/permissions/queries/index.ts @@ -0,0 +1,7 @@ +import { INSERT_FLOW_QUERY } from "./flows"; + +export const queries = { + flows: { + insert: INSERT_FLOW_QUERY, + } +} \ No newline at end of file diff --git a/e2e/tests/api-driven/src/permissions/steps.ts b/e2e/tests/api-driven/src/permissions/steps.ts new file mode 100644 index 0000000000..c23c308108 --- /dev/null +++ b/e2e/tests/api-driven/src/permissions/steps.ts @@ -0,0 +1,101 @@ +import { After, Before, Given, Then, When, World } from "@cucumber/cucumber"; +import { strict as assert } from "node:assert"; +import { + getUser, +} from "../helpers"; +import { + addUserToTeam, + cleanupPermissionsTest, + performGQLQuery, + setupPermissionsTest, +} from "./helpers"; + +interface TestUser { + id: number, + email: string, +} + +export class CustomWorld extends World { + user1!: TestUser; + user2!: TestUser; + teamId1!: number; + teamId2!: number; + team1Flow!: string; + team2Flow!: string; + + error?: Error = undefined; + activeUser!: TestUser; +} + +Before("@team-admin-permissions", async function () { + const { user1, user2, teamId1, teamId2, team1Flow, team2Flow } = await setupPermissionsTest(); + this.user1 = user1 + this.user2 = user2 + this.teamId1 = teamId1 + this.teamId2 = teamId2 + this.team1Flow = team1Flow + this.team2Flow = team2Flow +}); + +After("@team-admin-permissions", async function () { + await cleanupPermissionsTest(); +}); + +Given("a teamAdmin is a member of a team", async function (this: CustomWorld) { + await addUserToTeam(this.user1.id, this.teamId1); + const user = await getUser(this.user1.email); + + assert.ok(user, "User is not defined"); + assert.strictEqual(user.teams.length, 1); + assert.strictEqual(user.teams[0].role, "teamEditor"); + assert.strictEqual(user.teams[0].teamId, this.teamId1); + + this.activeUser = this.user1; +}); + +Given("a teamAdmin is not in the requested team", async function (this: CustomWorld) { + await addUserToTeam(this.user2.id, this.teamId2); + const user = await getUser(this.user2.email); + + assert.ok(user, "User is not defined"); + assert.strictEqual(user.teams.length, 1); + assert.strictEqual(user.teams[0].role, "teamEditor"); + assert.strictEqual(user.teams[0].teamId, this.teamId2 ); + + this.activeUser = this.user2; +}); + +When( + "they perform {string} on {string}", + async function ( + this: CustomWorld, + action: string, + table: string, + ) { + try { + await performGQLQuery({ + world: this, + action, + table, + }); + } catch (error) { + if (error instanceof Error) { + this.error = error; + return; + } + throw error; + } + }, +); + +Then("they have access", function (this: CustomWorld) { + if (this.error) { + assert.fail(`Permission query failed with error: ${this.error.message}`); + } +}); + +Then("they do not have access", function (this: CustomWorld) { + if (this.error) { + assert.ok(`Permission query failed with error: ${this.error.message}`); + } +}); diff --git a/e2e/tests/api-driven/src/permissions/teamAdmin.feature b/e2e/tests/api-driven/src/permissions/teamAdmin.feature new file mode 100644 index 0000000000..bc0b49d396 --- /dev/null +++ b/e2e/tests/api-driven/src/permissions/teamAdmin.feature @@ -0,0 +1,21 @@ +Feature: Testing Permissions for teamAdmin Role + + @regression @team-admin-permissions + Scenario Outline: teamAdmin permissions + Given a teamAdmin is a member of a team + When they perform "" on "" + Then they have access + + Examples: + | TABLE | ACTION | + | flows | insert | + + @regression @team-admin-permissions + Scenario Outline: teamAdmin permissions in a different team + Given a teamAdmin is not in the requested team + When they perform "" on "
" + Then they do not have access + + Examples: + | TABLE | ACTION | + | flows | insert | \ No newline at end of file