diff --git a/README.md b/README.md index b4f9f429..c89932bf 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ A couple of environment variables are required: - user_owner - user with owner role - user_orguser - Organization user - user_orgviewer - Organization viewer +- user_orgadmin - Organization admin - user_orgowner - Organization owner - user_platformowner - Platform owner diff --git a/cypress/cypress.config.ts b/cypress/cypress.config.ts index 9954393f..a1a9e411 100644 --- a/cypress/cypress.config.ts +++ b/cypress/cypress.config.ts @@ -15,6 +15,7 @@ export default defineConfig({ // orgs user_orguser: 'orguser@example.com', user_orgviewer: 'orgviewer@example.com', + user_orgadmin: 'orgadmin@example.com', user_orgowner: 'orgowner@example.com', // top level user for all default tests user_platformowner: 'platformowner@example.com', diff --git a/cypress/e2e/rbac/organizations/orgAdmin.cy.ts b/cypress/e2e/rbac/organizations/orgAdmin.cy.ts new file mode 100644 index 00000000..78533124 --- /dev/null +++ b/cypress/e2e/rbac/organizations/orgAdmin.cy.ts @@ -0,0 +1,170 @@ +import { testData } from 'cypress/fixtures/variables'; +import GroupAction from 'cypress/support/actions/organizations/GroupsAction'; +import NotificationsAction from 'cypress/support/actions/organizations/NotificationsAction'; +import OverviewAction from 'cypress/support/actions/organizations/OverviewAction'; +import ProjectsActions from 'cypress/support/actions/organizations/ProjectsActions'; +import { aliasMutation, aliasQuery, registerIdleHandler } from 'cypress/utils/aliasQuery'; + +const overview = new OverviewAction(); +const group = new GroupAction(); +const project = new ProjectsActions(); +const notifications = new NotificationsAction(); + +const orgAdmin = [Cypress.env('user_orgadmin')]; + +orgAdmin.forEach(admin => { + const desc = { + [Cypress.env('user_orgadmin')]: 'Org admin', + }; + + describe(`Organizations ${desc[admin]} journey`, () => { + beforeEach(() => { + // register interceptors/idle handler + cy.intercept('POST', Cypress.env('api'), req => { + aliasQuery(req, 'getOrganization'); + aliasMutation(req, 'updateOrganizationFriendlyName'); + aliasMutation(req, 'addUserToGroup'); + aliasMutation(req, 'addGroupToOrganization'); + }); + + registerIdleHandler('idle'); + + cy.login(admin, admin); + cy.visit(`${Cypress.env('url')}/organizations/lagoon-demo-organization`); + }); + + if (admin === Cypress.env('user_orgadmin')) { + it('Fails to change org name and desc - no permission for ORGADMIN', () => { + overview.doFailedChangeOrgFriendlyname(testData.organizations.overview.friendlyName); + overview.closeModal(); + overview.doFailedChangeOrgDescription(testData.organizations.overview.description); + overview.closeModal(); + }); + } else { + it('Changes org name and desc', () => { + overview.changeOrgFriendlyname(testData.organizations.overview.friendlyName); + overview.changeOrgDescription(testData.organizations.overview.description); + }); + } + + it('Navigates to groups and creates', () => { + cy.waitForNetworkIdle('@idle', 500); + + const group1 = testData.organizations.groups.newGroupName; + const group2 = testData.organizations.groups.newGroupName2; + + cy.get('.groups').click(); + cy.location('pathname').should('equal', '/organizations/lagoon-demo-organization/groups'); + + group.doAddGroup(group1, group2); + registerIdleHandler('groupQuery'); + group.doAddMemberToGroup(testData.organizations.users.email, group1); + }); + + it('Navigates to projects and creates a new one', () => { + registerIdleHandler('projectsQuery'); + cy.intercept('POST', Cypress.env('api'), req => { + aliasMutation(req, 'addProjectToOrganization'); + }); + + cy.waitForNetworkIdle('@idle', 500); + + cy.get('.projects').click(); + cy.location('pathname').should('equal', '/organizations/lagoon-demo-organization/projects'); + cy.waitForNetworkIdle('@projectsQuery', 1000); + + project.doAddProject(testData.organizations.project); + }); + + it('Navigates to notifications and creates a couple', () => { + cy.intercept('POST', Cypress.env('api'), req => { + aliasMutation(req, 'addNotificationSlack'); + aliasMutation(req, 'UpdateNotificationSlack'); + aliasMutation(req, 'addNotificationRocketChat'); + aliasMutation(req, 'addNotificationMicrosoftTeams'); + aliasMutation(req, 'addNotificationEmail'); + aliasMutation(req, 'addNotificationWebhook'); + }); + + registerIdleHandler('notificationsQuery'); + + cy.waitForNetworkIdle('@idle', 500); + cy.get('.notifications').click(); + cy.location('pathname').should('equal', '/organizations/lagoon-demo-organization/notifications'); + cy.waitForNetworkIdle('@notificationsQuery', 1000); + + const { slack: slackData, email: emailData, webhook: webhookData } = testData.organizations.notifications; + + notifications.doAddNotification('slack', slackData); + notifications.doAddNotification('email', emailData); + notifications.doAddNotification('webhook', webhookData); + }); + + it('Navigates to a project, adds a group and notifications', () => { + cy.visit( + `${Cypress.env('url')}/organizations/lagoon-demo-organization/projects/${ + testData.organizations.project.projectName + }` + ); + + cy.getBySel('addGroupToProject').click(); + + cy.get('.react-select__indicator').click({ force: true }); + cy.get('#react-select-2-option-0').click(); + + cy.getBySel('addGroupToProjectConfirm').click(); + + cy.log('add notifications'); + + cy.getBySel('addNotificationToProject').click(); + + cy.get('[class$=control]').click({ force: true }); + cy.get('#react-select-3-option-0').click(); + + cy.getBySel('addNotificationToProjectConfirm').click(); + }); + + // cleanup + after(() => { + registerIdleHandler('projectsQuery'); + registerIdleHandler('groupQuery'); + cy.intercept('POST', Cypress.env('api'), req => { + aliasMutation(req, 'removeNotification'); + aliasMutation(req, 'deleteGroup'); + aliasMutation(req, 'deleteProject'); + }); + + cy.waitForNetworkIdle('@idle', 500); + cy.get('.groups').click(); + + group.doDeleteGroup(testData.organizations.groups.newGroupName); + cy.wait('@gqldeleteGroupMutation'); + + group.doDeleteGroup(testData.organizations.groups.newGroupName2); + cy.wait('@gqldeleteGroupMutation'); + + cy.waitForNetworkIdle('@idle', 500); + cy.get('.projects').click(); + + cy.waitForNetworkIdle('@projectsQuery', 1000); + + project.doDeleteProject(testData.organizations.project.projectName); + + cy.get('.notifications').click(); + + cy.waitForNetworkIdle('@idle', 500); + + const { + webhook: { name: webhooknName }, + email: { name: emailName }, + slack: { name: slackName }, + } = testData.organizations.notifications; + + notifications.doDeleteNotification(webhooknName); + cy.wait('@gqlremoveNotificationMutation'); // wait for a delete mutation instead + notifications.doDeleteNotification(emailName); + cy.wait('@gqlremoveNotificationMutation'); + notifications.doDeleteNotification(slackName); + }); + }); +}); diff --git a/src/components/Organizations/AddUserToOrganization/index.js b/src/components/Organizations/AddUserToOrganization/index.js index 895415f8..06b2c859 100644 --- a/src/components/Organizations/AddUserToOrganization/index.js +++ b/src/components/Organizations/AddUserToOrganization/index.js @@ -11,8 +11,8 @@ import { Footer } from '../SharedStyles'; import { NewUser } from './Styles'; export const ADD_USER_MUTATION = gql` - mutation AddUserToOrganization($email: String!, $organization: Int!, $owner: Boolean) { - addUserToOrganization(input: { user: { email: $email }, organization: $organization, owner: $owner }) { + mutation AddUserToOrganization($email: String!, $organization: Int!, $owner: Boolean, $admin: Boolean) { + addUserToOrganization(input: { user: { email: $email }, organization: $organization, owner: $owner, admin: $admin }) { id } } @@ -28,6 +28,8 @@ export const AddUserToOrganization = ({ setInputValue, checkboxValueOwner, setCheckboxValueOwner, + checkboxValueAdmin, + setCheckboxValueAdmin, onAddUser, users, }) => { @@ -70,6 +72,16 @@ export const AddUserToOrganization = ({ onChange={setCheckboxValueOwner} /> +