Merge branch 'main' of github.com:specklesystems/speckle-server into alessandro/web-1767-guest-table-should-show-what-they-have-access-to

This commit is contained in:
Alessandro Magionami
2024-09-13 12:53:06 +02:00
28 changed files with 781 additions and 486 deletions
@@ -1,21 +1,15 @@
import { db } from '@/db/knex'
import {
deleteProjectRoleFactory,
getStream,
upsertProjectRoleFactory
} from '@/modules/core/repositories/streams'
import { getStream } from '@/modules/core/repositories/streams'
import {
findEmailsByUserIdFactory,
findVerifiedEmailsByUserIdFactory
} from '@/modules/core/repositories/userEmails'
import { getStreams } from '@/modules/core/services/streams'
import {
findUserByTargetFactory,
insertInviteAndDeleteOldFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { mapWorkspaceRoleToInitialProjectRole } from '@/modules/workspaces/domain/logic'
import {
getWorkspaceRolesFactory,
upsertWorkspaceFactory,
@@ -38,7 +32,6 @@ import {
updateWorkspaceFactory,
addDomainToWorkspaceFactory
} from '@/modules/workspaces/services/management'
import { queryAllWorkspaceProjectsFactory } from '@/modules/workspaces/services/projects'
import { BasicTestUser } from '@/test/authHelper'
import { CreateWorkspaceInviteMutationVariables } from '@/test/graphql/generated/graphql'
import { MaybeNullOrUndefined, Roles, WorkspaceRoles } from '@speckle/shared'
@@ -138,10 +131,6 @@ export const assignToWorkspace = async (
findVerifiedEmailsByUserId: findVerifiedEmailsByUserIdFactory({ db }),
getWorkspaceRoles: getWorkspaceRolesFactory({ db }),
upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db }),
getDefaultWorkspaceProjectRoleMapping: mapWorkspaceRoleToInitialProjectRole,
upsertProjectRole: upsertProjectRoleFactory({ db }),
deleteProjectRole: deleteProjectRoleFactory({ db }),
queryAllWorkspaceProjects: queryAllWorkspaceProjectsFactory({ getStreams }),
emitWorkspaceEvent: (...args) => getEventBus().emit(...args)
})
@@ -159,8 +148,6 @@ export const unassignFromWorkspace = async (
const deleteWorkspaceRole = deleteWorkspaceRoleFactory({
getWorkspaceRoles: getWorkspaceRolesFactory({ db }),
deleteWorkspaceRole: dbDeleteWorkspaceRoleFactory({ db }),
deleteProjectRole: deleteProjectRoleFactory({ db }),
queryAllWorkspaceProjects: queryAllWorkspaceProjectsFactory({ getStreams }),
emitWorkspaceEvent: (...args) => getEventBus().emit(...args)
})
@@ -565,11 +565,9 @@ describe('Workspaces GQL CRUD', () => {
// first 10 users
await createTestUsers(freeGuests)
await Promise.all(
freeGuests.map((guest) =>
assignToWorkspace(workspace, guest, Roles.Workspace.Guest)
)
)
for (const guest of freeGuests) {
await assignToWorkspace(workspace, guest, Roles.Workspace.Guest)
}
await Promise.all([
createTestUser(member),
@@ -4,7 +4,7 @@ import { Roles, StreamRoles } from '@speckle/shared'
import { StreamAclRecord, StreamRecord } from '@/modules/core/helpers/types'
import {
onProjectCreatedFactory,
onWorkspaceJoinedFactory
onWorkspaceRoleUpdatedFactory
} from '@/modules/workspaces/events/eventListener'
import { expect } from 'chai'
import { mapWorkspaceRoleToInitialProjectRole } from '@/modules/workspaces/domain/logic'
@@ -61,16 +61,22 @@ describe('Event handlers', () => {
expect(projectRoles.length).to.equal(2)
})
})
describe('onWorkspaceJoinedFactory creates a function, that', () => {
describe('onWorkspaceRoleUpdatedFactory creates a function, that', () => {
it('assigns no project roles if the role mapping returns null', async () => {
await onWorkspaceJoinedFactory({
let isDeleteCalled = false
await onWorkspaceRoleUpdatedFactory({
getDefaultWorkspaceProjectRoleMapping: async () => ({
[Roles.Workspace.Admin]: Roles.Stream.Owner,
[Roles.Workspace.Member]: Roles.Stream.Contributor,
[Roles.Workspace.Guest]: null
}),
async *queryAllWorkspaceProjects() {
expect.fail()
yield [{ id: 'test' } as StreamRecord]
},
deleteProjectRole: async () => {
isDeleteCalled = true
return undefined
},
upsertProjectRole: async () => {
expect.fail()
@@ -80,6 +86,8 @@ describe('Event handlers', () => {
userId: cryptoRandomString({ length: 10 }),
workspaceId: cryptoRandomString({ length: 10 })
})
expect(isDeleteCalled).to.be.true
})
it('assigns the mapped projects roles to all queried project', async () => {
const projectIds = [
@@ -92,7 +100,7 @@ describe('Event handlers', () => {
const projectRole = Roles.Stream.Reviewer
const storedRoles: { userId: string; role: StreamRoles; projectId: string }[] = []
await onWorkspaceJoinedFactory({
await onWorkspaceRoleUpdatedFactory({
getDefaultWorkspaceProjectRoleMapping: async () => ({
[Roles.Workspace.Admin]: Roles.Stream.Owner,
[Roles.Workspace.Member]: projectRole,
@@ -103,6 +111,9 @@ describe('Event handlers', () => {
yield projIds.map((projId) => ({ id: projId } as unknown as StreamRecord))
}
},
deleteProjectRole: async () => {
expect.fail()
},
upsertProjectRole: async (args) => {
storedRoles.push(args)
return {} as StreamRecord
@@ -14,7 +14,10 @@ import {
import { Roles } from '@speckle/shared'
import { expect } from 'chai'
import cryptoRandomString from 'crypto-random-string'
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
import {
WorkspaceEvents,
WorkspaceEventsPayloads
} from '@/modules/workspacesCore/domain/events'
import { StreamAclRecord, StreamRecord } from '@/modules/core/helpers/types'
import { expectToThrow } from '@/test/assertionHelper'
import { createRandomPassword } from '@/modules/core/helpers/testHelpers'
@@ -386,17 +389,21 @@ const buildDeleteWorkspaceRoleAndTestContext = (
context.eventData.eventName = eventName
context.eventData.payload = payload
switch (eventName) {
case 'workspace.role-deleted': {
const { userId } =
payload as WorkspaceEventsPayloads['workspace.role-deleted']
for (const project of context.workspaceProjects) {
context.workspaceProjectRoles = context.workspaceProjectRoles.filter(
(role) => role.resourceId !== project.id && role.userId !== userId
)
}
break
}
}
return []
},
async *queryAllWorkspaceProjects() {
yield context.workspaceProjects
},
deleteProjectRole: async ({ projectId, userId }) => {
context.workspaceProjectRoles = context.workspaceProjectRoles.filter(
(role) => role.resourceId !== projectId && role.userId !== userId
)
return {} as StreamRecord
},
...dependencyOverrides
}
@@ -430,32 +437,47 @@ const buildUpdateWorkspaceRoleAndTestContext = (
context.eventData.eventName = eventName
context.eventData.payload = payload
return []
},
async *queryAllWorkspaceProjects() {
yield context.workspaceProjects
},
getDefaultWorkspaceProjectRoleMapping: mapWorkspaceRoleToInitialProjectRole,
upsertProjectRole: async (role) => {
const streamAcl: StreamAclRecord = {
userId: role.userId,
role: role.role,
resourceId: role.projectId
switch (eventName) {
case 'workspace.role-deleted': {
const { userId } =
payload as WorkspaceEventsPayloads['workspace.role-deleted']
for (const project of context.workspaceProjects) {
context.workspaceProjectRoles = context.workspaceProjectRoles.filter(
(role) => role.resourceId !== project.id && role.userId !== userId
)
}
break
}
case 'workspace.role-updated': {
const workspaceRole =
payload as WorkspaceEventsPayloads['workspace.role-updated']
const mapping = await mapWorkspaceRoleToInitialProjectRole({
workspaceId: workspaceRole.workspaceId
})
for (const project of context.workspaceProjects) {
const projectRole = mapping[workspaceRole.role]
if (!projectRole) {
continue
}
const streamAcl: StreamAclRecord = {
userId: workspaceRole.userId,
role: projectRole,
resourceId: project.id
}
context.workspaceProjectRoles = context.workspaceProjectRoles.filter(
(acl) => acl.userId !== workspaceRole.userId
)
context.workspaceProjectRoles.push(streamAcl)
}
break
}
}
context.workspaceProjectRoles = context.workspaceProjectRoles.filter(
(acl) => acl.userId !== role.userId
)
context.workspaceProjectRoles.push(streamAcl)
return {} as StreamRecord
},
deleteProjectRole: async ({ userId }) => {
context.workspaceProjectRoles = context.workspaceProjectRoles.filter(
(acl) => acl.userId !== userId
)
return {} as StreamRecord
return []
},
...dependencyOverrides
}
@@ -589,9 +611,15 @@ describe('Workspace role services', () => {
await updateWorkspaceRole(role)
const payload = {
...(context.eventData
.payload as WorkspaceEventsPayloads['workspace.role-updated'])
}
delete payload.flags
expect(context.eventData.isCalled).to.be.true
expect(context.eventData.eventName).to.equal(WorkspaceEvents.RoleUpdated)
expect(context.eventData.payload).to.deep.equal(role)
expect(payload).to.deep.equal(role)
})
it('throws if attempting to remove the last admin in a workspace', async () => {
const userId = cryptoRandomString({ length: 10 })