4b06f42db7
* sort of works * type fixes * added option to run old way too
1233 lines
42 KiB
TypeScript
1233 lines
42 KiB
TypeScript
import { Streams } from '@/modules/core/dbSchema'
|
|
import { AllScopes } from '@/modules/core/helpers/mainConstants'
|
|
import { ProjectRecordVisibility } from '@/modules/core/helpers/types'
|
|
import type { BasicTestWorkspace } from '@/modules/workspaces/tests/helpers/creation'
|
|
import {
|
|
assignToWorkspace,
|
|
createTestWorkspace,
|
|
unassignFromWorkspace
|
|
} from '@/modules/workspaces/tests/helpers/creation'
|
|
import type { ProjectImplicitRoleCheck } from '@/modules/workspaces/tests/helpers/rolesGraphql'
|
|
import { projectImplicitRoleCheck } from '@/modules/workspaces/tests/helpers/rolesGraphql'
|
|
import { WorkspaceSeatType } from '@/modules/workspacesCore/domain/types'
|
|
import {
|
|
WorkspaceAcl,
|
|
Workspaces,
|
|
WorkspaceSeats
|
|
} from '@/modules/workspacesCore/helpers/db'
|
|
import type { BasicTestUser } from '@/test/authHelper'
|
|
import { createAuthTokenForUser, createTestUsers } from '@/test/authHelper'
|
|
import {
|
|
ActiveUserLeaveWorkspaceDocument,
|
|
GetUserProjectsWithAccessChecksDocument,
|
|
GetUserWorkspaceAccessDocument,
|
|
GetUserWorkspaceProjectsWithAccessChecksDocument,
|
|
GetWorkspaceDocument,
|
|
UpdateWorkspaceRoleDocument,
|
|
UpdateWorkspaceSeatTypeDocument
|
|
} from '@/modules/core/graph/generated/graphql'
|
|
import type { TestApolloServer } from '@/test/graphqlHelper'
|
|
import { createTestContext, testApolloServer } from '@/test/graphqlHelper'
|
|
import { beforeEachContext, truncateTables } from '@/test/hooks'
|
|
import type { BasicTestStream } from '@/test/speckle-helpers/streamHelper'
|
|
import { addToStream, createTestStream } from '@/test/speckle-helpers/streamHelper'
|
|
import { Roles } from '@speckle/shared'
|
|
import { expect } from 'chai'
|
|
import cryptoRandomString from 'crypto-random-string'
|
|
|
|
describe('Workspaces Roles/Seats GQL', () => {
|
|
let apollo: TestApolloServer
|
|
|
|
const serverAdminUser: BasicTestUser = {
|
|
id: '',
|
|
name: 'John Speckle',
|
|
email: 'john-speckle-workspace-admin@example.org',
|
|
role: Roles.Server.Admin
|
|
}
|
|
|
|
const serverMemberUser: BasicTestUser = {
|
|
id: '',
|
|
name: 'James Speckle',
|
|
email: 'james-speckle-workspace-member@example.org',
|
|
role: Roles.Server.User
|
|
}
|
|
|
|
before(async () => {
|
|
await beforeEachContext()
|
|
await createTestUsers([serverAdminUser, serverMemberUser])
|
|
const token = await createAuthTokenForUser(serverAdminUser.id, AllScopes)
|
|
apollo = await testApolloServer({
|
|
context: await createTestContext({
|
|
auth: true,
|
|
userId: serverAdminUser.id,
|
|
token,
|
|
role: serverAdminUser.role,
|
|
scopes: AllScopes
|
|
})
|
|
})
|
|
})
|
|
|
|
const getWorkspaceProjects = async (params: {
|
|
user: BasicTestUser
|
|
workspace: BasicTestWorkspace
|
|
}) => {
|
|
const res = await apollo.execute(
|
|
GetUserWorkspaceProjectsWithAccessChecksDocument,
|
|
{
|
|
id: params.workspace.id
|
|
},
|
|
{ authUserId: params.user.id, assertNoErrors: true }
|
|
)
|
|
|
|
const projects = res.data?.workspace.projects.items || []
|
|
expect(res.data?.workspace, 'Could not retrieve workspace for user').to.be.ok
|
|
|
|
return {
|
|
projects,
|
|
workspace: res.data!.workspace,
|
|
checkProject: (project: BasicTestStream) => {
|
|
return projectImplicitRoleCheck(projects.find((p) => p.id === project.id))
|
|
},
|
|
checkAllProjects: (check: (project: ProjectImplicitRoleCheck) => boolean) => {
|
|
return projects.map(projectImplicitRoleCheck).every(check)
|
|
}
|
|
}
|
|
}
|
|
|
|
const getUserProjects = async (params: { user: BasicTestUser }) => {
|
|
const res = await apollo.execute(
|
|
GetUserProjectsWithAccessChecksDocument,
|
|
{
|
|
filter: {
|
|
includeImplicitAccess: true
|
|
}
|
|
},
|
|
{ authUserId: params.user.id, assertNoErrors: true }
|
|
)
|
|
|
|
const projects = res.data?.activeUser?.projects.items || []
|
|
|
|
return {
|
|
projects,
|
|
checkProject: (project: BasicTestStream) => {
|
|
return projectImplicitRoleCheck(projects.find((p) => p.id === project.id))
|
|
},
|
|
checkAllProjects: (check: (project: ProjectImplicitRoleCheck) => boolean) => {
|
|
return projects.map(projectImplicitRoleCheck).every(check)
|
|
}
|
|
}
|
|
}
|
|
|
|
const getUserWorkspace = async (params: {
|
|
user: BasicTestUser
|
|
workspace: BasicTestWorkspace
|
|
}) => {
|
|
const res = await apollo.execute(
|
|
GetUserWorkspaceAccessDocument,
|
|
{
|
|
id: params.workspace.id
|
|
},
|
|
{ authUserId: params.user.id }
|
|
)
|
|
const workspace = res.data?.workspace
|
|
|
|
return {
|
|
workspace
|
|
}
|
|
}
|
|
|
|
describe('single role changes in a workspace without projects', () => {
|
|
const workspace: BasicTestWorkspace = {
|
|
id: '',
|
|
ownerId: '',
|
|
slug: cryptoRandomString({ length: 10 }),
|
|
name: 'My Test Workspace'
|
|
}
|
|
|
|
before(async () => {
|
|
await createTestWorkspace(workspace, serverAdminUser, {
|
|
addPlan: {
|
|
name: 'team',
|
|
status: 'valid'
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('update workspace role', () => {
|
|
after(async () => {
|
|
await apollo.execute(
|
|
UpdateWorkspaceRoleDocument,
|
|
{
|
|
input: {
|
|
userId: serverMemberUser.id,
|
|
workspaceId: workspace.id,
|
|
role: null
|
|
}
|
|
},
|
|
{ assertNoErrors: true }
|
|
)
|
|
})
|
|
|
|
it('should create a role if none exists', async () => {
|
|
const res = await apollo.execute(UpdateWorkspaceRoleDocument, {
|
|
input: {
|
|
userId: serverMemberUser.id,
|
|
workspaceId: workspace.id,
|
|
role: Roles.Workspace.Admin
|
|
}
|
|
})
|
|
|
|
const { data } = await apollo.execute(GetWorkspaceDocument, {
|
|
workspaceId: workspace.id
|
|
})
|
|
const userRole = data?.workspace.team.items.find(
|
|
(user) => user.id === serverMemberUser.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: serverMemberUser.id,
|
|
workspaceId: workspace.id,
|
|
role: Roles.Workspace.Member
|
|
}
|
|
})
|
|
|
|
const roles = res.data?.workspaceMutations.updateRole.team.items
|
|
|
|
expect(res).to.not.haveGraphQLErrors()
|
|
expect(roles?.some((role) => role.id === serverMemberUser.id)).to.be.true
|
|
})
|
|
|
|
it('should throw if setting an invalid role', async () => {
|
|
const res = await apollo.execute(UpdateWorkspaceRoleDocument, {
|
|
input: {
|
|
userId: serverMemberUser.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: serverAdminUser.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: serverMemberUser.id,
|
|
workspaceId: workspace.id,
|
|
role: Roles.Workspace.Member
|
|
}
|
|
},
|
|
{ assertNoErrors: true }
|
|
)
|
|
})
|
|
|
|
it('should delete the specified role', async () => {
|
|
const res = await apollo.execute(UpdateWorkspaceRoleDocument, {
|
|
input: {
|
|
userId: serverMemberUser.id,
|
|
workspaceId: workspace.id,
|
|
role: null
|
|
}
|
|
})
|
|
|
|
const roles = res.data?.workspaceMutations.updateRole.team.items
|
|
|
|
expect(res).to.not.haveGraphQLErrors()
|
|
expect(roles?.some((role) => role.id === serverMemberUser.id)).to.be.false
|
|
})
|
|
|
|
it('should throw if attempting to remove last admin', async () => {
|
|
const res = await apollo.execute(UpdateWorkspaceRoleDocument, {
|
|
input: {
|
|
userId: serverAdminUser.id,
|
|
workspaceId: workspace.id,
|
|
role: null
|
|
}
|
|
})
|
|
|
|
expect(res).to.haveGraphQLErrors('last admin')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('in a workspace with projects', () => {
|
|
const workspace: BasicTestWorkspace = {
|
|
id: '',
|
|
ownerId: '',
|
|
slug: cryptoRandomString({ length: 10 }),
|
|
name: 'Test Workspace'
|
|
}
|
|
|
|
const workspaceAdminUser: BasicTestUser = {
|
|
id: '',
|
|
name: 'John "Owner" Specke',
|
|
email: 'john-owner-speckle@example.org'
|
|
}
|
|
|
|
const workspaceMemberUser: BasicTestUser = {
|
|
id: '',
|
|
name: 'John "Member" Speckel',
|
|
email: 'john-member-speckle@example.org'
|
|
}
|
|
|
|
const workspaceGuestUser: BasicTestUser = {
|
|
id: '',
|
|
name: 'John "Middle Child" Speckle',
|
|
email: 'john-guest-speckle@example.org'
|
|
}
|
|
|
|
const workspaceMemberViewerUser: BasicTestUser = {
|
|
id: '',
|
|
name: 'John "Member" Viewer Speckel',
|
|
email: 'john-member-speckle-viewer@example.org'
|
|
}
|
|
|
|
const workspaceGuestViewerUser: BasicTestUser = {
|
|
id: '',
|
|
name: 'John "Middle Child" Viewer Speckle',
|
|
email: 'john-guest-speckle-viewer@example.org'
|
|
}
|
|
|
|
const workspaceProjectA: BasicTestStream = {
|
|
id: '',
|
|
ownerId: '',
|
|
name: 'Project A',
|
|
visibility: ProjectRecordVisibility.Workspace
|
|
}
|
|
|
|
const workspaceProjectB: BasicTestStream = {
|
|
id: '',
|
|
ownerId: '',
|
|
name: 'Project B',
|
|
visibility: ProjectRecordVisibility.Workspace
|
|
}
|
|
|
|
const workspaceProjectC: BasicTestStream = {
|
|
id: '',
|
|
ownerId: '',
|
|
name: 'Project C',
|
|
visibility: ProjectRecordVisibility.Workspace
|
|
}
|
|
|
|
const workspaceProjectD: BasicTestStream = {
|
|
id: '',
|
|
ownerId: '',
|
|
name: 'Project D',
|
|
visibility: ProjectRecordVisibility.Workspace
|
|
}
|
|
|
|
const workspaceProjectE: BasicTestStream = {
|
|
id: '',
|
|
ownerId: '',
|
|
name: 'Project E (Fully private)',
|
|
visibility: ProjectRecordVisibility.Private
|
|
}
|
|
|
|
const workspaceProjects = [
|
|
workspaceProjectA,
|
|
workspaceProjectB,
|
|
workspaceProjectC,
|
|
workspaceProjectD,
|
|
workspaceProjectE
|
|
]
|
|
|
|
before(async () => {
|
|
await createTestUsers([
|
|
workspaceAdminUser,
|
|
workspaceMemberUser,
|
|
workspaceGuestUser,
|
|
workspaceMemberViewerUser,
|
|
workspaceGuestViewerUser
|
|
])
|
|
})
|
|
|
|
beforeEach(async () => {
|
|
await createTestWorkspace(workspace, serverAdminUser, {
|
|
addPlan: {
|
|
name: 'team',
|
|
status: 'valid'
|
|
}
|
|
})
|
|
|
|
await Promise.all([
|
|
assignToWorkspace(
|
|
workspace,
|
|
workspaceAdminUser,
|
|
Roles.Workspace.Admin,
|
|
WorkspaceSeatType.Editor
|
|
),
|
|
assignToWorkspace(
|
|
workspace,
|
|
workspaceMemberUser,
|
|
Roles.Workspace.Member,
|
|
WorkspaceSeatType.Editor
|
|
),
|
|
assignToWorkspace(
|
|
workspace,
|
|
workspaceGuestUser,
|
|
Roles.Workspace.Guest,
|
|
WorkspaceSeatType.Editor
|
|
),
|
|
assignToWorkspace(
|
|
workspace,
|
|
workspaceMemberViewerUser,
|
|
Roles.Workspace.Member,
|
|
WorkspaceSeatType.Viewer
|
|
),
|
|
assignToWorkspace(
|
|
workspace,
|
|
workspaceGuestViewerUser,
|
|
Roles.Workspace.Guest,
|
|
WorkspaceSeatType.Viewer
|
|
)
|
|
])
|
|
|
|
for (const project of workspaceProjects) {
|
|
project.workspaceId = workspace.id
|
|
await createTestStream(project, serverAdminUser)
|
|
}
|
|
|
|
/**
|
|
* Initial workspace roles:
|
|
*
|
|
* workspaceAdminUser Admin (Editor)
|
|
* workspaceMemberUser Member (Editor)
|
|
* workspaceGuestUser Guest (Editor)
|
|
* workspaceMemberViewerUser Member (Viewer)
|
|
* workspaceGuestViewerUser Guest (Viewer)
|
|
*
|
|
* Initial explicit workspace project roles:
|
|
*
|
|
* | | Project A | Project B | Project C | Project D | Project E (private) |
|
|
* |---------------------------|-------------|-------------|-----------|-----------|---------------------|
|
|
* | workspaceAdminUser | Owner | None | None | None | None
|
|
* | workspaceMemberUser | Owner | Contributor | Reviewer | None | None
|
|
* | workspaceGuestUser | Contributor | Reviewer | None | None | Reviewer
|
|
* | workspaceMemberViewerUser | Reviewer | None | None | None | Reviewer
|
|
* | workspaceGuestViewerUser | None | Reviewer | None | None | Reviewer
|
|
*/
|
|
|
|
await Promise.all([
|
|
// A
|
|
addToStream(workspaceProjectA, workspaceAdminUser, Roles.Stream.Owner),
|
|
addToStream(workspaceProjectA, workspaceMemberUser, Roles.Stream.Owner),
|
|
addToStream(workspaceProjectA, workspaceGuestUser, Roles.Stream.Contributor),
|
|
addToStream(
|
|
workspaceProjectA,
|
|
workspaceMemberViewerUser,
|
|
Roles.Stream.Reviewer
|
|
),
|
|
// B
|
|
addToStream(workspaceProjectB, workspaceMemberUser, Roles.Stream.Contributor),
|
|
addToStream(workspaceProjectB, workspaceGuestUser, Roles.Stream.Reviewer),
|
|
addToStream(workspaceProjectB, workspaceGuestViewerUser, Roles.Stream.Reviewer),
|
|
// C
|
|
addToStream(workspaceProjectC, workspaceMemberUser, Roles.Stream.Reviewer),
|
|
// E
|
|
addToStream(workspaceProjectE, workspaceGuestUser, Roles.Stream.Reviewer),
|
|
addToStream(
|
|
workspaceProjectE,
|
|
workspaceMemberViewerUser,
|
|
Roles.Stream.Reviewer
|
|
),
|
|
addToStream(workspaceProjectE, workspaceGuestViewerUser, Roles.Stream.Reviewer)
|
|
])
|
|
})
|
|
|
|
afterEach(async () => {
|
|
await truncateTables([
|
|
Workspaces.name,
|
|
Streams.name,
|
|
WorkspaceAcl.name,
|
|
WorkspaceSeats.name
|
|
])
|
|
})
|
|
|
|
const getProjects = async (params: { user: BasicTestUser }) =>
|
|
getWorkspaceProjects({ user: params.user, workspace })
|
|
|
|
describe('retrieving projects', () => {
|
|
it('workspaceAdminUser is implicit owner of all of them and explicit owner in one', async () => {
|
|
const { projects, checkProject, checkAllProjects } = await getProjects({
|
|
user: workspaceAdminUser
|
|
})
|
|
|
|
expect(projects.length).to.eq(5)
|
|
expect(checkAllProjects((p) => p.isOwner)).to.be.ok
|
|
expect(checkProject(workspaceProjectA).isExplicitOwner).to.be.ok
|
|
expect(checkProject(workspaceProjectB).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectC).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectD).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectE).hasExplicitRole).to.be.not.ok
|
|
})
|
|
|
|
it('workspaceMemberUser is implicit reviewer in all of them, except E, and also has explicit roles in some', async () => {
|
|
const { projects, checkAllProjects, checkProject } = await getProjects({
|
|
user: workspaceMemberUser
|
|
})
|
|
|
|
expect(projects.length).to.eq(4)
|
|
expect(checkAllProjects((p) => p.isReviewer)).to.be.ok
|
|
expect(checkProject(workspaceProjectA).isExplicitOwner).to.be.ok
|
|
expect(checkProject(workspaceProjectB).isExplicitContributor).to.be.ok
|
|
expect(checkProject(workspaceProjectC).isExplicitReviewer).to.be.ok
|
|
expect(checkProject(workspaceProjectD).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectE).hasAccess).to.be.not.ok
|
|
})
|
|
|
|
it('workspaceGuestUser only has explicit roles in 3 projects', async () => {
|
|
const { projects, checkProject } = await getProjects({
|
|
user: workspaceGuestUser
|
|
})
|
|
|
|
expect(projects.length).to.eq(3)
|
|
expect(checkProject(workspaceProjectA).isExplicitContributor).to.be.ok
|
|
expect(checkProject(workspaceProjectB).isExplicitReviewer).to.be.ok
|
|
expect(checkProject(workspaceProjectC).hasAccess).to.be.not.ok
|
|
expect(checkProject(workspaceProjectD).hasAccess).to.be.not.ok
|
|
expect(checkProject(workspaceProjectE).isExplicitReviewer).to.be.ok
|
|
})
|
|
|
|
it('workspaceMemberViewerUser is only explicit reviewer in 2 projects, and has implicit roles elsewhere', async () => {
|
|
const { projects, checkAllProjects, checkProject } = await getProjects({
|
|
user: workspaceMemberViewerUser
|
|
})
|
|
expect(projects.length).to.eq(5)
|
|
expect(checkAllProjects((p) => p.isReviewer)).to.be.ok
|
|
expect(checkProject(workspaceProjectA).isExplicitReviewer).to.be.ok
|
|
expect(checkProject(workspaceProjectB).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectC).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectD).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectE).isExplicitReviewer).to.be.ok
|
|
})
|
|
|
|
it('workspaceGuestViewerUser is only explicit reviewer in 2 projects', async () => {
|
|
const { projects, checkProject } = await getProjects({
|
|
user: workspaceGuestViewerUser
|
|
})
|
|
|
|
expect(projects.length).to.eq(2)
|
|
expect(checkProject(workspaceProjectB).isExplicitReviewer).to.be.ok
|
|
expect(checkProject(workspaceProjectA).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectC).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectD).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectE).isExplicitReviewer).to.be.ok
|
|
})
|
|
})
|
|
|
|
describe('doing single seat type changes', () => {
|
|
it('cant change workspace admin to viewer', async () => {
|
|
const res = await apollo.execute(UpdateWorkspaceSeatTypeDocument, {
|
|
input: {
|
|
userId: workspaceAdminUser.id,
|
|
workspaceId: workspace.id,
|
|
seatType: WorkspaceSeatType.Viewer
|
|
}
|
|
})
|
|
|
|
expect(res).to.haveGraphQLErrors('cannot have a seat of type')
|
|
})
|
|
|
|
it('changing member editor to viewer, should downgrade all explicit roles to reviewer', async () => {
|
|
await apollo.execute(
|
|
UpdateWorkspaceSeatTypeDocument,
|
|
{
|
|
input: {
|
|
userId: workspaceMemberUser.id,
|
|
workspaceId: workspace.id,
|
|
seatType: WorkspaceSeatType.Viewer
|
|
}
|
|
},
|
|
{ assertNoErrors: true }
|
|
)
|
|
|
|
const { projects, checkProject } = await getProjects({
|
|
user: workspaceMemberUser
|
|
})
|
|
|
|
expect(projects.length).to.eq(4)
|
|
expect(checkProject(workspaceProjectA).isExplicitReviewer).to.be.ok
|
|
expect(checkProject(workspaceProjectB).isExplicitReviewer).to.be.ok
|
|
expect(checkProject(workspaceProjectC).isExplicitReviewer).to.be.ok
|
|
expect(checkProject(workspaceProjectD).hasExplicitRole).to.be.not.ok
|
|
})
|
|
|
|
it('changing guest editor to viewer, should downgrade all explicit roles to reviewer', async () => {
|
|
await apollo.execute(
|
|
UpdateWorkspaceSeatTypeDocument,
|
|
{
|
|
input: {
|
|
userId: workspaceGuestUser.id,
|
|
workspaceId: workspace.id,
|
|
seatType: WorkspaceSeatType.Viewer
|
|
}
|
|
},
|
|
{ assertNoErrors: true }
|
|
)
|
|
|
|
const { projects, checkProject } = await getProjects({
|
|
user: workspaceGuestUser
|
|
})
|
|
|
|
expect(projects.length).to.eq(3)
|
|
expect(checkProject(workspaceProjectA).isExplicitReviewer).to.be.ok
|
|
expect(checkProject(workspaceProjectB).isExplicitReviewer).to.be.ok
|
|
expect(checkProject(workspaceProjectE).isExplicitReviewer).to.be.ok
|
|
})
|
|
})
|
|
|
|
describe('doing single role changes', () => {
|
|
describe('when changing workspace admin', () => {
|
|
describe('to workspace member', () => {
|
|
beforeEach(async () => {
|
|
await apollo.execute(
|
|
UpdateWorkspaceRoleDocument,
|
|
{
|
|
input: {
|
|
userId: workspaceAdminUser.id,
|
|
workspaceId: workspace.id,
|
|
role: Roles.Workspace.Member
|
|
}
|
|
},
|
|
{ assertNoErrors: true }
|
|
)
|
|
})
|
|
|
|
it('should still remain explicit owner and be implicit reviewer elsewhere, except private E', async () => {
|
|
const { projects, checkAllProjects, checkProject } = await getProjects({
|
|
user: workspaceAdminUser
|
|
})
|
|
|
|
expect(projects.length).to.eq(4)
|
|
expect(checkAllProjects((p) => p.isReviewer)).to.be.ok
|
|
expect(checkProject(workspaceProjectA).isExplicitOwner).to.be.ok
|
|
expect(checkProject(workspaceProjectB).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectC).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectD).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectE).hasAccess).to.be.not.ok
|
|
})
|
|
})
|
|
|
|
describe('to workspace guest', () => {
|
|
beforeEach(async () => {
|
|
await apollo.execute(
|
|
UpdateWorkspaceRoleDocument,
|
|
{
|
|
input: {
|
|
userId: workspaceAdminUser.id,
|
|
workspaceId: workspace.id,
|
|
role: Roles.Workspace.Guest
|
|
}
|
|
},
|
|
{ assertNoErrors: true }
|
|
)
|
|
})
|
|
|
|
it('should only have 1 project access, and not owner, but contributor', async () => {
|
|
const { projects, checkProject } = await getProjects({
|
|
user: workspaceAdminUser
|
|
})
|
|
|
|
expect(projects.length).to.eq(1)
|
|
expect(checkProject(workspaceProjectA).isOwner).to.not.be.ok
|
|
expect(checkProject(workspaceProjectA).isExplicitContributor).to.be.ok
|
|
expect(checkProject(workspaceProjectB).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectC).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectD).hasExplicitRole).to.be.not.ok
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('when changing workspace member', () => {
|
|
describe('to workspace admin', () => {
|
|
beforeEach(async () => {
|
|
await apollo.execute(
|
|
UpdateWorkspaceRoleDocument,
|
|
{
|
|
input: {
|
|
userId: workspaceMemberUser.id,
|
|
workspaceId: workspace.id,
|
|
role: Roles.Workspace.Admin
|
|
}
|
|
},
|
|
{ assertNoErrors: true }
|
|
)
|
|
})
|
|
|
|
it('should get implicit owner role everywhere and explicit upgraded to owner', async () => {
|
|
const { projects, checkProject, checkAllProjects } = await getProjects({
|
|
user: workspaceMemberUser
|
|
})
|
|
|
|
expect(projects.length).to.eq(5)
|
|
expect(checkAllProjects((p) => p.isOwner)).to.be.ok
|
|
expect(checkProject(workspaceProjectA).isExplicitOwner).to.be.ok
|
|
expect(checkProject(workspaceProjectB).isExplicitOwner).to.be.ok
|
|
expect(checkProject(workspaceProjectC).isExplicitOwner).to.be.ok
|
|
expect(checkProject(workspaceProjectD).hasExplicitRole).to.not.be.ok
|
|
expect(checkProject(workspaceProjectE).hasExplicitRole).to.not.be.ok
|
|
})
|
|
})
|
|
|
|
describe('to workspace guest', () => {
|
|
beforeEach(async () => {
|
|
await apollo.execute(
|
|
UpdateWorkspaceRoleDocument,
|
|
{
|
|
input: {
|
|
userId: workspaceMemberUser.id,
|
|
workspaceId: workspace.id,
|
|
role: Roles.Workspace.Guest
|
|
}
|
|
},
|
|
{ assertNoErrors: true }
|
|
)
|
|
})
|
|
|
|
it('no implicit access and all explicit downgraded to contributor or less', async () => {
|
|
const { projects, checkProject } = await getProjects({
|
|
user: workspaceMemberUser
|
|
})
|
|
|
|
expect(projects.length).to.eq(3)
|
|
expect(checkProject(workspaceProjectA).isExplicitContributor).to.be.ok
|
|
expect(checkProject(workspaceProjectB).isExplicitContributor).to.be.ok
|
|
expect(checkProject(workspaceProjectC).isExplicitReviewer).to.be.ok
|
|
expect(checkProject(workspaceProjectD).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectE).hasExplicitRole).to.be.not.ok
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('when changing workspace guest', () => {
|
|
describe('to workspace admin', () => {
|
|
beforeEach(async () => {
|
|
await apollo.execute(
|
|
UpdateWorkspaceRoleDocument,
|
|
{
|
|
input: {
|
|
userId: workspaceGuestUser.id,
|
|
workspaceId: workspace.id,
|
|
role: Roles.Workspace.Admin
|
|
}
|
|
},
|
|
{ assertNoErrors: true }
|
|
)
|
|
})
|
|
|
|
it('should upgrade explicit role to owner, and have implicit owner everywhere', async () => {
|
|
const { projects, checkProject, checkAllProjects } = await getProjects({
|
|
user: workspaceGuestUser
|
|
})
|
|
|
|
expect(projects.length).to.eq(5)
|
|
expect(checkAllProjects((p) => p.isOwner)).to.be.ok
|
|
expect(checkProject(workspaceProjectA).isExplicitOwner).to.be.ok
|
|
expect(checkProject(workspaceProjectB).isExplicitOwner).to.be.ok
|
|
expect(checkProject(workspaceProjectC).hasExplicitRole).to.not.be.ok
|
|
expect(checkProject(workspaceProjectD).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectE).isExplicitOwner).to.be.ok
|
|
})
|
|
})
|
|
|
|
describe('to workspace member', () => {
|
|
beforeEach(async () => {
|
|
await apollo.execute(
|
|
UpdateWorkspaceRoleDocument,
|
|
{
|
|
input: {
|
|
userId: workspaceGuestUser.id,
|
|
workspaceId: workspace.id,
|
|
role: Roles.Workspace.Member
|
|
}
|
|
},
|
|
{ assertNoErrors: true }
|
|
)
|
|
})
|
|
|
|
it('should retain same explicit access and get full implicit acccess', async () => {
|
|
const { projects, checkProject, checkAllProjects } = await getProjects({
|
|
user: workspaceGuestUser
|
|
})
|
|
|
|
expect(projects.length).to.eq(5)
|
|
expect(checkAllProjects((p) => p.isReviewer)).to.be.ok
|
|
expect(checkProject(workspaceProjectA).isExplicitContributor).to.be.ok
|
|
expect(checkProject(workspaceProjectB).isExplicitReviewer).to.be.ok
|
|
expect(checkProject(workspaceProjectC).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectD).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectE).isExplicitReviewer).to.be.ok
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('when changing workspace member viewer', () => {
|
|
describe('to workspace admin', () => {
|
|
beforeEach(async () => {
|
|
await apollo.execute(
|
|
UpdateWorkspaceRoleDocument,
|
|
{
|
|
input: {
|
|
userId: workspaceMemberViewerUser.id,
|
|
workspaceId: workspace.id,
|
|
role: Roles.Workspace.Admin
|
|
}
|
|
},
|
|
{ assertNoErrors: true }
|
|
)
|
|
})
|
|
|
|
it('should get editor seat, implicit owner role everywhere and explicit upgraded to owner', async () => {
|
|
const { workspace, projects, checkProject, checkAllProjects } =
|
|
await getProjects({
|
|
user: workspaceMemberViewerUser
|
|
})
|
|
|
|
expect(workspace.seatType).to.eq(WorkspaceSeatType.Editor)
|
|
expect(projects.length).to.eq(5)
|
|
expect(checkAllProjects((p) => p.isOwner)).to.be.ok
|
|
expect(checkProject(workspaceProjectA).isExplicitOwner).to.be.ok
|
|
expect(checkProject(workspaceProjectB).hasExplicitRole).to.not.be.ok
|
|
expect(checkProject(workspaceProjectC).hasExplicitRole).to.not.be.ok
|
|
expect(checkProject(workspaceProjectD).hasExplicitRole).to.not.be.ok
|
|
expect(checkProject(workspaceProjectE).isExplicitOwner).to.be.ok
|
|
})
|
|
})
|
|
|
|
describe('to workspace guest', () => {
|
|
beforeEach(async () => {
|
|
await apollo.execute(
|
|
UpdateWorkspaceRoleDocument,
|
|
{
|
|
input: {
|
|
userId: workspaceMemberViewerUser.id,
|
|
workspaceId: workspace.id,
|
|
role: Roles.Workspace.Guest
|
|
}
|
|
},
|
|
{ assertNoErrors: true }
|
|
)
|
|
})
|
|
|
|
it('retain viewer seat, no implicit access and all explicit at reviewer or less', async () => {
|
|
const { projects, checkProject, workspace } = await getProjects({
|
|
user: workspaceMemberViewerUser
|
|
})
|
|
|
|
expect(workspace.seatType).to.eq(WorkspaceSeatType.Viewer)
|
|
expect(projects.length).to.eq(2)
|
|
expect(checkProject(workspaceProjectA).isExplicitReviewer).to.be.ok
|
|
expect(checkProject(workspaceProjectE).isExplicitReviewer).to.be.ok
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('when changing workspace guest viewer', () => {
|
|
describe('to workspace admin', () => {
|
|
beforeEach(async () => {
|
|
await apollo.execute(
|
|
UpdateWorkspaceRoleDocument,
|
|
{
|
|
input: {
|
|
userId: workspaceGuestViewerUser.id,
|
|
workspaceId: workspace.id,
|
|
role: Roles.Workspace.Admin
|
|
}
|
|
},
|
|
{ assertNoErrors: true }
|
|
)
|
|
})
|
|
|
|
it('should upgrade seatType to editor, explicit role to owner, and have implicit owner everywhere', async () => {
|
|
const { workspace, projects, checkProject, checkAllProjects } =
|
|
await getProjects({
|
|
user: workspaceGuestViewerUser
|
|
})
|
|
|
|
expect(workspace.seatType).to.eq(WorkspaceSeatType.Editor)
|
|
expect(projects.length).to.eq(5)
|
|
expect(checkAllProjects((p) => p.isOwner)).to.be.ok
|
|
expect(checkProject(workspaceProjectA).hasExplicitRole).to.not.be.ok
|
|
expect(checkProject(workspaceProjectB).isExplicitOwner).to.be.ok
|
|
expect(checkProject(workspaceProjectC).hasExplicitRole).to.not.be.ok
|
|
expect(checkProject(workspaceProjectD).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectE).isExplicitOwner).to.be.ok
|
|
})
|
|
})
|
|
|
|
describe('to workspace member', () => {
|
|
beforeEach(async () => {
|
|
await apollo.execute(
|
|
UpdateWorkspaceRoleDocument,
|
|
{
|
|
input: {
|
|
userId: workspaceGuestViewerUser.id,
|
|
workspaceId: workspace.id,
|
|
role: Roles.Workspace.Member
|
|
}
|
|
},
|
|
{ assertNoErrors: true }
|
|
)
|
|
})
|
|
|
|
it('should retain viewer seat, same explicit access and get full workspace visibility implicit acccess', async () => {
|
|
const { workspace, projects, checkProject, checkAllProjects } =
|
|
await getProjects({
|
|
user: workspaceGuestViewerUser
|
|
})
|
|
|
|
expect(workspace.seatType).to.eq(WorkspaceSeatType.Viewer)
|
|
expect(projects.length).to.eq(5)
|
|
expect(checkAllProjects((p) => p.isReviewer)).to.be.ok
|
|
expect(checkProject(workspaceProjectA).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectB).isExplicitReviewer).to.be.ok
|
|
expect(checkProject(workspaceProjectC).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectD).hasExplicitRole).to.be.not.ok
|
|
expect(checkProject(workspaceProjectE).isExplicitReviewer).to.be.ok
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('doing composite role/seat changes', () => {
|
|
const testWorkspace: BasicTestWorkspace = {
|
|
id: '',
|
|
ownerId: '',
|
|
slug: '',
|
|
name: 'Test Composite Role Change Workspace'
|
|
}
|
|
|
|
const workspaceAdminUser: BasicTestUser = {
|
|
id: '',
|
|
name: 'Composite John "Owner" Specke',
|
|
email: 'composite-john-owner-speckle@example.org'
|
|
}
|
|
|
|
const workspaceMemberUser: BasicTestUser = {
|
|
id: '',
|
|
name: 'Composite John "Member" Speckel',
|
|
email: 'composite-john-member-speckle@example.org'
|
|
}
|
|
|
|
before(async () => {
|
|
await createTestUsers([workspaceAdminUser, workspaceMemberUser])
|
|
})
|
|
|
|
beforeEach(async () => {
|
|
await createTestWorkspace(testWorkspace, serverAdminUser, {
|
|
addPlan: {
|
|
name: 'team',
|
|
status: 'valid'
|
|
}
|
|
})
|
|
|
|
await assignToWorkspace(
|
|
testWorkspace,
|
|
workspaceAdminUser,
|
|
Roles.Workspace.Admin,
|
|
WorkspaceSeatType.Editor
|
|
)
|
|
await assignToWorkspace(
|
|
testWorkspace,
|
|
workspaceMemberUser,
|
|
Roles.Workspace.Member,
|
|
WorkspaceSeatType.Editor
|
|
)
|
|
})
|
|
|
|
afterEach(async () => {
|
|
await truncateTables([
|
|
Workspaces.name,
|
|
Streams.name,
|
|
WorkspaceAcl.name,
|
|
WorkspaceSeats.name
|
|
])
|
|
})
|
|
|
|
it('downgrading admin->guest if last owner, sets new owner from workspace admins', async () => {
|
|
// User Workspace Role Project Role
|
|
// serverAdminUser Admin None
|
|
// workspaceAdminUser Admin Owner
|
|
//
|
|
// Action: `workspaceAdminUser` downgraded to workspace guest
|
|
|
|
const project: BasicTestStream = {
|
|
id: '',
|
|
ownerId: '',
|
|
name: 'Test Composite Project',
|
|
isPublic: false,
|
|
workspaceId: testWorkspace.id
|
|
}
|
|
await createTestStream(project, workspaceAdminUser)
|
|
const apollo = await testApolloServer({
|
|
authUserId: serverAdminUser.id
|
|
})
|
|
|
|
const remove = await apollo.execute(UpdateWorkspaceRoleDocument, {
|
|
input: {
|
|
userId: workspaceAdminUser.id,
|
|
role: Roles.Workspace.Guest,
|
|
workspaceId: testWorkspace.id
|
|
}
|
|
})
|
|
expect(remove).to.not.haveGraphQLErrors()
|
|
|
|
const { workspace, checkProject } = await getWorkspaceProjects({
|
|
user: workspaceAdminUser,
|
|
workspace: testWorkspace
|
|
})
|
|
expect(workspace?.role).to.eq(Roles.Workspace.Guest)
|
|
expect(checkProject(project).isExplicitContributor).to.be.ok
|
|
|
|
const { checkProject: checkProjectForAdmin } = await getUserProjects({
|
|
user: serverAdminUser
|
|
})
|
|
expect(checkProjectForAdmin(project).isExplicitOwner).to.be.ok
|
|
})
|
|
|
|
it('downgrading member to viewer if last owner, sets new owner from workspace admins', async () => {
|
|
// User Workspace Role Project Role
|
|
// workspaceAdminUser Admin None
|
|
// workspaceMemberUser Member Owner
|
|
//
|
|
// Action: `workspaceAdminUser` downgraded to workspace guest
|
|
|
|
// ensure serverAdmin is no longer admin, so there's only 1 - workspaceAdmin
|
|
await unassignFromWorkspace(testWorkspace, serverAdminUser)
|
|
|
|
const project: BasicTestStream = {
|
|
id: '',
|
|
ownerId: '',
|
|
name: 'Test Composite Project',
|
|
isPublic: false,
|
|
workspaceId: testWorkspace.id
|
|
}
|
|
await createTestStream(project, workspaceMemberUser)
|
|
const apollo = await testApolloServer({
|
|
authUserId: workspaceAdminUser.id
|
|
})
|
|
|
|
const downgrade = await apollo.execute(UpdateWorkspaceSeatTypeDocument, {
|
|
input: {
|
|
userId: workspaceMemberUser.id,
|
|
workspaceId: testWorkspace.id,
|
|
seatType: WorkspaceSeatType.Viewer
|
|
}
|
|
})
|
|
expect(downgrade).to.not.haveGraphQLErrors()
|
|
|
|
const { workspace, checkProject } = await getWorkspaceProjects({
|
|
user: workspaceMemberUser,
|
|
workspace: testWorkspace
|
|
})
|
|
expect(workspace?.role).to.eq(Roles.Workspace.Member)
|
|
expect(workspace?.seatType).to.eq(WorkspaceSeatType.Viewer)
|
|
expect(checkProject(project).isExplicitReviewer).to.be.ok
|
|
|
|
const { checkProject: checkProjectForAdmin } = await getUserProjects({
|
|
user: workspaceAdminUser
|
|
})
|
|
expect(checkProjectForAdmin(project).isExplicitOwner).to.be.ok
|
|
})
|
|
|
|
it('leaving workspace as last owner of a workspace, sets new owner from workspace admins', async () => {
|
|
// User Workspace Role Project Role
|
|
// workspaceAdminUser Admin None
|
|
// workspaceMemberUser Member Owner
|
|
//
|
|
// Action: `workspaceMemberUser` leaves workspace
|
|
|
|
// ensure serverAdmin is no longer admin, so there's only 1 - workspaceAdmin
|
|
await unassignFromWorkspace(testWorkspace, serverAdminUser)
|
|
|
|
const project: BasicTestStream = {
|
|
id: '',
|
|
ownerId: '',
|
|
name: 'Test Leave Project',
|
|
isPublic: false,
|
|
workspaceId: testWorkspace.id
|
|
}
|
|
await createTestStream(project, workspaceMemberUser)
|
|
const apollo = await testApolloServer({
|
|
authUserId: workspaceMemberUser.id
|
|
})
|
|
|
|
const leave = await apollo.execute(ActiveUserLeaveWorkspaceDocument, {
|
|
id: testWorkspace.id
|
|
})
|
|
expect(leave).to.not.haveGraphQLErrors()
|
|
|
|
const { workspace } = await getUserWorkspace({
|
|
user: workspaceMemberUser,
|
|
workspace: testWorkspace
|
|
})
|
|
expect(workspace?.role).to.not.be.ok
|
|
|
|
const { checkProject } = await getUserProjects({
|
|
user: workspaceMemberUser
|
|
})
|
|
expect(checkProject(project).hasExplicitRole).to.be.not.ok
|
|
|
|
const { checkProject: checkProjectForAdmin } = await getUserProjects({
|
|
user: workspaceAdminUser
|
|
})
|
|
expect(checkProjectForAdmin(project).isExplicitOwner).to.be.ok
|
|
})
|
|
|
|
it('leaving workspace w/o owner roles works fine and removes all roles', async () => {
|
|
// User Workspace Role Project Role
|
|
// workspaceAdminUser Admin Owner
|
|
// workspaceMemberUser Member Reviewer
|
|
//
|
|
// Action: `workspaceMemberUser` leaves workspace
|
|
|
|
const project: BasicTestStream = {
|
|
id: '',
|
|
ownerId: '',
|
|
name: 'Test Leave Project',
|
|
isPublic: false,
|
|
workspaceId: testWorkspace.id
|
|
}
|
|
await createTestStream(project, workspaceAdminUser)
|
|
await addToStream(project, workspaceMemberUser, Roles.Stream.Reviewer)
|
|
|
|
const apollo = await testApolloServer({
|
|
authUserId: workspaceMemberUser.id
|
|
})
|
|
|
|
const leave = await apollo.execute(ActiveUserLeaveWorkspaceDocument, {
|
|
id: testWorkspace.id
|
|
})
|
|
expect(leave).to.not.haveGraphQLErrors()
|
|
expect(leave.data?.workspaceMutations.leave).to.be.ok
|
|
|
|
const { checkProject } = await getUserProjects({
|
|
user: workspaceMemberUser
|
|
})
|
|
expect(checkProject(project).hasExplicitRole).to.be.not.ok
|
|
|
|
const { workspace } = await getUserWorkspace({
|
|
user: workspaceMemberUser,
|
|
workspace: testWorkspace
|
|
})
|
|
expect(workspace?.role).to.be.not.ok
|
|
})
|
|
|
|
it('removing a workspace member that is the last owner of a workspace project sets new owner from workspace admins', async () => {
|
|
// User Workspace Role Project Role
|
|
// workspaceAdminUser Admin None
|
|
// workspaceMemberUser Member Owner
|
|
//
|
|
// Action: `workspaceAdminUser` removes `workspaceMemberUser` from the workspace
|
|
|
|
// ensure serverAdmin is no longer admin, so there's only 1 - workspaceAdmin
|
|
await unassignFromWorkspace(testWorkspace, serverAdminUser)
|
|
|
|
const project: BasicTestStream = {
|
|
id: '',
|
|
ownerId: '',
|
|
name: 'Test Remove Project',
|
|
isPublic: false,
|
|
workspaceId: testWorkspace.id
|
|
}
|
|
await createTestStream(project, workspaceMemberUser)
|
|
const apollo = await testApolloServer({
|
|
authUserId: workspaceAdminUser.id
|
|
})
|
|
|
|
const remove = await apollo.execute(UpdateWorkspaceRoleDocument, {
|
|
input: {
|
|
userId: workspaceMemberUser.id,
|
|
role: null,
|
|
workspaceId: testWorkspace.id
|
|
}
|
|
})
|
|
expect(remove).to.not.haveGraphQLErrors()
|
|
expect(remove.data?.workspaceMutations.updateRole).to.be.ok
|
|
|
|
const { checkProject } = await getUserProjects({
|
|
user: workspaceMemberUser
|
|
})
|
|
expect(checkProject(project).hasExplicitRole).to.be.not.ok
|
|
|
|
const { workspace } = await getUserWorkspace({
|
|
user: workspaceMemberUser,
|
|
workspace: testWorkspace
|
|
})
|
|
expect(workspace?.role).to.be.not.ok
|
|
|
|
const { checkProject: checkProjectForAdmin } = await getUserProjects({
|
|
user: workspaceAdminUser
|
|
})
|
|
expect(checkProjectForAdmin(project).isExplicitOwner).to.be.ok
|
|
})
|
|
|
|
it('removing a workspace member that is not the last owner of a workspace project works fine and removes all roles', async () => {
|
|
// User Workspace Role Project Role
|
|
// workspaceAdminUser Admin Owner
|
|
// workspaceMemberUser Member Reviewer
|
|
//
|
|
// Action: `workspaceAdminUser` removes `workspaceMemberUser` from the workspace
|
|
|
|
const project: BasicTestStream = {
|
|
id: '',
|
|
ownerId: '',
|
|
name: 'Test Remove Project',
|
|
isPublic: false,
|
|
workspaceId: testWorkspace.id
|
|
}
|
|
await createTestStream(project, workspaceAdminUser)
|
|
await addToStream(project, workspaceMemberUser, Roles.Stream.Reviewer)
|
|
|
|
const apollo = await testApolloServer({
|
|
authUserId: workspaceAdminUser.id
|
|
})
|
|
|
|
const remove = await apollo.execute(UpdateWorkspaceRoleDocument, {
|
|
input: {
|
|
userId: workspaceMemberUser.id,
|
|
role: null,
|
|
workspaceId: testWorkspace.id
|
|
}
|
|
})
|
|
expect(remove).to.not.haveGraphQLErrors()
|
|
expect(remove.data?.workspaceMutations.updateRole).to.be.ok
|
|
|
|
const { checkProject } = await getUserProjects({
|
|
user: workspaceMemberUser
|
|
})
|
|
expect(checkProject(project).hasExplicitRole).to.be.not.ok
|
|
|
|
const { workspace } = await getUserWorkspace({
|
|
user: workspaceMemberUser,
|
|
workspace: testWorkspace
|
|
})
|
|
expect(workspace?.role).to.be.not.ok
|
|
})
|
|
})
|
|
})
|