Files
speckle-server/packages/server/modules/workspaces/tests/integration/workspaceSeat.graph.spec.ts
T
Gergő Jedlicska a3644a50c5 gergo/web 2931 introduce missing workspace plans (#4323)
* fix(gatekeeper): missing priceId-s should stop the server from booting

* feat(shared): add all new workspace plans

* feat(billing): add new world plans

* feat(ci): use stripe sandbox id-s from test env vars

* chore(ci): remove defunct stripe context

* chore(server-env): fix server env example

* feat(gatekeeper): add missing plans to graphql schema

* test(gatekeeper): update plan upgrade tests to match implementation

* fix(ci): update ci .env source

* fix(ci): we do need some secrets from stripe

* fix(ci): run gql schema introspect with the test env

* fix(frontend): format new plan names

* chore(ci): temp disable gql schema checks

* feat(helm): add unlimited seat prices env vars

* feat(helm): add unlimited seat prices to values

* feat(gatekeeper): do not load priceId-s as a side effect, load with module init
2025-04-07 15:24:57 +02:00

258 lines
7.7 KiB
TypeScript

import { db } from '@/db/knex'
import {
createRandomEmail,
createRandomString
} from '@/modules/core/helpers/testHelpers'
import { WorkspaceSeatType } from '@/modules/gatekeeper/domain/billing'
import { getWorkspaceUserSeatFactory } from '@/modules/gatekeeper/repositories/workspaceSeat'
import {
assignToWorkspace,
BasicTestWorkspace,
createTestWorkspace
} 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 {
addToStream,
BasicTestStream,
createTestStream
} from '@/test/speckle-helpers/streamHelper'
import { Roles } from '@speckle/shared'
import { expect } from 'chai'
const getWorkspaceUserSeat = getWorkspaceUserSeatFactory({ db })
describe('Workspace Seats @graphql', () => {
const workspaceAdmin: BasicTestUser = {
id: '',
name: 'Workspace Seats Admin Guy',
email: createRandomEmail(),
role: Roles.Server.Admin,
verified: true
}
let apollo: TestApolloServer
before(async () => {
await beforeEachContext()
await createTestUser(workspaceAdmin)
apollo = await testApolloServer({ authUserId: workspaceAdmin.id })
})
beforeEach(() => {
// cause we have a fake subscription
StripeClientMock.mockFunction(
'reconcileWorkspaceSubscriptionFactory',
() => async () => {}
)
})
after(async () => {
StripeClientMock.resetMockedFunctions()
})
describe('when being changed', () => {
const testWorkspace1: BasicTestWorkspace = {
id: '',
slug: '',
ownerId: '',
name: 'Test Workspace 1'
}
before(async () => {
await createTestWorkspace(testWorkspace1, workspaceAdmin, {
addPlan: { name: 'pro', status: 'valid' },
addSubscription: true
})
})
const updateSeatType = (input: WorkspaceUpdateSeatTypeInput) =>
apollo.execute(UpdateWorkspaceSeatTypeDocument, { input })
it('should throw an error if user is not a member of the workspace', async () => {
const user: BasicTestUser = {
id: createRandomString(),
name: createRandomString(),
email: createRandomEmail(),
role: Roles.Server.User,
verified: true
}
await createTestUser(user)
const res = await updateSeatType({
workspaceId: testWorkspace1.id,
userId: user.id,
seatType: WorkspaceSeatType.Editor
})
expect(res).to.haveGraphQLErrors('User does not have a role in the workspace')
expect(res.data?.workspaceMutations.updateSeatType).to.not.be.ok
})
it('should throw an error if seat type is not compatible with workspace role', 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.Admin)
const res = await updateSeatType({
workspaceId: testWorkspace1.id,
userId: user.id,
seatType: WorkspaceSeatType.Viewer
})
expect(res).to.haveGraphQLErrors('cannot have a seat of type')
expect(res.data?.workspaceMutations.updateSeatType).to.not.be.ok
})
it.skip('should upgrade a workspace seat and reconcile subscription', 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)
const oldSeat = await getWorkspaceUserSeat({
workspaceId: testWorkspace1.id,
userId: user.id
})
expect(oldSeat?.type).to.eq(WorkspaceSeatType.Viewer)
const { args, length: reconciledTimes } = StripeClientMock.hijackFactoryFunction(
'reconcileWorkspaceSubscriptionFactory',
async () => {}
)
const res = await updateSeatType({
workspaceId: testWorkspace1.id,
userId: user.id,
seatType: WorkspaceSeatType.Editor
})
expect(res).to.not.haveGraphQLErrors()
expect(
res.data?.workspaceMutations.updateSeatType.team.items.find(
(i) => i.id === user.id
)?.seatType
).to.eq(WorkspaceSeatType.Editor)
expect(reconciledTimes() > 0).to.be.true
const reconcileArgs = args[0][0]
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 reduce project role 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)
await addToStream(userOwnedProject, workspaceAdmin, Roles.Stream.Owner)
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 from user perspective, they should have reduced roles
const res2 = await apollo.execute(
GetProjectCollaboratorsDocument,
{
projectId: userOwnedProject.id
},
{ assertNoErrors: true, authUserId: user.id }
)
expect(res2.data?.project.id).to.eq(userOwnedProject.id)
expect(res2.data?.project.team.length).to.greaterThanOrEqual(1)
const userRes = res2.data?.project.team.find((t) => t.id === user.id)
expect(userRes?.role).to.eq(Roles.Stream.Reviewer)
})
})
})