a3644a50c5
* 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
258 lines
7.7 KiB
TypeScript
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)
|
|
})
|
|
})
|
|
})
|