Files
speckle-server/packages/server/modules/workspaces/tests/integration/roles.graph.spec.ts
T
Chuck Driesler fc26fe4c9e fix(workspaces): transact workspace role changes (#2752)
* fix(workspaces): transact all simultaneous workspace-project role changes

* fix(workspaces): more correct usage of transactions

* fix(workspaces): add tests for transaction-based role changes

* fix(workspaces): do not leak knex into domain

* fix(workspaces): transaction-ify factory functions

* fix(workspaces): factory-ify some old functions

* fix(workspaces): missed a resolver

* fix(workspaces): delete comment (very difficult)
2024-08-28 16:53:10 +02:00

410 lines
13 KiB
TypeScript

import { AllScopes } from '@/modules/core/helpers/mainConstants'
import { grantStreamPermissions } from '@/modules/core/repositories/streams'
import {
assignToWorkspace,
BasicTestWorkspace,
createTestWorkspace
} from '@/modules/workspaces/tests/helpers/creation'
import {
BasicTestUser,
createAuthTokenForUser,
createTestUsers
} from '@/test/authHelper'
import {
ActiveUserLeaveWorkspaceDocument,
GetWorkspaceDocument,
GetWorkspaceProjectsDocument,
GetWorkspaceTeamDocument,
UpdateWorkspaceRoleDocument
} from '@/test/graphql/generated/graphql'
import {
createTestContext,
testApolloServer,
TestApolloServer
} from '@/test/graphqlHelper'
import { beforeEachContext, truncateTables } from '@/test/hooks'
import { BasicTestStream, createTestStream } from '@/test/speckle-helpers/streamHelper'
import { Roles } from '@speckle/shared'
import { expect } from 'chai'
describe('Workspaces Roles GQL', () => {
let apollo: TestApolloServer
const workspace: BasicTestWorkspace = {
id: '',
ownerId: '',
name: 'My Test Workspace'
}
const testAdminUser: BasicTestUser = {
id: '',
name: 'John Speckle',
email: 'john-speckle-workspace-admin@example.org',
role: Roles.Server.Admin
}
const testMemberUser: BasicTestUser = {
id: '',
name: 'James Speckle',
email: 'james-speckle-workspace-member@example.org',
role: Roles.Server.User
}
before(async () => {
await beforeEachContext()
await createTestUsers([testAdminUser, testMemberUser])
const token = await createAuthTokenForUser(testAdminUser.id, AllScopes)
apollo = await testApolloServer({
context: createTestContext({
auth: true,
userId: testAdminUser.id,
token,
role: testAdminUser.role,
scopes: AllScopes
})
})
await createTestWorkspace(workspace, testAdminUser)
})
describe('update workspace role', () => {
after(async () => {
await apollo.execute(UpdateWorkspaceRoleDocument, {
input: {
userId: testMemberUser.id,
workspaceId: workspace.id,
role: null
}
})
})
it('should create a role if none exists', async () => {
const res = await apollo.execute(UpdateWorkspaceRoleDocument, {
input: {
userId: testMemberUser.id,
workspaceId: workspace.id,
role: Roles.Workspace.Admin
}
})
const { data } = await apollo.execute(GetWorkspaceDocument, {
workspaceId: workspace.id
})
const userRole = data?.workspace.team.find(
(user) => user.id === testMemberUser.id
)
expect(res).to.not.haveGraphQLErrors()
expect(userRole).to.exist
expect(userRole?.role).to.equal(Roles.Workspace.Admin)
})
it('should update a role that exists', async () => {
const res = await apollo.execute(UpdateWorkspaceRoleDocument, {
input: {
userId: testMemberUser.id,
workspaceId: workspace.id,
role: Roles.Workspace.Member
}
})
const roles = res.data?.workspaceMutations.updateRole.team
expect(res).to.not.haveGraphQLErrors()
expect(roles?.some((role) => role.id === testMemberUser.id)).to.be.true
})
it('should throw if setting an invalid role', async () => {
const res = await apollo.execute(UpdateWorkspaceRoleDocument, {
input: {
userId: testMemberUser.id,
workspaceId: workspace.id,
role: 'not-a-role'
}
})
expect(res).to.haveGraphQLErrors('Invalid workspace role')
})
it('should throw if attempting to remove last admin', async () => {
const res = await apollo.execute(UpdateWorkspaceRoleDocument, {
input: {
userId: testAdminUser.id,
workspaceId: workspace.id,
role: Roles.Workspace.Member
}
})
expect(res).to.haveGraphQLErrors('last admin')
})
})
describe('delete workspace role', () => {
before(async () => {
await apollo.execute(UpdateWorkspaceRoleDocument, {
input: {
userId: testMemberUser.id,
workspaceId: workspace.id,
role: Roles.Workspace.Member
}
})
})
it('should delete the specified role', async () => {
const res = await apollo.execute(UpdateWorkspaceRoleDocument, {
input: {
userId: testMemberUser.id,
workspaceId: workspace.id,
role: null
}
})
const roles = res.data?.workspaceMutations.updateRole.team
expect(res).to.not.haveGraphQLErrors()
expect(roles?.some((role) => role.id === testMemberUser.id)).to.be.false
})
it('should throw if attempting to remove last admin', async () => {
const res = await apollo.execute(UpdateWorkspaceRoleDocument, {
input: {
userId: testAdminUser.id,
workspaceId: workspace.id,
role: null
}
})
expect(res).to.haveGraphQLErrors('last admin')
})
})
describe('in a workspace with projects', () => {
let workspaceMemberApollo: TestApolloServer
const workspaceWithProjects: BasicTestWorkspace = {
id: '',
ownerId: '',
name: 'Test Workspace w/ Projects'
}
const workspaceProject: BasicTestStream = {
id: '',
ownerId: '',
name: 'Test Project',
isPublic: true
}
before(async () => {
const token = await createAuthTokenForUser(testMemberUser.id, AllScopes)
workspaceMemberApollo = await testApolloServer({
context: createTestContext({
auth: true,
userId: testMemberUser.id,
token,
role: testMemberUser.role,
scopes: AllScopes
})
})
})
beforeEach(async () => {
await createTestWorkspace(workspaceWithProjects, testAdminUser)
workspaceProject.workspaceId = workspaceWithProjects.id
await createTestStream(workspaceProject, testAdminUser)
})
afterEach(async () => {
await truncateTables(['workspaces', 'streams'])
})
describe('when leaving the workspace as the last owner of a workspace project', () => {
// User Workspace Role Project Role
// testAdminUser Admin Reviewer
// testMemberUser Admin Owner
//
// Action: `testMemberUser` leaves workspace
beforeEach(async () => {
await assignToWorkspace(
workspaceWithProjects,
testMemberUser,
Roles.Workspace.Admin
)
await grantStreamPermissions({
streamId: workspaceProject.id,
userId: testAdminUser.id,
role: Roles.Stream.Reviewer
})
})
it('should throw and preserve all roles', async () => {
const res = await workspaceMemberApollo.execute(
ActiveUserLeaveWorkspaceDocument,
{ id: workspaceWithProjects.id }
)
const { data: workspaceTeamData } = await apollo.execute(
GetWorkspaceTeamDocument,
{ workspaceId: workspaceWithProjects.id }
)
const { data: workspaceProjectsData } = await apollo.execute(
GetWorkspaceProjectsDocument,
{ id: workspaceWithProjects.id }
)
const teamRoles = workspaceTeamData?.workspace.team
const projectRoles = workspaceProjectsData?.workspace.projects.items[0].team
expect(res).to.haveGraphQLErrors('Could not revoke permissions for last admin')
expect(teamRoles).to.exist
expect(teamRoles?.some((role) => role.id === testMemberUser.id)).to.be.true
expect(projectRoles).to.exist
expect(projectRoles?.some((role) => role.id === testMemberUser.id)).to.be.true
})
})
describe('when removing a workspace member that is the last owner of a workspace project', () => {
// User Workspace Role Project Role
// testAdminUser Admin Reviewer
// testMemberUser Admin Owner
//
// Action: `testAdminUser` removes `testMemberUser` from the workspace
beforeEach(async () => {
await assignToWorkspace(
workspaceWithProjects,
testMemberUser,
Roles.Workspace.Admin
)
await grantStreamPermissions({
streamId: workspaceProject.id,
userId: testAdminUser.id,
role: Roles.Stream.Reviewer
})
})
it('should throw and preserve all roles', async () => {
const res = await apollo.execute(UpdateWorkspaceRoleDocument, {
input: {
userId: testMemberUser.id,
role: null,
workspaceId: workspaceWithProjects.id
}
})
const { data: workspaceTeamData } = await apollo.execute(
GetWorkspaceTeamDocument,
{ workspaceId: workspaceWithProjects.id }
)
const { data: workspaceProjectsData } = await apollo.execute(
GetWorkspaceProjectsDocument,
{ id: workspaceWithProjects.id }
)
const teamRoles = workspaceTeamData?.workspace.team
const projectRoles = workspaceProjectsData?.workspace.projects.items[0].team
expect(res).to.haveGraphQLErrors('Could not revoke permissions for last admin')
expect(teamRoles).to.exist
expect(teamRoles?.some((role) => role.id === testMemberUser.id)).to.be.true
expect(projectRoles).to.exist
expect(projectRoles?.some((role) => role.id === testMemberUser.id)).to.be.true
})
})
describe('when leaving a workspace without any project owner roles', () => {
// User Workspace Role Project Role
// testAdminUser Admin Owner
// testMemberUser Member Reviewer
//
// Action: `testMemberUser` leaves workspace
beforeEach(async () => {
await assignToWorkspace(
workspaceWithProjects,
testMemberUser,
Roles.Workspace.Member
)
await grantStreamPermissions({
streamId: workspaceProject.id,
userId: testMemberUser.id,
role: Roles.Stream.Reviewer
})
})
it('should remove all workspace and project roles for user', async () => {
const res = await workspaceMemberApollo.execute(
ActiveUserLeaveWorkspaceDocument,
{ id: workspaceWithProjects.id }
)
const { data: workspaceTeamData } = await apollo.execute(
GetWorkspaceTeamDocument,
{ workspaceId: workspaceWithProjects.id }
)
const { data: workspaceProjectsData } = await apollo.execute(
GetWorkspaceProjectsDocument,
{ id: workspaceWithProjects.id }
)
const teamRoles = workspaceTeamData?.workspace.team
const projectRoles = workspaceProjectsData?.workspace.projects.items[0].team
expect(res).to.not.haveGraphQLErrors()
expect(teamRoles).to.exist
expect(teamRoles?.some((role) => role.id === testMemberUser.id)).to.be.false
expect(projectRoles).to.exist
expect(projectRoles?.some((role) => role.id === testMemberUser.id)).to.be.false
})
})
describe('when removing a workspace member that has no workspace project owner roles', () => {
// User Workspace Role Project Role
// testAdminUser Admin Owner
// testMemberUser Member Reviewer
//
// Action: `testAdminUser` removes `testMemberUser` from the workspace
beforeEach(async () => {
await assignToWorkspace(
workspaceWithProjects,
testMemberUser,
Roles.Workspace.Member
)
await grantStreamPermissions({
streamId: workspaceProject.id,
userId: testMemberUser.id,
role: Roles.Stream.Reviewer
})
})
it('should remove all workspace and project roles for removed member', async () => {
const res = await apollo.execute(UpdateWorkspaceRoleDocument, {
input: {
userId: testMemberUser.id,
role: null,
workspaceId: workspaceWithProjects.id
}
})
const { data: workspaceTeamData } = await apollo.execute(
GetWorkspaceTeamDocument,
{ workspaceId: workspaceWithProjects.id }
)
const { data: workspaceProjectsData } = await apollo.execute(
GetWorkspaceProjectsDocument,
{ id: workspaceWithProjects.id }
)
const teamRoles = workspaceTeamData?.workspace.team
const projectRoles = workspaceProjectsData?.workspace.projects.items[0].team
expect(res).to.not.haveGraphQLErrors()
expect(teamRoles).to.exist
expect(teamRoles?.some((role) => role.id === testMemberUser.id)).to.be.false
expect(projectRoles).to.exist
expect(projectRoles?.some((role) => role.id === testMemberUser.id)).to.be.false
})
})
})
})