feat(server): support editor -> viewer seat downgrades (#4181)
* new seat based project role checks implemented * everything done * minor bugfix
This commit is contained in:
committed by
GitHub
parent
50fd05afe8
commit
d903e8ffc4
@@ -308,14 +308,16 @@ export const assignToWorkspace = async (
|
||||
await updateWorkspaceRole({
|
||||
userId: user.id,
|
||||
workspaceId: workspace.id,
|
||||
role
|
||||
role,
|
||||
updatedByUserId: workspace.ownerId
|
||||
})
|
||||
|
||||
if (seatType) {
|
||||
await assignWorkspaceSeat({
|
||||
userId: user.id,
|
||||
workspaceId: workspace.id,
|
||||
type: seatType
|
||||
type: seatType,
|
||||
assignedByUserId: workspace.ownerId
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
createWebhookConfigFactory,
|
||||
createWebhookEventFactory
|
||||
} from '@/modules/webhooks/repositories/webhooks'
|
||||
import { WorkspaceInvalidRoleError } from '@/modules/workspaces/errors/workspace'
|
||||
import {
|
||||
assignToWorkspace,
|
||||
BasicTestWorkspace,
|
||||
@@ -281,9 +282,7 @@ describe('Workspace project GQL CRUD', () => {
|
||||
})
|
||||
const newRole = await getUserStreamRole(workspaceGuest.id, roleProject.id)
|
||||
|
||||
expect(res).to.haveGraphQLErrors(
|
||||
'Workspace guests cannot be project owners'
|
||||
)
|
||||
expect(res).to.haveGraphQLErrors({ code: WorkspaceInvalidRoleError.code })
|
||||
expect(newRole).to.eq(Roles.Stream.Reviewer)
|
||||
})
|
||||
|
||||
@@ -310,12 +309,12 @@ describe('Workspace project GQL CRUD', () => {
|
||||
expect(resB).to.not.haveGraphQLErrors()
|
||||
expect(newRole).to.eq(Roles.Stream.Owner)
|
||||
} else {
|
||||
expect(resA).to.haveGraphQLErrors(
|
||||
'Workspace viewers can only be project reviewers.'
|
||||
)
|
||||
expect(resB).to.haveGraphQLErrors(
|
||||
'Workspace viewers can only be project reviewers.'
|
||||
)
|
||||
expect(resA).to.haveGraphQLErrors({
|
||||
code: WorkspaceInvalidRoleError.code
|
||||
})
|
||||
expect(resB).to.haveGraphQLErrors({
|
||||
code: WorkspaceInvalidRoleError.code
|
||||
})
|
||||
expect(newRole).to.eq(Roles.Stream.Reviewer)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -250,7 +250,11 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
|
||||
ensureValidWorkspaceRoleSeat: async () => {
|
||||
throw new Error('Should not happen')
|
||||
}
|
||||
})({ workspaceId: createRandomString(), userId: createRandomString() })
|
||||
})({
|
||||
workspaceId: createRandomString(),
|
||||
userId: createRandomString(),
|
||||
approvedByUserId: createRandomString()
|
||||
})
|
||||
)
|
||||
|
||||
expect(err.message).to.equal('User not found')
|
||||
@@ -270,7 +274,11 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
|
||||
ensureValidWorkspaceRoleSeat: async () => {
|
||||
throw new Error('Should not happen')
|
||||
}
|
||||
})({ workspaceId: createRandomString(), userId: createRandomString() })
|
||||
})({
|
||||
workspaceId: createRandomString(),
|
||||
userId: createRandomString(),
|
||||
approvedByUserId: createRandomString()
|
||||
})
|
||||
)
|
||||
|
||||
expect(err.message).to.equal(WorkspaceNotFoundError.defaultMessage)
|
||||
@@ -298,7 +306,11 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
|
||||
ensureValidWorkspaceRoleSeat: async () => {
|
||||
throw new Error('Should not happen')
|
||||
}
|
||||
})({ workspaceId: createRandomString(), userId: createRandomString() })
|
||||
})({
|
||||
workspaceId: createRandomString(),
|
||||
userId: createRandomString(),
|
||||
approvedByUserId: createRandomString()
|
||||
})
|
||||
)
|
||||
|
||||
expect(err.message).to.equal('Workspace join request not found')
|
||||
@@ -364,7 +376,7 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
})
|
||||
})({ workspaceId: workspace.id, userId: user.id })
|
||||
})({ workspaceId: workspace.id, userId: user.id, approvedByUserId: user.id })
|
||||
).to.equal(true)
|
||||
|
||||
expect(
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
createRandomEmail,
|
||||
createRandomString
|
||||
} from '@/modules/core/helpers/testHelpers'
|
||||
import { deleteProjectRoleFactory } from '@/modules/core/repositories/streams'
|
||||
import { WorkspaceSeatType } from '@/modules/gatekeeper/domain/billing'
|
||||
import { getWorkspaceUserSeatFactory } from '@/modules/gatekeeper/repositories/workspaceSeat'
|
||||
import {
|
||||
@@ -12,12 +13,14 @@ import {
|
||||
} from '@/modules/workspaces/tests/helpers/creation'
|
||||
import { BasicTestUser, createTestUser } from '@/test/authHelper'
|
||||
import {
|
||||
GetProjectCollaboratorsDocument,
|
||||
UpdateWorkspaceSeatTypeDocument,
|
||||
WorkspaceUpdateSeatTypeInput
|
||||
} from '@/test/graphql/generated/graphql'
|
||||
import { testApolloServer, TestApolloServer } from '@/test/graphqlHelper'
|
||||
import { beforeEachContext } from '@/test/hooks'
|
||||
import { StripeClientMock } from '@/test/mocks/global'
|
||||
import { BasicTestStream, createTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
|
||||
@@ -112,7 +115,7 @@ describe('Workspace Seats @graphql', () => {
|
||||
expect(res.data?.workspaceMutations.updateSeatType).to.not.be.ok
|
||||
})
|
||||
|
||||
it('should assign a workspace seat with the provided type and reconcile subscription', async () => {
|
||||
it('should upgrade a workspace seat and reconcile subscription', async () => {
|
||||
const user: BasicTestUser = {
|
||||
id: createRandomString(),
|
||||
name: createRandomString(),
|
||||
@@ -151,5 +154,107 @@ describe('Workspace Seats @graphql', () => {
|
||||
expect(reconcileArgs.prorationBehavior).to.eq('always_invoice') // new plan
|
||||
expect(reconcileArgs.subscriptionData.products.length).to.be.ok
|
||||
})
|
||||
|
||||
it('should downgrade a workspace seat', async () => {
|
||||
const user: BasicTestUser = {
|
||||
id: createRandomString(),
|
||||
name: createRandomString(),
|
||||
email: createRandomEmail(),
|
||||
role: Roles.Server.User,
|
||||
verified: true
|
||||
}
|
||||
await createTestUser(user)
|
||||
await assignToWorkspace(
|
||||
testWorkspace1,
|
||||
user,
|
||||
Roles.Workspace.Member,
|
||||
WorkspaceSeatType.Editor
|
||||
)
|
||||
|
||||
const res = await updateSeatType({
|
||||
workspaceId: testWorkspace1.id,
|
||||
userId: user.id,
|
||||
seatType: WorkspaceSeatType.Viewer
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(
|
||||
res.data?.workspaceMutations.updateSeatType.team.items.find(
|
||||
(i) => i.id === user.id
|
||||
)?.seatType
|
||||
).to.eq(WorkspaceSeatType.Viewer)
|
||||
})
|
||||
|
||||
it('should assign away project ownership on downgrade to viewer seat', async () => {
|
||||
const testWorkspace2: BasicTestWorkspace = {
|
||||
id: '',
|
||||
slug: '',
|
||||
ownerId: '',
|
||||
name: 'Test Workspace 2'
|
||||
}
|
||||
await createTestWorkspace(testWorkspace2, workspaceAdmin, {
|
||||
addPlan: { name: 'pro', status: 'valid' }
|
||||
})
|
||||
|
||||
const user: BasicTestUser = {
|
||||
id: createRandomString(),
|
||||
name: createRandomString(),
|
||||
email: createRandomEmail(),
|
||||
role: Roles.Server.User,
|
||||
verified: true
|
||||
}
|
||||
await createTestUser(user)
|
||||
await assignToWorkspace(
|
||||
testWorkspace2,
|
||||
user,
|
||||
Roles.Workspace.Member,
|
||||
WorkspaceSeatType.Editor
|
||||
)
|
||||
|
||||
const userOwnedProject: BasicTestStream = {
|
||||
name: 'User Owned Project',
|
||||
isPublic: false,
|
||||
id: '',
|
||||
ownerId: '',
|
||||
workspaceId: testWorkspace2.id
|
||||
}
|
||||
await createTestStream(userOwnedProject, user)
|
||||
|
||||
// Manually remove admin stream role, to test that it's being added
|
||||
await deleteProjectRoleFactory({ db })({
|
||||
projectId: userOwnedProject.id,
|
||||
userId: workspaceAdmin.id
|
||||
})
|
||||
|
||||
const res1 = await updateSeatType({
|
||||
workspaceId: testWorkspace2.id,
|
||||
userId: user.id,
|
||||
seatType: WorkspaceSeatType.Viewer
|
||||
})
|
||||
|
||||
expect(res1).to.not.haveGraphQLErrors()
|
||||
expect(
|
||||
res1.data?.workspaceMutations.updateSeatType.team.items.find(
|
||||
(i) => i.id === user.id
|
||||
)?.seatType
|
||||
).to.eq(WorkspaceSeatType.Viewer)
|
||||
|
||||
// Check project ownership
|
||||
const res2 = await apollo.execute(
|
||||
GetProjectCollaboratorsDocument,
|
||||
{
|
||||
projectId: userOwnedProject.id
|
||||
},
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
|
||||
expect(res2.data?.project.id).to.eq(userOwnedProject.id)
|
||||
expect(res2.data?.project.team.length).to.greaterThanOrEqual(2)
|
||||
|
||||
const adminRes = res2.data?.project.team.find((t) => t.id === workspaceAdmin.id)
|
||||
const userRes = res2.data?.project.team.find((t) => t.id === user.id)
|
||||
expect(adminRes?.role).to.eq(Roles.Stream.Owner)
|
||||
expect(userRes?.role).to.eq(Roles.Stream.Reviewer)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -8,6 +8,10 @@ import {
|
||||
} from '@/modules/workspaces/events/eventListener'
|
||||
import { expect } from 'chai'
|
||||
import { chunk } from 'lodash'
|
||||
import {
|
||||
GetWorkspaceRolesAndSeats,
|
||||
WorkspaceSeatType
|
||||
} from '@/modules/gatekeeper/domain/billing'
|
||||
|
||||
describe('Event handlers', () => {
|
||||
describe('onProjectCreatedFactory creates a function, that', () => {
|
||||
@@ -38,13 +42,28 @@ describe('Event handlers', () => {
|
||||
|
||||
const projectRoles: StreamAclRecord[] = []
|
||||
|
||||
// TODO: New plan support
|
||||
const onProjectCreated = onProjectCreatedFactory({
|
||||
getWorkspaceRoles: async () => workspaceRoles,
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: async () => ({
|
||||
[Roles.Workspace.Admin]: Roles.Stream.Owner,
|
||||
[Roles.Workspace.Member]: Roles.Stream.Contributor,
|
||||
[Roles.Workspace.Guest]: null
|
||||
}),
|
||||
getWorkspaceRolesAndSeats: async () =>
|
||||
workspaceRoles.reduce((acc, role) => {
|
||||
acc[role.userId] = { role, seat: null, userId: role.userId }
|
||||
return acc
|
||||
}, {} as Awaited<ReturnType<GetWorkspaceRolesAndSeats>>),
|
||||
getWorkspaceRolesAllowedProjectRoles: async () => {
|
||||
const mapping = {
|
||||
[Roles.Workspace.Admin]: Roles.Stream.Owner,
|
||||
[Roles.Workspace.Member]: Roles.Stream.Contributor,
|
||||
[Roles.Workspace.Guest]: null
|
||||
}
|
||||
return {
|
||||
defaultProjectRole: ({ workspaceRole }) => {
|
||||
return mapping[workspaceRole]
|
||||
},
|
||||
allowedProjectRoles: ({ workspaceRole }) => {
|
||||
return [mapping[workspaceRole] || Roles.Stream.Reviewer]
|
||||
}
|
||||
}
|
||||
},
|
||||
upsertProjectRole: async ({ projectId, userId, role }) => {
|
||||
projectRoles.push({
|
||||
resourceId: projectId,
|
||||
@@ -68,29 +87,47 @@ describe('Event handlers', () => {
|
||||
describe('onWorkspaceRoleUpdatedFactory creates a function, that', () => {
|
||||
it('assigns no project roles if the role mapping returns null', async () => {
|
||||
let isDeleteCalled = false
|
||||
const fakeProject = { id: 'test' } as StreamRecord
|
||||
|
||||
await onWorkspaceRoleUpdatedFactory({
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: async () => ({
|
||||
[Roles.Workspace.Admin]: Roles.Stream.Owner,
|
||||
[Roles.Workspace.Member]: Roles.Stream.Contributor,
|
||||
[Roles.Workspace.Guest]: null
|
||||
}),
|
||||
getWorkspaceRolesAllowedProjectRoles: async () => {
|
||||
const mapping = {
|
||||
[Roles.Workspace.Admin]: Roles.Stream.Owner,
|
||||
[Roles.Workspace.Member]: Roles.Stream.Contributor,
|
||||
[Roles.Workspace.Guest]: null
|
||||
}
|
||||
return {
|
||||
defaultProjectRole: ({ workspaceRole }) => {
|
||||
return mapping[workspaceRole]
|
||||
},
|
||||
allowedProjectRoles: ({ workspaceRole }) => {
|
||||
return [mapping[workspaceRole] || Roles.Stream.Reviewer]
|
||||
}
|
||||
}
|
||||
},
|
||||
async *queryAllWorkspaceProjects() {
|
||||
yield [{ id: 'test' } as StreamRecord]
|
||||
yield [fakeProject as StreamRecord]
|
||||
},
|
||||
deleteProjectRole: async () => {
|
||||
isDeleteCalled = true
|
||||
return undefined
|
||||
getStreamsCollaboratorCounts: async () => {
|
||||
return {}
|
||||
},
|
||||
upsertProjectRole: async () => {
|
||||
expect.fail()
|
||||
setStreamCollaborator: async ({ role }) => {
|
||||
if (!role) {
|
||||
isDeleteCalled = true
|
||||
} else {
|
||||
expect.fail()
|
||||
}
|
||||
|
||||
return fakeProject
|
||||
}
|
||||
})({
|
||||
acl: {
|
||||
role: Roles.Workspace.Guest,
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
workspaceId: cryptoRandomString({ length: 10 })
|
||||
}
|
||||
},
|
||||
seatType: WorkspaceSeatType.Editor,
|
||||
updatedByUserId: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
|
||||
expect(isDeleteCalled).to.be.true
|
||||
@@ -108,30 +145,50 @@ describe('Event handlers', () => {
|
||||
const storedRoles: { userId: string; role: StreamRoles; projectId: string }[] = []
|
||||
let trackProjectUpdate: boolean | undefined = false
|
||||
await onWorkspaceRoleUpdatedFactory({
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: async () => ({
|
||||
[Roles.Workspace.Admin]: Roles.Stream.Owner,
|
||||
[Roles.Workspace.Member]: projectRole,
|
||||
[Roles.Workspace.Guest]: null
|
||||
}),
|
||||
getWorkspaceRolesAllowedProjectRoles: async () => {
|
||||
const mapping = {
|
||||
[Roles.Workspace.Admin]: Roles.Stream.Owner,
|
||||
[Roles.Workspace.Member]: projectRole,
|
||||
[Roles.Workspace.Guest]: null
|
||||
}
|
||||
return {
|
||||
defaultProjectRole: ({ workspaceRole }) => {
|
||||
return mapping[workspaceRole]
|
||||
},
|
||||
allowedProjectRoles: ({ workspaceRole }) => {
|
||||
return [mapping[workspaceRole] || Roles.Stream.Reviewer]
|
||||
}
|
||||
}
|
||||
},
|
||||
async *queryAllWorkspaceProjects() {
|
||||
for (const projIds of chunk(projectIds, 3)) {
|
||||
yield projIds.map((projId) => ({ id: projId } as unknown as StreamRecord))
|
||||
}
|
||||
},
|
||||
deleteProjectRole: async () => {
|
||||
expect.fail()
|
||||
getStreamsCollaboratorCounts: async () => {
|
||||
return {}
|
||||
},
|
||||
upsertProjectRole: async (args, options) => {
|
||||
storedRoles.push(args)
|
||||
trackProjectUpdate = trackProjectUpdate || options?.trackProjectUpdate
|
||||
return {} as StreamRecord
|
||||
setStreamCollaborator: async (params, options) => {
|
||||
if (!params.role) {
|
||||
return expect.fail()
|
||||
} else {
|
||||
storedRoles.push({
|
||||
userId: params.userId,
|
||||
role: params.role,
|
||||
projectId: params.streamId
|
||||
})
|
||||
trackProjectUpdate = trackProjectUpdate || options?.trackProjectUpdate
|
||||
return {} as StreamRecord
|
||||
}
|
||||
}
|
||||
})({
|
||||
acl: {
|
||||
role: Roles.Workspace.Member,
|
||||
userId,
|
||||
workspaceId: cryptoRandomString({ length: 10 })
|
||||
}
|
||||
},
|
||||
seatType: WorkspaceSeatType.Editor,
|
||||
updatedByUserId: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
expect(storedRoles).deep.equals(
|
||||
projectIds.map((projectId) => ({ projectId, role: projectRole, userId }))
|
||||
|
||||
@@ -770,10 +770,12 @@ describe('Workspace role services', () => {
|
||||
it('sets the workspace role', async () => {
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const workspaceOwnerId = cryptoRandomString({ length: 10 })
|
||||
const role = {
|
||||
userId,
|
||||
workspaceId,
|
||||
role: Roles.Workspace.Member
|
||||
role: Roles.Workspace.Member,
|
||||
updatedByUserId: workspaceOwnerId
|
||||
}
|
||||
|
||||
const { updateWorkspaceRole, context } = buildUpdateWorkspaceRoleAndTestContext({
|
||||
@@ -791,6 +793,7 @@ describe('Workspace role services', () => {
|
||||
it('emits a role-updated event', async () => {
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const workspaceOwnerId = cryptoRandomString({ length: 10 })
|
||||
const role: Pick<WorkspaceAcl, 'userId' | 'workspaceId' | 'role'> = {
|
||||
userId,
|
||||
workspaceId,
|
||||
@@ -801,7 +804,7 @@ describe('Workspace role services', () => {
|
||||
workspaceId
|
||||
})
|
||||
|
||||
await updateWorkspaceRole(role)
|
||||
await updateWorkspaceRole({ ...role, updatedByUserId: workspaceOwnerId })
|
||||
|
||||
const payload = {
|
||||
...(context.eventData
|
||||
@@ -811,11 +814,16 @@ describe('Workspace role services', () => {
|
||||
|
||||
expect(context.eventData.isCalled).to.be.true
|
||||
expect(context.eventData.eventName).to.equal(WorkspaceEvents.RoleUpdated)
|
||||
expect(payload).to.deep.equal({ acl: role, seatType: WorkspaceSeatType.Editor })
|
||||
expect(payload).to.deep.equal({
|
||||
acl: role,
|
||||
seatType: WorkspaceSeatType.Editor,
|
||||
updatedByUserId: workspaceOwnerId
|
||||
})
|
||||
})
|
||||
it('throws if attempting to remove the last admin in a workspace', async () => {
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const workspaceOwnerId = cryptoRandomString({ length: 10 })
|
||||
const role: WorkspaceAcl = {
|
||||
userId,
|
||||
workspaceId,
|
||||
@@ -829,7 +837,11 @@ describe('Workspace role services', () => {
|
||||
})
|
||||
|
||||
await expectToThrow(() =>
|
||||
updateWorkspaceRole({ ...role, role: Roles.Workspace.Member })
|
||||
updateWorkspaceRole({
|
||||
...role,
|
||||
role: Roles.Workspace.Member,
|
||||
updatedByUserId: workspaceOwnerId
|
||||
})
|
||||
)
|
||||
})
|
||||
it('throws if attempting to set user role to more than GUEST and workspace domain protection is enabled and user has not an email matching a workspace domain', async () => {
|
||||
@@ -880,7 +892,8 @@ describe('Workspace role services', () => {
|
||||
updateWorkspaceRole({
|
||||
workspaceId,
|
||||
userId: guestId,
|
||||
role: Roles.Workspace.Member
|
||||
role: Roles.Workspace.Member,
|
||||
updatedByUserId: adminId
|
||||
})
|
||||
)
|
||||
expect(err.message).to.eq(new WorkspaceProtectedError().message)
|
||||
@@ -888,6 +901,7 @@ describe('Workspace role services', () => {
|
||||
it('sets roles on workspace projects when user added to workspace as admin', async () => {
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const workspaceOwnerId = cryptoRandomString({ length: 10 })
|
||||
const projectId = cryptoRandomString({ length: 10 })
|
||||
|
||||
const workspaceRole: WorkspaceAcl = {
|
||||
@@ -902,7 +916,7 @@ describe('Workspace role services', () => {
|
||||
workspaceProjects: [{ id: projectId } as StreamRecord]
|
||||
})
|
||||
|
||||
await updateWorkspaceRole(workspaceRole)
|
||||
await updateWorkspaceRole({ ...workspaceRole, updatedByUserId: workspaceOwnerId })
|
||||
|
||||
expect(context.workspaceProjectRoles.length).to.equal(1)
|
||||
expect(context.workspaceProjectRoles[0].userId).to.equal(userId)
|
||||
@@ -912,6 +926,7 @@ describe('Workspace role services', () => {
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const projectId = cryptoRandomString({ length: 10 })
|
||||
const workspaceOwnerId = cryptoRandomString({ length: 10 })
|
||||
|
||||
const workspaceRole: WorkspaceAcl = {
|
||||
userId,
|
||||
@@ -925,7 +940,7 @@ describe('Workspace role services', () => {
|
||||
workspaceProjects: [{ id: projectId } as StreamRecord]
|
||||
})
|
||||
|
||||
await updateWorkspaceRole(workspaceRole)
|
||||
await updateWorkspaceRole({ ...workspaceRole, updatedByUserId: workspaceOwnerId })
|
||||
|
||||
expect(context.workspaceProjectRoles.length).to.equal(1)
|
||||
expect(context.workspaceProjectRoles[0].userId).to.equal(userId)
|
||||
@@ -935,6 +950,7 @@ describe('Workspace role services', () => {
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const projectId = cryptoRandomString({ length: 10 })
|
||||
const workspaceOwnerId = cryptoRandomString({ length: 10 })
|
||||
|
||||
const workspaceRole: WorkspaceAcl = {
|
||||
userId,
|
||||
@@ -948,7 +964,7 @@ describe('Workspace role services', () => {
|
||||
workspaceProjects: [{ id: projectId } as StreamRecord]
|
||||
})
|
||||
|
||||
await updateWorkspaceRole(workspaceRole)
|
||||
await updateWorkspaceRole({ ...workspaceRole, updatedByUserId: workspaceOwnerId })
|
||||
|
||||
expect(context.workspaceProjectRoles.find((role) => role.userId === userId)).to
|
||||
.not.exist
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ProjectTeamMember } from '@/modules/core/domain/projects/types'
|
||||
import { ProjectNotFoundError } from '@/modules/core/errors/projects'
|
||||
import { StreamAclRecord, StreamRecord } from '@/modules/core/helpers/types'
|
||||
import { GetWorkspaceRolesAllowedProjectRolesFactory } from '@/modules/workspaces/domain/operations'
|
||||
import { WorkspaceInvalidProjectError } from '@/modules/workspaces/errors/workspace'
|
||||
import {
|
||||
moveProjectToWorkspaceFactory,
|
||||
@@ -12,11 +13,22 @@ import { Roles } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
|
||||
const getWorkspaceRoleToDefaultProjectRoleMapping = async () => ({
|
||||
'workspace:admin': Roles.Stream.Owner,
|
||||
'workspace:guest': null,
|
||||
'workspace:member': Roles.Stream.Contributor
|
||||
})
|
||||
const getWorkspaceRolesAllowedProjectRoles: GetWorkspaceRolesAllowedProjectRolesFactory =
|
||||
async () => {
|
||||
const mapping = {
|
||||
[Roles.Workspace.Admin]: Roles.Stream.Owner,
|
||||
[Roles.Workspace.Member]: Roles.Stream.Contributor,
|
||||
[Roles.Workspace.Guest]: null
|
||||
}
|
||||
return {
|
||||
defaultProjectRole: ({ workspaceRole }) => {
|
||||
return mapping[workspaceRole]
|
||||
},
|
||||
allowedProjectRoles: () => {
|
||||
return Object.values(Roles.Stream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('Project retrieval services', () => {
|
||||
describe('queryAllWorkspaceProjectFactory returns a generator, that', () => {
|
||||
@@ -105,10 +117,10 @@ describe('Project management services', () => {
|
||||
getProjectCollaborators: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
getWorkspaceRolesAndSeats: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: async () => {
|
||||
getWorkspaceRolesAllowedProjectRoles: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
updateWorkspaceRole: async () => {
|
||||
@@ -119,7 +131,8 @@ describe('Project management services', () => {
|
||||
const err = await expectToThrow(() =>
|
||||
moveProjectToWorkspace({
|
||||
projectId: cryptoRandomString({ length: 6 }),
|
||||
workspaceId: cryptoRandomString({ length: 6 })
|
||||
workspaceId: cryptoRandomString({ length: 6 }),
|
||||
movedByUserId: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
)
|
||||
expect(err.message).to.equal(new ProjectNotFoundError().message)
|
||||
@@ -140,10 +153,10 @@ describe('Project management services', () => {
|
||||
getProjectCollaborators: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
getWorkspaceRolesAndSeats: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: async () => {
|
||||
getWorkspaceRolesAllowedProjectRoles: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
updateWorkspaceRole: async () => {
|
||||
@@ -154,7 +167,8 @@ describe('Project management services', () => {
|
||||
const err = await expectToThrow(() =>
|
||||
moveProjectToWorkspace({
|
||||
projectId: cryptoRandomString({ length: 6 }),
|
||||
workspaceId: cryptoRandomString({ length: 6 })
|
||||
workspaceId: cryptoRandomString({ length: 6 }),
|
||||
movedByUserId: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
)
|
||||
expect(err instanceof WorkspaceInvalidProjectError).to.be.true
|
||||
@@ -185,27 +199,27 @@ describe('Project management services', () => {
|
||||
} as unknown as ProjectTeamMember
|
||||
]
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
return [
|
||||
{
|
||||
userId,
|
||||
role: Roles.Workspace.Admin,
|
||||
workspaceId,
|
||||
createdAt: new Date()
|
||||
getWorkspaceRolesAndSeats: async () => {
|
||||
return {
|
||||
[userId]: {
|
||||
role: {
|
||||
userId,
|
||||
role: Roles.Workspace.Admin,
|
||||
workspaceId,
|
||||
createdAt: new Date()
|
||||
},
|
||||
seat: null,
|
||||
userId
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: async () => ({
|
||||
'workspace:admin': Roles.Stream.Owner,
|
||||
'workspace:guest': null,
|
||||
'workspace:member': Roles.Stream.Contributor
|
||||
}),
|
||||
getWorkspaceRolesAllowedProjectRoles,
|
||||
updateWorkspaceRole: async (role) => {
|
||||
updatedRoles.push(role)
|
||||
}
|
||||
})
|
||||
|
||||
await moveProjectToWorkspace({ projectId, workspaceId })
|
||||
await moveProjectToWorkspace({ projectId, workspaceId, movedByUserId: userId })
|
||||
|
||||
expect(updatedRoles.length).to.equal(1)
|
||||
expect(updatedRoles[0].role).to.equal(Roles.Workspace.Admin)
|
||||
@@ -236,16 +250,16 @@ describe('Project management services', () => {
|
||||
} as unknown as ProjectTeamMember
|
||||
]
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
return []
|
||||
getWorkspaceRolesAndSeats: async () => {
|
||||
return {}
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping,
|
||||
getWorkspaceRolesAllowedProjectRoles,
|
||||
updateWorkspaceRole: async (role) => {
|
||||
updatedRoles.push(role)
|
||||
}
|
||||
})
|
||||
|
||||
await moveProjectToWorkspace({ projectId, workspaceId })
|
||||
await moveProjectToWorkspace({ projectId, workspaceId, movedByUserId: userId })
|
||||
|
||||
expect(updatedRoles.length).to.equal(1)
|
||||
expect(updatedRoles[0].role).to.equal(Roles.Workspace.Member)
|
||||
@@ -277,16 +291,16 @@ describe('Project management services', () => {
|
||||
} as unknown as ProjectTeamMember
|
||||
]
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
return []
|
||||
getWorkspaceRolesAndSeats: async () => {
|
||||
return {}
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping,
|
||||
getWorkspaceRolesAllowedProjectRoles,
|
||||
updateWorkspaceRole: async (role) => {
|
||||
updatedRoles.push(role)
|
||||
}
|
||||
})
|
||||
|
||||
await moveProjectToWorkspace({ projectId, workspaceId })
|
||||
await moveProjectToWorkspace({ projectId, workspaceId, movedByUserId: userId })
|
||||
|
||||
expect(updatedRoles.length).to.equal(1)
|
||||
expect(updatedRoles[0].role).to.equal(Roles.Workspace.Guest)
|
||||
@@ -319,14 +333,14 @@ describe('Project management services', () => {
|
||||
} as unknown as ProjectTeamMember
|
||||
]
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
return []
|
||||
getWorkspaceRolesAndSeats: async () => {
|
||||
return {}
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping,
|
||||
getWorkspaceRolesAllowedProjectRoles,
|
||||
updateWorkspaceRole: async () => {}
|
||||
})
|
||||
|
||||
await moveProjectToWorkspace({ projectId, workspaceId })
|
||||
await moveProjectToWorkspace({ projectId, workspaceId, movedByUserId: userId })
|
||||
|
||||
expect(updatedRoles.length).to.equal(1)
|
||||
expect(updatedRoles[0].role).to.equal(Roles.Stream.Owner)
|
||||
@@ -359,18 +373,14 @@ describe('Project management services', () => {
|
||||
} as unknown as ProjectTeamMember
|
||||
]
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
return []
|
||||
getWorkspaceRolesAndSeats: async () => {
|
||||
return {}
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: async () => ({
|
||||
[Roles.Workspace.Guest]: null,
|
||||
[Roles.Workspace.Member]: Roles.Stream.Contributor,
|
||||
[Roles.Workspace.Admin]: Roles.Stream.Owner
|
||||
}),
|
||||
getWorkspaceRolesAllowedProjectRoles,
|
||||
updateWorkspaceRole: async () => {}
|
||||
})
|
||||
|
||||
await moveProjectToWorkspace({ projectId, workspaceId })
|
||||
await moveProjectToWorkspace({ projectId, workspaceId, movedByUserId: userId })
|
||||
|
||||
expect(updatedRoles.length).to.equal(1)
|
||||
expect(updatedRoles[0].role).to.equal(Roles.Stream.Contributor)
|
||||
@@ -403,25 +413,25 @@ describe('Project management services', () => {
|
||||
} as unknown as ProjectTeamMember
|
||||
]
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
return [
|
||||
{
|
||||
userId,
|
||||
workspaceId,
|
||||
role: Roles.Workspace.Admin,
|
||||
createdAt: new Date()
|
||||
getWorkspaceRolesAndSeats: async () => {
|
||||
return {
|
||||
[userId]: {
|
||||
role: {
|
||||
userId,
|
||||
workspaceId,
|
||||
role: Roles.Workspace.Admin,
|
||||
createdAt: new Date()
|
||||
},
|
||||
seat: null,
|
||||
userId
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: async () => ({
|
||||
[Roles.Workspace.Guest]: null,
|
||||
[Roles.Workspace.Member]: Roles.Stream.Contributor,
|
||||
[Roles.Workspace.Admin]: Roles.Stream.Owner
|
||||
}),
|
||||
getWorkspaceRolesAllowedProjectRoles,
|
||||
updateWorkspaceRole: async () => {}
|
||||
})
|
||||
|
||||
await moveProjectToWorkspace({ projectId, workspaceId })
|
||||
await moveProjectToWorkspace({ projectId, workspaceId, movedByUserId: userId })
|
||||
|
||||
expect(updatedRoles.length).to.equal(1)
|
||||
expect(updatedRoles[0].role).to.equal(Roles.Stream.Owner)
|
||||
|
||||
Reference in New Issue
Block a user