feat(workspaces): add workspace slug support (#2982)
* feat(workspaces): add workspace slug support * chore(workspaces): lint * feat(workspaces): add slug validation and generation * fix(workspaces): test lint miss
This commit is contained in:
@@ -32,11 +32,21 @@ export type GetWorkspace = (args: {
|
||||
userId?: string
|
||||
}) => Promise<WorkspaceWithOptionalRole | null>
|
||||
|
||||
export type GetWorkspaceBySlug = (args: {
|
||||
workspaceSlug: string
|
||||
userId?: string
|
||||
}) => Promise<WorkspaceWithOptionalRole | null>
|
||||
|
||||
export type GetWorkspaces = (args: {
|
||||
workspaceIds: string[]
|
||||
userId?: string
|
||||
}) => Promise<WorkspaceWithOptionalRole[]>
|
||||
|
||||
export type GetWorkspacesBySlug = (args: {
|
||||
workspaceIds: string[]
|
||||
userId?: string
|
||||
}) => Promise<WorkspaceWithOptionalRole[]>
|
||||
|
||||
export type StoreWorkspaceDomain = (args: {
|
||||
workspaceDomain: WorkspaceDomain
|
||||
}) => Promise<void>
|
||||
|
||||
@@ -18,6 +18,18 @@ export class WorkspaceInvalidUpdateError extends BaseError {
|
||||
static statusCode = 400
|
||||
}
|
||||
|
||||
export class WorkspaceSlugTakenError extends BaseError {
|
||||
static defaultMessage = 'The given workspace slug is already taken'
|
||||
static code = 'WORKSPACE_SLUG_TAKEN'
|
||||
static statusCode = 400
|
||||
}
|
||||
|
||||
export class WorkspaceSlugInvalidError extends BaseError {
|
||||
static defaultMessage = 'The workspace slug is invalid'
|
||||
static code = 'WORKSPACE_SLUG_INVALID'
|
||||
static statusCode = 400
|
||||
}
|
||||
|
||||
export class WorkspaceInvalidRoleError extends BaseError {
|
||||
static defaultMessage = 'Invalid workspace role provided'
|
||||
static code = 'WORKSPACE_INVALID_ROLE_ERROR'
|
||||
|
||||
@@ -73,7 +73,8 @@ import {
|
||||
countProjectsVersionsByWorkspaceIdFactory,
|
||||
countWorkspaceRoleWithOptionalProjectRoleFactory,
|
||||
getUserIdsWithRoleInWorkspaceFactory,
|
||||
getWorkspaceRoleForUserFactory
|
||||
getWorkspaceRoleForUserFactory,
|
||||
getWorkspaceBySlugFactory
|
||||
} from '@/modules/workspaces/repositories/workspaces'
|
||||
import {
|
||||
buildWorkspaceInviteEmailContentsFactory,
|
||||
@@ -90,8 +91,10 @@ import {
|
||||
createWorkspaceFactory,
|
||||
deleteWorkspaceFactory,
|
||||
deleteWorkspaceRoleFactory,
|
||||
generateValidSlugFactory,
|
||||
updateWorkspaceFactory,
|
||||
updateWorkspaceRoleFactory
|
||||
updateWorkspaceRoleFactory,
|
||||
validateSlugFactory
|
||||
} from '@/modules/workspaces/services/management'
|
||||
import {
|
||||
getWorkspaceProjectsFactory,
|
||||
@@ -215,6 +218,13 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
token: args.token,
|
||||
workspaceId: args.workspaceId
|
||||
})
|
||||
},
|
||||
validateWorkspaceSlug: async (_parent, args) => {
|
||||
const validateSlug = validateSlugFactory({
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db })
|
||||
})
|
||||
await validateSlug({ slug: args.slug })
|
||||
return true
|
||||
}
|
||||
},
|
||||
Mutation: {
|
||||
@@ -277,9 +287,15 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
},
|
||||
WorkspaceMutations: {
|
||||
create: async (_parent, args, context) => {
|
||||
const { name, description, defaultLogoIndex, logo } = args.input
|
||||
const { name, description, defaultLogoIndex, logo, slug } = args.input
|
||||
|
||||
const createWorkspace = createWorkspaceFactory({
|
||||
validateSlug: validateSlugFactory({
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db })
|
||||
}),
|
||||
generateValidSlug: generateValidSlugFactory({
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db })
|
||||
}),
|
||||
upsertWorkspace: upsertWorkspaceFactory({ db }),
|
||||
upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db }),
|
||||
emitWorkspaceEvent: getEventBus().emit
|
||||
@@ -289,6 +305,7 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
userId: context.userId!,
|
||||
workspaceInput: {
|
||||
name,
|
||||
slug,
|
||||
description: description ?? null,
|
||||
logo: logo ?? null,
|
||||
defaultLogoIndex: defaultLogoIndex ?? 0
|
||||
@@ -331,6 +348,9 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
)
|
||||
|
||||
const updateWorkspace = updateWorkspaceFactory({
|
||||
validateSlug: validateSlugFactory({
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db })
|
||||
}),
|
||||
getWorkspace: getWorkspaceWithDomainsFactory({ db }),
|
||||
upsertWorkspace: upsertWorkspaceFactory({ db }),
|
||||
emitWorkspaceEvent: getEventBus().emit
|
||||
@@ -430,6 +450,9 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
db
|
||||
}),
|
||||
updateWorkspace: updateWorkspaceFactory({
|
||||
validateSlug: validateSlugFactory({
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db })
|
||||
}),
|
||||
getWorkspace: getWorkspaceWithDomainsFactory({ db }),
|
||||
upsertWorkspace: upsertWorkspaceFactory({ db }),
|
||||
emitWorkspaceEvent: getEventBus().emit
|
||||
|
||||
@@ -3,6 +3,7 @@ import { buildTableHelper } from '@/modules/core/dbSchema'
|
||||
export const Workspaces = buildTableHelper('workspaces', [
|
||||
'id',
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
GetUserDiscoverableWorkspaces,
|
||||
GetUserIdsWithRoleInWorkspace,
|
||||
GetWorkspace,
|
||||
GetWorkspaceBySlug,
|
||||
GetWorkspaceCollaborators,
|
||||
GetWorkspaceCollaboratorsTotalCount,
|
||||
GetWorkspaceDomains,
|
||||
@@ -86,6 +87,32 @@ export const getUserDiscoverableWorkspacesFactory =
|
||||
>[]
|
||||
}
|
||||
|
||||
const workspaceWithRoleBaseQuery = ({
|
||||
db,
|
||||
userId
|
||||
}: {
|
||||
db: Knex
|
||||
userId?: string
|
||||
}): Knex.QueryBuilder<WorkspaceWithOptionalRole, WorkspaceWithOptionalRole[]> => {
|
||||
let q = db<WorkspaceWithOptionalRole, WorkspaceWithOptionalRole[]>('workspaces')
|
||||
if (userId) {
|
||||
q = q
|
||||
.select([
|
||||
...Object.values(Workspaces.col),
|
||||
// Getting first role from grouped results
|
||||
knex.raw(`(array_agg("workspace_acl"."role"))[1] as role`)
|
||||
])
|
||||
.leftJoin(DbWorkspaceAcl.name, function () {
|
||||
this.on(DbWorkspaceAcl.col.workspaceId, Workspaces.col.id).andOnVal(
|
||||
DbWorkspaceAcl.col.userId,
|
||||
userId
|
||||
)
|
||||
})
|
||||
.groupBy(Workspaces.col.id)
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
export const getWorkspacesFactory =
|
||||
({ db }: { db: Knex }): GetWorkspaces =>
|
||||
async (params: {
|
||||
@@ -96,39 +123,28 @@ export const getWorkspacesFactory =
|
||||
userId?: string
|
||||
}) => {
|
||||
const { workspaceIds, userId } = params
|
||||
if (!workspaceIds?.length) return []
|
||||
|
||||
const q = Workspaces.knex<WorkspaceWithOptionalRole[]>(db).whereIn(
|
||||
Workspaces.col.id,
|
||||
workspaceIds
|
||||
)
|
||||
|
||||
if (userId) {
|
||||
q.select([
|
||||
...Object.values(Workspaces.col),
|
||||
// Getting first role from grouped results
|
||||
knex.raw(`(array_agg("workspace_acl"."role"))[1] as role`)
|
||||
])
|
||||
q.leftJoin(DbWorkspaceAcl.name, function () {
|
||||
this.on(DbWorkspaceAcl.col.workspaceId, Workspaces.col.id).andOnVal(
|
||||
DbWorkspaceAcl.col.userId,
|
||||
userId
|
||||
)
|
||||
})
|
||||
q.groupBy(Workspaces.col.id)
|
||||
}
|
||||
|
||||
const results = await q
|
||||
const q = workspaceWithRoleBaseQuery({ db, userId })
|
||||
const results = await q.whereIn(Workspaces.col.id, workspaceIds)
|
||||
return results
|
||||
}
|
||||
|
||||
export const getWorkspaceFactory =
|
||||
({ db }: { db: Knex }): GetWorkspace =>
|
||||
async ({ workspaceId, userId }) => {
|
||||
const [workspace] = await getWorkspacesFactory({ db })({
|
||||
workspaceIds: [workspaceId],
|
||||
userId
|
||||
})
|
||||
const workspace = await workspaceWithRoleBaseQuery({ db, userId })
|
||||
.where(Workspaces.col.id, workspaceId)
|
||||
.first()
|
||||
|
||||
return workspace || null
|
||||
}
|
||||
|
||||
export const getWorkspaceBySlugFactory =
|
||||
({ db }: { db: Knex }): GetWorkspaceBySlug =>
|
||||
async ({ workspaceSlug, userId }) => {
|
||||
const workspace = await workspaceWithRoleBaseQuery({ db, userId })
|
||||
.where(Workspaces.col.slug, workspaceSlug)
|
||||
.first()
|
||||
|
||||
return workspace || null
|
||||
}
|
||||
@@ -143,6 +159,7 @@ export const upsertWorkspaceFactory =
|
||||
.merge([
|
||||
'description',
|
||||
'logo',
|
||||
'slug',
|
||||
'defaultLogoIndex',
|
||||
'defaultProjectRole',
|
||||
'name',
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
GetWorkspaceWithDomains,
|
||||
GetWorkspaceDomains,
|
||||
UpdateWorkspace,
|
||||
GetWorkspaceBySlug,
|
||||
UpdateWorkspaceRole
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import {
|
||||
@@ -18,7 +19,12 @@ import {
|
||||
WorkspaceDomain,
|
||||
WorkspaceWithDomains
|
||||
} from '@/modules/workspacesCore/domain/types'
|
||||
import { MaybeNullOrUndefined, Roles } from '@speckle/shared'
|
||||
import {
|
||||
generateSlugFromName,
|
||||
MaybeNullOrUndefined,
|
||||
Roles,
|
||||
validateWorkspaceSlug
|
||||
} from '@speckle/shared'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { deleteStream } from '@/modules/core/repositories/streams'
|
||||
import {
|
||||
@@ -33,6 +39,8 @@ import {
|
||||
WorkspaceProtectedError,
|
||||
WorkspaceUnverifiedDomainError,
|
||||
WorkspaceNoVerifiedDomainsError,
|
||||
WorkspaceSlugTakenError,
|
||||
WorkspaceSlugInvalidError,
|
||||
WorkspaceInvalidUpdateError
|
||||
} from '@/modules/workspaces/errors/workspace'
|
||||
import { isUserLastWorkspaceAdmin } from '@/modules/workspaces/helpers/roles'
|
||||
@@ -61,6 +69,7 @@ type WorkspaceCreateArgs = {
|
||||
userId: string
|
||||
workspaceInput: {
|
||||
name: string
|
||||
slug?: string | null
|
||||
description: string | null
|
||||
logo: string | null
|
||||
defaultLogoIndex: number
|
||||
@@ -68,14 +77,58 @@ type WorkspaceCreateArgs = {
|
||||
userResourceAccessLimits: MaybeNullOrUndefined<TokenResourceIdentifier[]>
|
||||
}
|
||||
|
||||
type GenerateValidSlug = (args: { name: string }) => Promise<string>
|
||||
|
||||
type ValidateWorkspaceSlug = (args: { slug: string }) => Promise<void>
|
||||
|
||||
export const validateSlugFactory =
|
||||
({
|
||||
getWorkspaceBySlug
|
||||
}: {
|
||||
getWorkspaceBySlug: GetWorkspaceBySlug
|
||||
}): ValidateWorkspaceSlug =>
|
||||
async ({ slug }) => {
|
||||
try {
|
||||
validateWorkspaceSlug(slug)
|
||||
} catch (err) {
|
||||
if (err instanceof Error) throw new WorkspaceSlugInvalidError(err.message)
|
||||
throw err
|
||||
}
|
||||
const maybeClashingWorkspace = await getWorkspaceBySlug({
|
||||
workspaceSlug: slug
|
||||
})
|
||||
if (maybeClashingWorkspace) throw new WorkspaceSlugTakenError()
|
||||
}
|
||||
|
||||
export const generateValidSlugFactory =
|
||||
({
|
||||
getWorkspaceBySlug
|
||||
}: {
|
||||
getWorkspaceBySlug: GetWorkspaceBySlug
|
||||
}): GenerateValidSlug =>
|
||||
async ({ name }) => {
|
||||
const generatedSlug = generateSlugFromName({ name })
|
||||
|
||||
const maybeClashingWorkspace = await getWorkspaceBySlug({
|
||||
workspaceSlug: generatedSlug
|
||||
})
|
||||
return maybeClashingWorkspace
|
||||
? `${generatedSlug}-${cryptoRandomString({ length: 5 })}`
|
||||
: generatedSlug
|
||||
}
|
||||
|
||||
export const createWorkspaceFactory =
|
||||
({
|
||||
upsertWorkspace,
|
||||
upsertWorkspaceRole,
|
||||
generateValidSlug,
|
||||
validateSlug,
|
||||
emitWorkspaceEvent
|
||||
}: {
|
||||
upsertWorkspace: UpsertWorkspace
|
||||
upsertWorkspaceRole: UpsertWorkspaceRole
|
||||
validateSlug: ValidateWorkspaceSlug
|
||||
generateValidSlug: GenerateValidSlug
|
||||
emitWorkspaceEvent: EventBus['emit']
|
||||
}) =>
|
||||
async ({
|
||||
@@ -92,8 +145,16 @@ export const createWorkspaceFactory =
|
||||
throw new ForbiddenError('You are not authorized to create a workspace')
|
||||
}
|
||||
|
||||
let slug: string
|
||||
if (workspaceInput.slug) {
|
||||
await validateSlug({ slug: workspaceInput.slug })
|
||||
slug = workspaceInput.slug
|
||||
} else {
|
||||
slug = await generateValidSlug(workspaceInput)
|
||||
}
|
||||
const workspace = {
|
||||
...workspaceInput,
|
||||
slug,
|
||||
id: cryptoRandomString({ length: 10 }),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
@@ -168,10 +229,12 @@ const sanitizeInput = (input: Partial<Workspace>) => {
|
||||
export const updateWorkspaceFactory =
|
||||
({
|
||||
getWorkspace,
|
||||
validateSlug,
|
||||
upsertWorkspace,
|
||||
emitWorkspaceEvent
|
||||
}: {
|
||||
getWorkspace: GetWorkspaceWithDomains
|
||||
validateSlug: ValidateWorkspaceSlug
|
||||
upsertWorkspace: UpsertWorkspace
|
||||
emitWorkspaceEvent: EventBus['emit']
|
||||
}): UpdateWorkspace =>
|
||||
@@ -182,7 +245,6 @@ export const updateWorkspaceFactory =
|
||||
throw new WorkspaceNotFoundError()
|
||||
}
|
||||
|
||||
// Validate incoming changes
|
||||
if (
|
||||
!isValidInput(workspaceInput) ||
|
||||
!isValidWorkspace(workspaceInput, currentWorkspace)
|
||||
@@ -190,6 +252,8 @@ export const updateWorkspaceFactory =
|
||||
throw new WorkspaceInvalidUpdateError()
|
||||
}
|
||||
|
||||
if (workspaceInput.slug) await validateSlug({ slug: workspaceInput.slug })
|
||||
|
||||
const workspace = {
|
||||
...omit(currentWorkspace, 'domains'),
|
||||
...sanitizeInput(workspaceInput),
|
||||
|
||||
@@ -19,7 +19,8 @@ import {
|
||||
getWorkspaceFactory,
|
||||
getWorkspaceWithDomainsFactory,
|
||||
getWorkspaceDomainsFactory,
|
||||
storeWorkspaceDomainFactory
|
||||
storeWorkspaceDomainFactory,
|
||||
getWorkspaceBySlugFactory
|
||||
} from '@/modules/workspaces/repositories/workspaces'
|
||||
import {
|
||||
buildWorkspaceInviteEmailContentsFactory,
|
||||
@@ -31,10 +32,13 @@ import {
|
||||
updateWorkspaceRoleFactory,
|
||||
deleteWorkspaceRoleFactory,
|
||||
updateWorkspaceFactory,
|
||||
addDomainToWorkspaceFactory
|
||||
addDomainToWorkspaceFactory,
|
||||
validateSlugFactory,
|
||||
generateValidSlugFactory
|
||||
} from '@/modules/workspaces/services/management'
|
||||
import { BasicTestUser } from '@/test/authHelper'
|
||||
import { CreateWorkspaceInviteMutationVariables } from '@/test/graphql/generated/graphql'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import {
|
||||
MaybeNullOrUndefined,
|
||||
Roles,
|
||||
@@ -51,6 +55,7 @@ export type BasicTestWorkspace = {
|
||||
* Leave empty, will be filled on creation
|
||||
*/
|
||||
ownerId: string
|
||||
slug: string
|
||||
name: string
|
||||
description?: string
|
||||
logo?: string
|
||||
@@ -60,11 +65,17 @@ export type BasicTestWorkspace = {
|
||||
}
|
||||
|
||||
export const createTestWorkspace = async (
|
||||
workspace: BasicTestWorkspace,
|
||||
workspace: Omit<BasicTestWorkspace, 'slug'> & { slug?: string },
|
||||
owner: BasicTestUser,
|
||||
domain?: string
|
||||
) => {
|
||||
const createWorkspace = createWorkspaceFactory({
|
||||
validateSlug: validateSlugFactory({
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db })
|
||||
}),
|
||||
generateValidSlug: generateValidSlugFactory({
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db })
|
||||
}),
|
||||
upsertWorkspace: upsertWorkspaceFactory({ db }),
|
||||
upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db }),
|
||||
emitWorkspaceEvent: (...args) => getEventBus().emit(...args)
|
||||
@@ -74,6 +85,7 @@ export const createTestWorkspace = async (
|
||||
userId: owner.id,
|
||||
workspaceInput: {
|
||||
name: workspace.name,
|
||||
slug: workspace.slug || cryptoRandomString({ length: 10 }),
|
||||
description: workspace.description || null,
|
||||
logo: workspace.logo || null,
|
||||
defaultLogoIndex: 0
|
||||
@@ -100,13 +112,17 @@ export const createTestWorkspace = async (
|
||||
}
|
||||
|
||||
const updateWorkspace = updateWorkspaceFactory({
|
||||
validateSlug: validateSlugFactory({
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db })
|
||||
}),
|
||||
getWorkspace: getWorkspaceWithDomainsFactory({ db }),
|
||||
upsertWorkspace: upsertWorkspaceFactory({ db }),
|
||||
emitWorkspaceEvent: getEventBus().emit
|
||||
emitWorkspaceEvent: (...args) => getEventBus().emit(...args)
|
||||
})
|
||||
|
||||
if (workspace.discoverabilityEnabled) {
|
||||
if (!domain) throw new Error('Domain is needed for discoverability')
|
||||
|
||||
await updateWorkspace({
|
||||
workspaceId: newWorkspace.id,
|
||||
workspaceInput: {
|
||||
|
||||
@@ -4,6 +4,7 @@ export const basicWorkspaceFragment = gql`
|
||||
fragment BasicWorkspace on Workspace {
|
||||
id
|
||||
name
|
||||
slug
|
||||
updatedAt
|
||||
createdAt
|
||||
role
|
||||
|
||||
@@ -78,6 +78,7 @@ import { createRandomPassword } from '@/modules/core/helpers/testHelpers'
|
||||
import { addOrUpdateStreamCollaborator } from '@/modules/core/services/streams/streamAccessService'
|
||||
import { WorkspaceProtectedError } from '@/modules/workspaces/errors/workspace'
|
||||
import { ForbiddenError } from '@/modules/shared/errors'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
|
||||
enum InviteByTarget {
|
||||
Email = 'email',
|
||||
@@ -234,6 +235,7 @@ describe('Workspaces Invites GQL', () => {
|
||||
name: 'My First Workspace',
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
domainBasedMembershipProtectionEnabled: false
|
||||
}
|
||||
|
||||
@@ -241,12 +243,14 @@ describe('Workspaces Invites GQL', () => {
|
||||
name: 'My Domain protected workspace',
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
domainBasedMembershipProtectionEnabled: true
|
||||
}
|
||||
|
||||
const otherGuysWorkspace: BasicTestWorkspace = {
|
||||
name: 'Other Guy Workspace',
|
||||
id: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: ''
|
||||
}
|
||||
|
||||
@@ -587,6 +591,7 @@ describe('Workspaces Invites GQL', () => {
|
||||
const myProjectInviteTargetWorkspace: BasicTestWorkspace = {
|
||||
name: 'My Project Invite Target Workspace #1',
|
||||
id: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: ''
|
||||
}
|
||||
|
||||
@@ -783,6 +788,7 @@ describe('Workspaces Invites GQL', () => {
|
||||
const myAdministrationWorkspace: BasicTestWorkspace = {
|
||||
name: 'My Administration Workspace',
|
||||
id: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: ''
|
||||
}
|
||||
|
||||
@@ -908,6 +914,7 @@ describe('Workspaces Invites GQL', () => {
|
||||
const myInviteTargetWorkspace: BasicTestWorkspace = {
|
||||
name: 'My Invite Target Workspace',
|
||||
id: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: ''
|
||||
}
|
||||
const myInviteTargetWorkspaceStream1: BasicTestStream = {
|
||||
@@ -1122,6 +1129,7 @@ describe('Workspaces Invites GQL', () => {
|
||||
const brokenWorkspace: BasicTestWorkspace = {
|
||||
name: 'Broken Workspace',
|
||||
id: 'a',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: ''
|
||||
}
|
||||
await createTestWorkspaces([[brokenWorkspace, me]])
|
||||
@@ -1557,6 +1565,7 @@ describe('Workspaces Invites GQL', () => {
|
||||
const otherWorkspace: BasicTestWorkspace = {
|
||||
name: 'Other Workspace',
|
||||
id: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: ''
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ describe('Workspace project GQL CRUD', () => {
|
||||
const workspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
name: 'My Test Workspace'
|
||||
}
|
||||
|
||||
@@ -185,6 +186,7 @@ describe('Workspace project GQL CRUD', () => {
|
||||
const targetWorkspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
name: 'Target Workspace'
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
getUserDiscoverableWorkspacesFactory,
|
||||
getWorkspaceWithDomainsFactory,
|
||||
countWorkspaceRoleWithOptionalProjectRoleFactory,
|
||||
getWorkspaceCollaboratorsFactory
|
||||
getWorkspaceCollaboratorsFactory,
|
||||
getWorkspaceBySlugFactory
|
||||
} from '@/modules/workspaces/repositories/workspaces'
|
||||
import db from '@/db/knex'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
@@ -40,8 +41,10 @@ import {
|
||||
grantStreamPermissions,
|
||||
upsertProjectRoleFactory
|
||||
} from '@/modules/core/repositories/streams'
|
||||
import { omit } from 'lodash'
|
||||
|
||||
const getWorkspace = getWorkspaceFactory({ db })
|
||||
const getWorkspaceBySlug = getWorkspaceBySlugFactory({ db })
|
||||
const getWorkspaceCollaborators = getWorkspaceCollaboratorsFactory({ db })
|
||||
const upsertWorkspace = upsertWorkspaceFactory({ db })
|
||||
const deleteWorkspace = deleteWorkspaceFactory({ db })
|
||||
@@ -77,6 +80,7 @@ const createAndStoreTestWorkspace = async (
|
||||
) => {
|
||||
const workspace: Omit<Workspace, 'domains'> = {
|
||||
id: cryptoRandomString({ length: 10 }),
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
@@ -105,6 +109,37 @@ describe('Workspace repositories', () => {
|
||||
// not testing get here, we're going to use that for testing upsert
|
||||
})
|
||||
|
||||
describe('getWorkspaceBySlugFactory creates a function, that', () => {
|
||||
it('returns null if the workspace is not found', async () => {
|
||||
const workspace = await getWorkspaceBySlug({
|
||||
workspaceSlug: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
expect(workspace).to.be.null
|
||||
})
|
||||
it('returns the workspace', async () => {
|
||||
const testUserA: BasicTestUser = {
|
||||
id: '',
|
||||
name: 'John A Speckle',
|
||||
email: 'john@example.speckle',
|
||||
role: Roles.Server.Admin
|
||||
}
|
||||
const testWorkspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
name: 'Test Workspace'
|
||||
}
|
||||
|
||||
await createTestUsers([testUserA])
|
||||
await createTestWorkspace(testWorkspace, testUserA)
|
||||
|
||||
const workspace = await getWorkspaceBySlug({
|
||||
workspaceSlug: testWorkspace.slug
|
||||
})
|
||||
expect(workspace?.id).to.be.equal(testWorkspace.id)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getWorkspaceCollaboratorsFactory creates a function, that', () => {
|
||||
const testUserA: BasicTestUser = {
|
||||
id: '',
|
||||
@@ -133,6 +168,7 @@ describe('Workspace repositories', () => {
|
||||
const testWorkspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
name: 'Test Workspace'
|
||||
}
|
||||
|
||||
@@ -159,17 +195,20 @@ describe('Workspace repositories', () => {
|
||||
{
|
||||
id: '',
|
||||
ownerId: '',
|
||||
name: 'Test Workspace A'
|
||||
name: 'Test Workspace A',
|
||||
slug: cryptoRandomString({ length: 10 })
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
ownerId: '',
|
||||
name: 'Test Workspace B'
|
||||
name: 'Test Workspace B',
|
||||
slug: cryptoRandomString({ length: 10 })
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
ownerId: '',
|
||||
name: 'Test Workspace C'
|
||||
name: 'Test Workspace C',
|
||||
slug: cryptoRandomString({ length: 10 })
|
||||
}
|
||||
]
|
||||
|
||||
@@ -229,11 +268,16 @@ describe('Workspace repositories', () => {
|
||||
const storedWorkspace = await getWorkspace({ workspaceId: testWorkspace.id })
|
||||
expect(storedWorkspace).to.deep.equal(testWorkspace)
|
||||
|
||||
const updateData = {
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
name: cryptoRandomString({ length: 20 }),
|
||||
createdAt: new Date()
|
||||
}
|
||||
|
||||
await upsertWorkspace({
|
||||
workspace: {
|
||||
...testWorkspace,
|
||||
id: cryptoRandomString({ length: 13 }),
|
||||
createdAt: new Date()
|
||||
...updateData
|
||||
}
|
||||
})
|
||||
|
||||
@@ -241,7 +285,10 @@ describe('Workspace repositories', () => {
|
||||
workspaceId: testWorkspace.id
|
||||
})
|
||||
|
||||
expect(modifiedStoredWorkspace).to.deep.equal(testWorkspace)
|
||||
expect(modifiedStoredWorkspace).to.deep.equal({
|
||||
...testWorkspace,
|
||||
...omit(updateData, ['createdAt'])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -255,6 +302,7 @@ describe('Workspace repositories', () => {
|
||||
const workspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
name: 'Incredibly Forgettable'
|
||||
}
|
||||
|
||||
@@ -753,6 +801,7 @@ describe('Workspace repositories', () => {
|
||||
const workspace = {
|
||||
id: createRandomPassword(),
|
||||
name: 'my workspace',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: user.id
|
||||
}
|
||||
await createTestWorkspace(workspace, user)
|
||||
@@ -786,6 +835,7 @@ describe('Workspace repositories', () => {
|
||||
const workspace = {
|
||||
id: createRandomPassword(),
|
||||
name: 'my workspace',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: admin.id
|
||||
}
|
||||
await createTestWorkspace(workspace, admin)
|
||||
@@ -794,6 +844,7 @@ describe('Workspace repositories', () => {
|
||||
const workspace2 = {
|
||||
id: createRandomPassword(),
|
||||
name: 'my workspace',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: admin.id
|
||||
}
|
||||
await createTestWorkspace(workspace2, admin)
|
||||
@@ -835,6 +886,7 @@ describe('Workspace repositories', () => {
|
||||
const workspace = {
|
||||
id: createRandomPassword(),
|
||||
name: 'my workspace',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: admin.id
|
||||
}
|
||||
|
||||
@@ -906,6 +958,7 @@ describe('Workspace repositories', () => {
|
||||
const workspace = {
|
||||
id: createRandomPassword(),
|
||||
name: 'my workspace',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: admin.id
|
||||
}
|
||||
await createTestWorkspace(workspace, admin)
|
||||
@@ -983,6 +1036,7 @@ describe('Workspace repositories', () => {
|
||||
const workspace1 = {
|
||||
id: createRandomPassword(),
|
||||
name: 'my workspace',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: admin.id
|
||||
}
|
||||
await createTestWorkspace(workspace1, admin)
|
||||
@@ -990,6 +1044,7 @@ describe('Workspace repositories', () => {
|
||||
const workspace2 = {
|
||||
id: createRandomPassword(),
|
||||
name: 'my workspace 2',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: admin.id
|
||||
}
|
||||
await createTestWorkspace(workspace2, admin)
|
||||
|
||||
@@ -26,6 +26,7 @@ import { beforeEachContext, truncateTables } from '@/test/hooks'
|
||||
import { BasicTestStream, createTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { isUndefined } from 'lodash'
|
||||
|
||||
describe('Workspaces Roles GQL', () => {
|
||||
@@ -64,6 +65,7 @@ describe('Workspaces Roles GQL', () => {
|
||||
const workspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
name: 'My Test Workspace'
|
||||
}
|
||||
|
||||
@@ -187,6 +189,7 @@ describe('Workspaces Roles GQL', () => {
|
||||
const workspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
name: 'Test Workspace',
|
||||
defaultProjectRole: Roles.Stream.Reviewer
|
||||
}
|
||||
@@ -531,6 +534,7 @@ describe('Workspaces Roles GQL', () => {
|
||||
const workspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
name: 'Test Workspace w/ Projects'
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,8 @@ describe('Workspaces GQL CRUD', () => {
|
||||
const workspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
name: 'Workspace A'
|
||||
name: 'Workspace A',
|
||||
slug: cryptoRandomString({ length: 10 })
|
||||
}
|
||||
|
||||
const testMemberUser: BasicTestUser = {
|
||||
@@ -188,6 +189,7 @@ describe('Workspaces GQL CRUD', () => {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
name: 'My Large Workspace',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
description: 'A workspace with many users and roles to test pagination.'
|
||||
}
|
||||
|
||||
@@ -529,7 +531,10 @@ describe('Workspaces GQL CRUD', () => {
|
||||
|
||||
it('should return workspace cost', async () => {
|
||||
const createRes = await apollo.execute(CreateWorkspaceDocument, {
|
||||
input: { name: createRandomString() }
|
||||
input: {
|
||||
name: createRandomString(),
|
||||
slug: cryptoRandomString({ length: 10 })
|
||||
}
|
||||
})
|
||||
expect(createRes).to.not.haveGraphQLErrors()
|
||||
const workspaceId = createRes.data!.workspaceMutations.create.id
|
||||
@@ -666,6 +671,7 @@ describe('Workspaces GQL CRUD', () => {
|
||||
const workspace = {
|
||||
id: '',
|
||||
name: 'test ws',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: ''
|
||||
}
|
||||
await createTestWorkspace(workspace, testMemberUser)
|
||||
@@ -710,9 +716,10 @@ describe('Workspaces GQL CRUD', () => {
|
||||
describe('mutation workspaceMutations.create', () => {
|
||||
it('should create a workspace', async () => {
|
||||
const workspaceName = cryptoRandomString({ length: 6 })
|
||||
const workspaceSlug = cryptoRandomString({ length: 10 })
|
||||
|
||||
const createRes = await apollo.execute(CreateWorkspaceDocument, {
|
||||
input: { name: workspaceName }
|
||||
input: { name: workspaceName, slug: workspaceSlug }
|
||||
})
|
||||
const getRes = await apollo.execute(GetWorkspaceDocument, {
|
||||
workspaceId: createRes.data!.workspaceMutations.create.id
|
||||
@@ -722,6 +729,7 @@ describe('Workspaces GQL CRUD', () => {
|
||||
expect(getRes).to.not.haveGraphQLErrors()
|
||||
expect(getRes.data?.workspace).to.exist
|
||||
expect(getRes.data?.workspace?.name).to.equal(workspaceName)
|
||||
expect(getRes.data?.workspace?.slug).to.equal(workspaceSlug)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -729,6 +737,7 @@ describe('Workspaces GQL CRUD', () => {
|
||||
const workspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
name: 'My Test Workspace'
|
||||
}
|
||||
|
||||
@@ -825,7 +834,7 @@ describe('Workspaces GQL CRUD', () => {
|
||||
})
|
||||
|
||||
describe('mutation workspaceMutations.update', () => {
|
||||
const workspace: BasicTestWorkspace = {
|
||||
const workspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
name: cryptoRandomString({ length: 6 }),
|
||||
@@ -927,7 +936,7 @@ describe('Workspaces GQL CRUD', () => {
|
||||
it('allows the active user to leave a workspace', async () => {
|
||||
const name = cryptoRandomString({ length: 6 })
|
||||
const workspaceCreateResult = await apollo.execute(CreateWorkspaceDocument, {
|
||||
input: { name }
|
||||
input: { name, slug: cryptoRandomString({ length: 10 }) }
|
||||
})
|
||||
expect(workspaceCreateResult).to.not.haveGraphQLErrors()
|
||||
|
||||
@@ -970,7 +979,7 @@ describe('Workspaces GQL CRUD', () => {
|
||||
it('stops the last workspace admin from leaving the workspace', async () => {
|
||||
const name = cryptoRandomString({ length: 6 })
|
||||
const workspaceCreateResult = await apollo.execute(CreateWorkspaceDocument, {
|
||||
input: { name }
|
||||
input: { name, slug: cryptoRandomString({ length: 10 }) }
|
||||
})
|
||||
|
||||
const id = workspaceCreateResult.data?.workspaceMutations.create.id
|
||||
@@ -1000,7 +1009,7 @@ describe('Workspaces GQL CRUD', () => {
|
||||
const workspaceName = cryptoRandomString({ length: 6 })
|
||||
|
||||
const createRes = await apollo.execute(CreateWorkspaceDocument, {
|
||||
input: { name: workspaceName }
|
||||
input: { name: workspaceName, slug: cryptoRandomString({ length: 10 }) }
|
||||
})
|
||||
expect(createRes).to.not.haveGraphQLErrors()
|
||||
const workspaceId = createRes.data!.workspaceMutations.create.id
|
||||
|
||||
@@ -24,6 +24,7 @@ describe('workspace domain services', () => {
|
||||
defaultLogoIndex: 0,
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
logo: null,
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
description: '',
|
||||
@@ -47,6 +48,7 @@ describe('workspace domain services', () => {
|
||||
defaultLogoIndex: 0,
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
logo: null,
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
description: '',
|
||||
|
||||
@@ -24,6 +24,7 @@ const createTestWorkspaceWithDomains = (
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
name: createRandomPassword(),
|
||||
slug: createRandomPassword(),
|
||||
description: createRandomPassword(),
|
||||
id: createRandomPassword(),
|
||||
logo: null,
|
||||
|
||||
@@ -8,10 +8,12 @@ import {
|
||||
addDomainToWorkspaceFactory,
|
||||
createWorkspaceFactory,
|
||||
deleteWorkspaceRoleFactory,
|
||||
generateValidSlugFactory,
|
||||
updateWorkspaceFactory,
|
||||
updateWorkspaceRoleFactory
|
||||
updateWorkspaceRoleFactory,
|
||||
validateSlugFactory
|
||||
} from '@/modules/workspaces/services/management'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { Roles, validateWorkspaceSlug } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import {
|
||||
@@ -27,6 +29,8 @@ import {
|
||||
WorkspaceNotFoundError,
|
||||
WorkspaceNoVerifiedDomainsError,
|
||||
WorkspaceProtectedError,
|
||||
WorkspaceSlugInvalidError,
|
||||
WorkspaceSlugTakenError,
|
||||
WorkspaceUnverifiedDomainError
|
||||
} from '@/modules/workspaces/errors/workspace'
|
||||
import { UserEmail } from '@/modules/core/domain/userEmails/types'
|
||||
@@ -66,6 +70,8 @@ const buildCreateWorkspaceWithTestContext = (
|
||||
}) => {
|
||||
context.storedWorkspaces.push(workspace)
|
||||
},
|
||||
validateSlug: async () => {},
|
||||
generateValidSlug: async () => cryptoRandomString({ length: 10 }),
|
||||
upsertWorkspaceRole: async (workspaceAcl: WorkspaceAcl) => {
|
||||
context.storedRoles.push(workspaceAcl)
|
||||
},
|
||||
@@ -88,6 +94,7 @@ const getCreateWorkspaceInput = () => {
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
workspaceInput: {
|
||||
description: 'foobar',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
logo: null,
|
||||
name: cryptoRandomString({ length: 6 }),
|
||||
defaultLogoIndex: 0
|
||||
@@ -96,19 +103,117 @@ const getCreateWorkspaceInput = () => {
|
||||
}
|
||||
|
||||
describe('Workspace services', () => {
|
||||
describe('isSlugValid', () => {
|
||||
it('throws for url unsafe characters', async () => {
|
||||
const err = await expectToThrow(() => {
|
||||
validateWorkspaceSlug('{{{}}}}}')
|
||||
})
|
||||
expect(err.message).to.contain('only lowercase letters, numbers')
|
||||
})
|
||||
it('throws for too short inputs', async () => {
|
||||
const err = await expectToThrow(() => {
|
||||
validateWorkspaceSlug('{')
|
||||
})
|
||||
expect(err.message).to.contain('characters long.')
|
||||
})
|
||||
it('throws for too long inputs', async () => {
|
||||
const err = await expectToThrow(() => {
|
||||
validateWorkspaceSlug(cryptoRandomString({ length: 31 }))
|
||||
})
|
||||
expect(err.message).to.contain('slug must not exceed')
|
||||
})
|
||||
it('throws for invalid start', async () => {
|
||||
const err = await expectToThrow(() => {
|
||||
validateWorkspaceSlug('-asdfasdf-')
|
||||
})
|
||||
expect(err.message).to.contain('cannot start or end with a')
|
||||
})
|
||||
it('returns true for valid slugs', () => {
|
||||
validateWorkspaceSlug('asdf-asdf')
|
||||
// if it did not throw, we're good
|
||||
expect(true)
|
||||
})
|
||||
})
|
||||
describe('validateSlugFactory creates a function, that', () => {
|
||||
it('throws WorkspaceSlugTakenError if the input slug clashes an existing workspace', async () => {
|
||||
const validateSlug = validateSlugFactory({
|
||||
getWorkspaceBySlug: async () =>
|
||||
({ id: cryptoRandomString({ length: 10 }) } as Workspace)
|
||||
})
|
||||
|
||||
const err = await expectToThrow(async () => {
|
||||
await validateSlug({
|
||||
slug: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
})
|
||||
expect(err.message).to.be.equal(new WorkspaceSlugTakenError().message)
|
||||
})
|
||||
it('throws validation error for invalid slugs', async () => {
|
||||
const validateSlug = validateSlugFactory({
|
||||
getWorkspaceBySlug: async () => null
|
||||
})
|
||||
|
||||
const err = await expectToThrow(async () => {
|
||||
await validateSlug({
|
||||
slug: '-----'
|
||||
})
|
||||
})
|
||||
expect(err.message).to.contain('cannot start or end with a hyphen')
|
||||
})
|
||||
})
|
||||
|
||||
describe('generateValidSlugFactory creates a function, that', () => {
|
||||
it('generates a slug from the input name', async () => {
|
||||
const slug = await generateValidSlugFactory({
|
||||
getWorkspaceBySlug: async () => null
|
||||
})({ name: 'Foo bAr{ }baZ' })
|
||||
expect(slug).to.be.equal('foo-bar-baz')
|
||||
})
|
||||
it('adds a random string to the generated slug if it clashes an existing one', async () => {
|
||||
const slug = await generateValidSlugFactory({
|
||||
getWorkspaceBySlug: async () =>
|
||||
({ id: cryptoRandomString({ length: 10 }) } as Workspace)
|
||||
})({ name: 'FoobAr' })
|
||||
expect(slug).contain('foobar-')
|
||||
expect(slug.length).to.be.equal(12)
|
||||
})
|
||||
})
|
||||
describe('createWorkspaceFactory creates a function, that', () => {
|
||||
it('stores the workspace', async () => {
|
||||
const { context, createWorkspace } = buildCreateWorkspaceWithTestContext()
|
||||
it('throws WorkspaceSlugInvalidError if the input slug is not valid', async () => {
|
||||
const { createWorkspace } = buildCreateWorkspaceWithTestContext({
|
||||
validateSlug: async () => {
|
||||
throw new WorkspaceSlugInvalidError()
|
||||
}
|
||||
})
|
||||
|
||||
const { userId, workspaceInput } = getCreateWorkspaceInput()
|
||||
const err = await expectToThrow(async () => {
|
||||
await createWorkspace({
|
||||
userId,
|
||||
workspaceInput: { ...workspaceInput, slug: 'asdf{{}}}' },
|
||||
userResourceAccessLimits: null
|
||||
})
|
||||
})
|
||||
expect(err.message).to.be.equal(new WorkspaceSlugInvalidError().message)
|
||||
})
|
||||
it('generates a workspace slug from the workspace name', async () => {
|
||||
const generatedSlug = cryptoRandomString({ length: 10 })
|
||||
const { userId, workspaceInput } = getCreateWorkspaceInput()
|
||||
const { context, createWorkspace } = buildCreateWorkspaceWithTestContext({
|
||||
generateValidSlug: async () => generatedSlug
|
||||
})
|
||||
|
||||
const workspace = await createWorkspace({
|
||||
userId,
|
||||
workspaceInput,
|
||||
workspaceInput: { ...workspaceInput, slug: null },
|
||||
userResourceAccessLimits: null
|
||||
})
|
||||
|
||||
expect(context.storedWorkspaces.length).to.equal(1)
|
||||
expect(context.storedWorkspaces[0]).to.deep.equal(omit(workspace, 'domains'))
|
||||
expect(omit(context.storedWorkspaces[0], 'slug')).to.deep.equal(
|
||||
omit(workspace, 'domains', 'slug')
|
||||
)
|
||||
expect(context.storedWorkspaces[0].slug).to.equal(generatedSlug)
|
||||
})
|
||||
it('makes the workspace creator becomes a workspace:admin', async () => {
|
||||
const { context, createWorkspace } = buildCreateWorkspaceWithTestContext()
|
||||
@@ -150,6 +255,7 @@ describe('Workspace services', () => {
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const workspace: WorkspaceWithDomains = {
|
||||
id: workspaceId,
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
description: cryptoRandomString({ length: 20 }),
|
||||
createdAt: new Date(),
|
||||
@@ -167,6 +273,9 @@ describe('Workspace services', () => {
|
||||
const err = await expectToThrow(async () => {
|
||||
await updateWorkspaceFactory({
|
||||
getWorkspace: async () => null,
|
||||
validateSlug: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
emitWorkspaceEvent: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
@@ -188,6 +297,9 @@ describe('Workspace services', () => {
|
||||
emitWorkspaceEvent: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
validateSlug: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
upsertWorkspace: async () => {
|
||||
expect.fail()
|
||||
}
|
||||
@@ -208,6 +320,9 @@ describe('Workspace services', () => {
|
||||
emitWorkspaceEvent: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
validateSlug: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
upsertWorkspace: async () => {
|
||||
expect.fail()
|
||||
}
|
||||
@@ -220,6 +335,29 @@ describe('Workspace services', () => {
|
||||
})
|
||||
expect(err.message).to.be.equal('Provided logo is malformed')
|
||||
})
|
||||
it('validates description length', async () => {
|
||||
const workspace = createTestWorkspaceWithDomainsData()
|
||||
const err = await expectToThrow(async () => {
|
||||
await updateWorkspaceFactory({
|
||||
getWorkspace: async () => workspace,
|
||||
emitWorkspaceEvent: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
validateSlug: async () => {
|
||||
throw new WorkspaceSlugInvalidError()
|
||||
},
|
||||
upsertWorkspace: async () => {
|
||||
expect.fail()
|
||||
}
|
||||
})({
|
||||
workspaceId: workspace.id,
|
||||
workspaceInput: {
|
||||
slug: '{}{}{}{}'
|
||||
}
|
||||
})
|
||||
})
|
||||
expect(err.message).to.be.equal(new WorkspaceSlugInvalidError().message)
|
||||
})
|
||||
it('does not allow turning on discoverability if the workspace has no verified domains', async () => {
|
||||
const workspace = createTestWorkspaceWithDomainsData()
|
||||
const err = await expectToThrow(async () => {
|
||||
@@ -228,6 +366,7 @@ describe('Workspace services', () => {
|
||||
emitWorkspaceEvent: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
validateSlug: async () => {},
|
||||
upsertWorkspace: async () => {
|
||||
expect.fail()
|
||||
}
|
||||
@@ -249,6 +388,7 @@ describe('Workspace services', () => {
|
||||
emitWorkspaceEvent: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
validateSlug: async () => {},
|
||||
upsertWorkspace: async () => {
|
||||
expect.fail()
|
||||
}
|
||||
@@ -271,6 +411,8 @@ describe('Workspace services', () => {
|
||||
emitWorkspaceEvent: async () => {
|
||||
return []
|
||||
},
|
||||
validateSlug: async () => {},
|
||||
|
||||
upsertWorkspace: async ({ workspace }) => {
|
||||
newWorkspaceName = workspace.name
|
||||
}
|
||||
@@ -309,6 +451,7 @@ describe('Workspace services', () => {
|
||||
emitWorkspaceEvent: async () => {
|
||||
return []
|
||||
},
|
||||
validateSlug: async () => {},
|
||||
upsertWorkspace: async ({ workspace }) => {
|
||||
updatedWorkspace = workspace
|
||||
}
|
||||
@@ -932,6 +1075,7 @@ describe('Workspace role services', () => {
|
||||
userId,
|
||||
id: workspaceId,
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
logo: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
@@ -975,6 +1119,7 @@ describe('Workspace role services', () => {
|
||||
const workspace: Workspace = {
|
||||
id: workspaceId,
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
logo: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
@@ -1030,6 +1175,7 @@ describe('Workspace role services', () => {
|
||||
const workspace: Workspace = {
|
||||
id: workspaceId,
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
logo: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
@@ -1091,6 +1237,7 @@ describe('Workspace role services', () => {
|
||||
const workspaceWithoutDomains = {
|
||||
id: workspaceId,
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
logo: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
|
||||
Reference in New Issue
Block a user