Files
speckle-server/packages/server/modules/gatekeeper/tests/intergration/billingRepositories.spec.ts
T
Gergő Jedlicska af3857a209 gergo/web 2038 billing graphql api (#3379)
* feat(gatekeeper): add gatekeeper module feature flag

* feat(gatekeeper): add workspace pricing table domain

* feat(gatekeeper): add checkout session creation

* feat(gatekeeper): verify stripe signature

* wip(gatekeeper): checkout callbacks

* feat(gatekeeper): add unlimited and academia plan types

* refactor(envHelper): getStringFromEnv helper

* chore(gatekeeper): add future todos

* feat(gatekeeper): add productId to the subscription domain

* feat(gatekeeper): add in memory repositories

* feat(gatekeeper): add more errors

* feat(gatekeeper): complete checkout session service

* feat(gatekeeper): add stripe client implementation

* feat(gatekeeper): add checkout session completion webhook callback path

* feat(gendo): fix not needing env vars if gendo module is not enabled

* feat(gatekeeper): require a license for billing

* chore(gatekeeper): cleanup before testing

* feat(gatekeeper): subscriptionData parsing model

* ci: add billing integration and gatekeeper modules to test config

* test(gatekeeper): add checkout service tests

* feat(gatekeeper): make completeCheckout callback idempotent properly

* feat(gatekeeper): move to knex based repositories

* test(gatekeeper): billing repository tests

* feat(gatekeeper): add yearly billing cycle toggle

* feat(ci): add stripe integration context to test job

* feat(billingPage): conditionally render the checkout CTAs

* fix(gatekeeper): remove flaky test condition

* feat(helm): add billing integration feature flag

* WIP billing gql api

* feat(gatekeeper): cancel checkout session api

* feat(gatekeeper): handle existing checkout sessions, when trying to create a new one

* feat(gatekeeper): add workspace plans gql api

* feat(gatekeeper): handle cancelation and subscription updates

* fix(gatekeeper): scope initialization

* fix(gatekeeper): eliminate stripe client import sideeffect

* fix(gatekeeper): eliminate stripe client import sideeffect 2

* fix(mainConstants): fitler gatekeeper scopes with feature flag
2024-10-25 10:46:09 +02:00

288 lines
11 KiB
TypeScript

import db from '@/db/knex'
import { WorkspaceSubscription } from '@/modules/gatekeeper/domain/billing'
import {
deleteCheckoutSessionFactory,
getCheckoutSessionFactory,
getWorkspaceCheckoutSessionFactory,
getWorkspacePlanFactory,
saveCheckoutSessionFactory,
upsertWorkspaceSubscriptionFactory,
updateCheckoutSessionStatusFactory,
upsertPaidWorkspacePlanFactory,
getWorkspaceSubscriptionFactory,
getWorkspaceSubscriptionBySubscriptionIdFactory
} from '@/modules/gatekeeper/repositories/billing'
import { upsertWorkspaceFactory } from '@/modules/workspaces/repositories/workspaces'
import { createAndStoreTestWorkspaceFactory } from '@/test/speckle-helpers/workspaces'
import { expect } from 'chai'
import cryptoRandomString from 'crypto-random-string'
const upsertWorkspace = upsertWorkspaceFactory({ db })
const createAndStoreTestWorkspace = createAndStoreTestWorkspaceFactory({
upsertWorkspace
})
const getWorkspacePlan = getWorkspacePlanFactory({ db })
const upsertPaidWorkspacePlan = upsertPaidWorkspacePlanFactory({ db })
const saveCheckoutSession = saveCheckoutSessionFactory({ db })
const deleteCheckoutSession = deleteCheckoutSessionFactory({ db })
const getCheckoutSession = getCheckoutSessionFactory({ db })
const getWorkspaceCheckoutSession = getWorkspaceCheckoutSessionFactory({ db })
const updateCheckoutSessionStatus = updateCheckoutSessionStatusFactory({ db })
const upsertWorkspaceSubscription = upsertWorkspaceSubscriptionFactory({ db })
const getWorkspaceSubscription = getWorkspaceSubscriptionFactory({ db })
const getWorkspaceSubscriptionBySubscriptionId =
getWorkspaceSubscriptionBySubscriptionIdFactory({ db })
describe('billing repositories @gatekeeper', () => {
describe('workspacePlans', () => {
describe('upsertPaidWorkspacePlanFactory creates a function, that', () => {
it('creates a workspacePlan if it does not exist', async () => {
const workspace = await createAndStoreTestWorkspace()
const workspaceId = workspace.id
let storedWorkspacePlan = await getWorkspacePlan({ workspaceId })
expect(storedWorkspacePlan).to.be.null
const workspacePlan = {
name: 'business',
status: 'paymentFailed',
workspaceId
} as const
await upsertPaidWorkspacePlan({
workspacePlan
})
storedWorkspacePlan = await getWorkspacePlan({ workspaceId })
expect(storedWorkspacePlan).deep.equal(workspacePlan)
})
it('updates a workspacePlan name and status if a plan exists', async () => {
const workspace = await createAndStoreTestWorkspace()
const workspaceId = workspace.id
const workspacePlan = {
name: 'business',
status: 'paymentFailed',
workspaceId
} as const
await upsertPaidWorkspacePlan({
workspacePlan
})
let storedWorkspacePlan = await getWorkspacePlan({ workspaceId })
expect(storedWorkspacePlan).deep.equal(workspacePlan)
const planUpdate = { ...workspacePlan, status: 'valid' } as const
await upsertPaidWorkspacePlan({ workspacePlan: planUpdate })
storedWorkspacePlan = await getWorkspacePlan({ workspaceId })
expect(storedWorkspacePlan).deep.equal(planUpdate)
})
})
})
describe('checkoutSessions', () => {
describe('saveCheckoutSessionFactory creates a function that,', () => {
it('stores a checkout session', async () => {
const workspace = await createAndStoreTestWorkspace()
const workspaceId = workspace.id
let storedSession = await getWorkspaceCheckoutSession({ workspaceId })
expect(storedSession).to.be.null
const checkoutSession = {
id: cryptoRandomString({ length: 10 }),
billingInterval: 'monthly',
createdAt: new Date(),
paymentStatus: 'unpaid',
updatedAt: new Date(),
url: 'https://example.com',
workspaceId,
workspacePlan: 'business'
} as const
await saveCheckoutSession({
checkoutSession
})
storedSession = await getCheckoutSession({ sessionId: checkoutSession.id })
expect(storedSession).deep.equal(checkoutSession)
})
})
describe('deleteCheckoutSessionFactory creates a function, that', () => {
it('deletes a checkout session', async () => {
const workspace = await createAndStoreTestWorkspace()
const workspaceId = workspace.id
const checkoutSession = {
id: cryptoRandomString({ length: 10 }),
billingInterval: 'monthly',
createdAt: new Date(),
paymentStatus: 'unpaid',
updatedAt: new Date(),
url: 'https://example.com',
workspaceId,
workspacePlan: 'business'
} as const
await saveCheckoutSession({
checkoutSession
})
let storedSession = await getCheckoutSession({ sessionId: checkoutSession.id })
expect(storedSession).deep.equal(checkoutSession)
await deleteCheckoutSession({ checkoutSessionId: checkoutSession.id })
storedSession = await getCheckoutSession({ sessionId: checkoutSession.id })
expect(storedSession).to.be.null
})
it('does not fail if the checkout session is not found', async () => {
await deleteCheckoutSession({
checkoutSessionId: cryptoRandomString({ length: 10 })
})
})
})
describe('updateCheckoutSessionFactory creates a function, that', () => {
it('updates the session paymentStatus and updatedAt', async () => {
const workspace = await createAndStoreTestWorkspace()
const workspaceId = workspace.id
const checkoutSession = {
id: cryptoRandomString({ length: 10 }),
billingInterval: 'monthly',
createdAt: new Date(),
paymentStatus: 'unpaid',
updatedAt: new Date(),
url: 'https://example.com',
workspaceId,
workspacePlan: 'business'
} as const
await saveCheckoutSession({
checkoutSession
})
let storedSession = await getCheckoutSession({
sessionId: checkoutSession.id
})
expect(storedSession).deep.equal(checkoutSession)
await updateCheckoutSessionStatus({
sessionId: checkoutSession.id,
paymentStatus: 'paid'
})
storedSession = await getCheckoutSession({
sessionId: checkoutSession.id
})
expect(storedSession?.paymentStatus).to.equal('paid')
})
})
describe('getWorkspaceCheckoutSessionFactory creates a function, that', () => {
it('returns null for workspaces without checkoutSessions', async () => {
const workspace = await createAndStoreTestWorkspace()
const workspaceId = workspace.id
const storedSession = await getWorkspaceCheckoutSession({ workspaceId })
expect(storedSession).to.be.null
})
it('gets the checkout session for the workspace', async () => {
const workspace = await createAndStoreTestWorkspace()
const workspaceId = workspace.id
const checkoutSession = {
id: cryptoRandomString({ length: 10 }),
billingInterval: 'monthly',
createdAt: new Date(),
paymentStatus: 'unpaid',
updatedAt: new Date(),
url: 'https://example.com',
workspaceId,
workspacePlan: 'business'
} as const
await saveCheckoutSession({
checkoutSession
})
const storedSession = await getWorkspaceCheckoutSession({ workspaceId })
expect(storedSession).deep.equal(checkoutSession)
})
})
})
describe('workspaceSubscriptions', () => {
describe('upsertWorkspaceSubscription creates a function, that', () => {
it('saves and updates the subscription', async () => {
const workspace = await createAndStoreTestWorkspace()
const workspaceId = workspace.id
const workspaceSubscription: WorkspaceSubscription = {
billingInterval: 'monthly' as const,
createdAt: new Date(),
updatedAt: new Date(),
currentBillingCycleEnd: new Date(),
subscriptionData: {
customerId: cryptoRandomString({ length: 10 }),
status: 'active' as const,
cancelAt: null,
products: [
{
priceId: cryptoRandomString({ length: 10 }),
quantity: 10,
productId: cryptoRandomString({ length: 10 }),
subscriptionItemId: cryptoRandomString({ length: 10 })
}
],
subscriptionId: cryptoRandomString({ length: 10 })
},
workspaceId
}
await upsertWorkspaceSubscription({ workspaceSubscription })
let storedSubscription = await getWorkspaceSubscription({ workspaceId })
expect(storedSubscription).deep.equal(workspaceSubscription)
workspaceSubscription.billingInterval = 'yearly'
workspaceSubscription.subscriptionData.products[0].quantity = 3
await upsertWorkspaceSubscription({ workspaceSubscription })
storedSubscription = await getWorkspaceSubscription({ workspaceId })
expect(storedSubscription).deep.equal(workspaceSubscription)
})
})
describe('getWorkspaceSubscriptionFactory creates a function, that', () => {
it('returns null if the subscription is not found', async () => {
const sub = await getWorkspaceSubscription({
workspaceId: cryptoRandomString({ length: 10 })
})
expect(sub).to.be.null
})
})
describe('getWorkspaceSubscriptionBySubscriptionIdFactory creates a function, that', () => {
it('returns null if the subscription is not found', async () => {
const sub = await getWorkspaceSubscriptionBySubscriptionId({
subscriptionId: cryptoRandomString({ length: 10 })
})
expect(sub).to.be.null
})
it('returns the sub', async () => {
const workspace = await createAndStoreTestWorkspace()
const workspaceId = workspace.id
const workspaceSubscription: WorkspaceSubscription = {
billingInterval: 'monthly' as const,
createdAt: new Date(),
updatedAt: new Date(),
currentBillingCycleEnd: new Date(),
subscriptionData: {
customerId: cryptoRandomString({ length: 10 }),
status: 'active' as const,
cancelAt: null,
products: [
{
priceId: cryptoRandomString({ length: 10 }),
quantity: 10,
productId: cryptoRandomString({ length: 10 }),
subscriptionItemId: cryptoRandomString({ length: 10 })
}
],
subscriptionId: cryptoRandomString({ length: 10 })
},
workspaceId
}
await upsertWorkspaceSubscription({ workspaceSubscription })
const storedSubscription = await getWorkspaceSubscriptionBySubscriptionId({
subscriptionId: workspaceSubscription.subscriptionData.subscriptionId
})
expect(storedSubscription).deep.equal(workspaceSubscription)
})
})
})
})