test(gatekeeper): add checkout service tests
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
WorkspacePlanBillingIntervals
|
||||
} from '@/modules/gatekeeper/domain/workspacePricing'
|
||||
import {
|
||||
CheckoutSessionNotFoundError,
|
||||
WorkspaceAlreadyPaidError,
|
||||
WorkspaceCheckoutSessionInProgressError
|
||||
} from '@/modules/gatekeeper/errors/billing'
|
||||
@@ -49,6 +50,7 @@ export const startCheckoutSessionFactory =
|
||||
// get workspace plan, if we're already on a paid plan, do not allow checkout
|
||||
// paid plans should use a subscription modification
|
||||
const existingWorkspacePlan = await getWorkspacePlan({ workspaceId })
|
||||
// it will technically not be possible to not have
|
||||
if (existingWorkspacePlan) {
|
||||
// maybe we can just ignore the plan not existing, cause we're putting it on a plan post checkout
|
||||
switch (existingWorkspacePlan.status) {
|
||||
@@ -117,13 +119,12 @@ export const completeCheckoutSessionFactory =
|
||||
subscriptionId: string
|
||||
}): Promise<void> => {
|
||||
const checkoutSession = await getCheckoutSession({ sessionId })
|
||||
if (!checkoutSession)
|
||||
throw new Error('checkout session is not found this is a bo bo')
|
||||
if (!checkoutSession) throw new CheckoutSessionNotFoundError()
|
||||
|
||||
switch (checkoutSession.paymentStatus) {
|
||||
case 'paid':
|
||||
// if the session is already paid, we do not need to provision anything
|
||||
return
|
||||
throw new WorkspaceAlreadyPaidError()
|
||||
case 'unpaid':
|
||||
break
|
||||
default:
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
import {
|
||||
CheckoutSessionNotFoundError,
|
||||
WorkspaceAlreadyPaidError,
|
||||
WorkspaceCheckoutSessionInProgressError
|
||||
} from '@/modules/gatekeeper/errors/billing'
|
||||
import {
|
||||
completeCheckoutSessionFactory,
|
||||
startCheckoutSessionFactory
|
||||
} from '@/modules/gatekeeper/services/checkout'
|
||||
import { expectToThrow } from '@/test/assertionHelper'
|
||||
import { expect } from 'chai'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import {
|
||||
CheckoutSession,
|
||||
PaidWorkspacePlan,
|
||||
SubscriptionData,
|
||||
WorkspaceSubscription
|
||||
} from '@/modules/gatekeeper/domain/billing'
|
||||
import {
|
||||
PaidWorkspacePlans,
|
||||
WorkspacePlanBillingIntervals
|
||||
} from '@/modules/gatekeeper/domain/workspacePricing'
|
||||
|
||||
describe('checkout @gatekeeper', () => {
|
||||
describe('startCheckoutSessionFactory creates a function, that', () => {
|
||||
it('does not allow checkout for workspace plans, that is in a valid state', async () => {
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const err = await expectToThrow(() =>
|
||||
startCheckoutSessionFactory({
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'pro',
|
||||
status: 'valid',
|
||||
workspaceId
|
||||
}),
|
||||
getWorkspaceCheckoutSession: () => {
|
||||
expect.fail()
|
||||
},
|
||||
countRole: () => {
|
||||
expect.fail()
|
||||
},
|
||||
createCheckoutSession: () => {
|
||||
expect.fail()
|
||||
},
|
||||
saveCheckoutSession: () => {
|
||||
expect.fail()
|
||||
}
|
||||
})({
|
||||
workspaceId,
|
||||
billingInterval: 'monthly',
|
||||
workspacePlan: 'business',
|
||||
workspaceSlug: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
)
|
||||
expect(err.message).to.be.equal(new WorkspaceAlreadyPaidError().message)
|
||||
})
|
||||
it('does not allow checkout for workspace plans, that is in a paymentFailed state', async () => {
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const err = await expectToThrow(() =>
|
||||
startCheckoutSessionFactory({
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'pro',
|
||||
status: 'paymentFailed',
|
||||
workspaceId
|
||||
}),
|
||||
getWorkspaceCheckoutSession: () => {
|
||||
expect.fail()
|
||||
},
|
||||
countRole: () => {
|
||||
expect.fail()
|
||||
},
|
||||
createCheckoutSession: () => {
|
||||
expect.fail()
|
||||
},
|
||||
saveCheckoutSession: () => {
|
||||
expect.fail()
|
||||
}
|
||||
})({
|
||||
workspaceId,
|
||||
billingInterval: 'monthly',
|
||||
workspacePlan: 'business',
|
||||
workspaceSlug: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
)
|
||||
expect(err.message).to.be.equal(new WorkspaceAlreadyPaidError().message)
|
||||
})
|
||||
it('does not allow checkout for a workspace, that already has a checkout session', async () => {
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const err = await expectToThrow(() =>
|
||||
startCheckoutSessionFactory({
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'team',
|
||||
status: 'trial',
|
||||
workspaceId
|
||||
}),
|
||||
getWorkspaceCheckoutSession: async () => ({
|
||||
billingInterval: 'monthly',
|
||||
id: cryptoRandomString({ length: 10 }),
|
||||
paymentStatus: 'unpaid',
|
||||
url: '',
|
||||
workspaceId,
|
||||
workspacePlan: 'business'
|
||||
}),
|
||||
countRole: () => {
|
||||
expect.fail()
|
||||
},
|
||||
createCheckoutSession: () => {
|
||||
expect.fail()
|
||||
},
|
||||
saveCheckoutSession: () => {
|
||||
expect.fail()
|
||||
}
|
||||
})({
|
||||
workspaceId,
|
||||
billingInterval: 'monthly',
|
||||
workspacePlan: 'business',
|
||||
workspaceSlug: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
)
|
||||
expect(err.message).to.be.equal(
|
||||
new WorkspaceCheckoutSessionInProgressError().message
|
||||
)
|
||||
})
|
||||
it('creates and stores a checkout for workspaces that are not on a plan', async () => {
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const workspacePlan: PaidWorkspacePlans = 'pro'
|
||||
const billingInterval: WorkspacePlanBillingIntervals = 'monthly'
|
||||
const checkoutSession: CheckoutSession = {
|
||||
id: cryptoRandomString({ length: 10 }),
|
||||
workspaceId,
|
||||
workspacePlan,
|
||||
url: 'https://example.com',
|
||||
billingInterval,
|
||||
paymentStatus: 'unpaid'
|
||||
}
|
||||
let storedCheckoutSession: CheckoutSession | undefined = undefined
|
||||
const createdCheckoutSession = await startCheckoutSessionFactory({
|
||||
getWorkspacePlan: async () => null,
|
||||
getWorkspaceCheckoutSession: async () => null,
|
||||
countRole: async () => 1,
|
||||
createCheckoutSession: async () => checkoutSession,
|
||||
saveCheckoutSession: async ({ checkoutSession }) => {
|
||||
storedCheckoutSession = checkoutSession
|
||||
}
|
||||
})({
|
||||
workspaceId,
|
||||
billingInterval,
|
||||
workspacePlan,
|
||||
workspaceSlug: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
expect(checkoutSession).deep.equal(storedCheckoutSession)
|
||||
expect(checkoutSession).deep.equal(createdCheckoutSession)
|
||||
})
|
||||
it('creates and stores a checkout for TRIAL and CANCELLED workspaces', async () => {
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const workspacePlan: PaidWorkspacePlans = 'pro'
|
||||
const billingInterval: WorkspacePlanBillingIntervals = 'monthly'
|
||||
const checkoutSession: CheckoutSession = {
|
||||
id: cryptoRandomString({ length: 10 }),
|
||||
workspaceId,
|
||||
workspacePlan,
|
||||
url: 'https://example.com',
|
||||
billingInterval,
|
||||
paymentStatus: 'unpaid'
|
||||
}
|
||||
let storedCheckoutSession: CheckoutSession | undefined = undefined
|
||||
const createdCheckoutSession = await startCheckoutSessionFactory({
|
||||
getWorkspacePlan: async () => null,
|
||||
getWorkspaceCheckoutSession: async () => null,
|
||||
countRole: async () => 1,
|
||||
createCheckoutSession: async () => checkoutSession,
|
||||
saveCheckoutSession: async ({ checkoutSession }) => {
|
||||
storedCheckoutSession = checkoutSession
|
||||
}
|
||||
})({
|
||||
workspaceId,
|
||||
billingInterval,
|
||||
workspacePlan,
|
||||
workspaceSlug: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
expect(checkoutSession).deep.equal(storedCheckoutSession)
|
||||
expect(checkoutSession).deep.equal(createdCheckoutSession)
|
||||
})
|
||||
})
|
||||
describe('completeCheckoutSessionFactory creates a function, that', () => {
|
||||
it('throws a CheckoutSessionNotFound if the checkoutSession is null', async () => {
|
||||
const sessionId = cryptoRandomString({ length: 10 })
|
||||
const subscriptionId = cryptoRandomString({ length: 10 })
|
||||
|
||||
const err = await expectToThrow(async () => {
|
||||
await completeCheckoutSessionFactory({
|
||||
getCheckoutSession: async () => null,
|
||||
updateCheckoutSessionStatus: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
upsertPaidWorkspacePlan: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
getSubscriptionData: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
saveWorkspaceSubscription: async () => {
|
||||
expect.fail()
|
||||
}
|
||||
})({ sessionId, subscriptionId })
|
||||
expect(err.message).to.equal(new CheckoutSessionNotFoundError().message)
|
||||
})
|
||||
})
|
||||
it('throws for already paid checkout sessions', async () => {
|
||||
const sessionId = cryptoRandomString({ length: 10 })
|
||||
const subscriptionId = cryptoRandomString({ length: 10 })
|
||||
|
||||
const err = await expectToThrow(async () => {
|
||||
await completeCheckoutSessionFactory({
|
||||
getCheckoutSession: async () => ({
|
||||
billingInterval: 'monthly',
|
||||
id: sessionId,
|
||||
paymentStatus: 'paid',
|
||||
url: 'https://example.com',
|
||||
workspaceId: cryptoRandomString({ length: 10 }),
|
||||
workspacePlan: 'business'
|
||||
}),
|
||||
updateCheckoutSessionStatus: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
upsertPaidWorkspacePlan: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
getSubscriptionData: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
saveWorkspaceSubscription: async () => {
|
||||
expect.fail()
|
||||
}
|
||||
})({ sessionId, subscriptionId })
|
||||
expect(err.message).to.equal(new WorkspaceAlreadyPaidError().message)
|
||||
})
|
||||
}),
|
||||
(['monthly', 'yearly'] as const).forEach((billingInterval) => {
|
||||
it(`sets the billingCycleEnd end for ${billingInterval} based on the checkoutSession.billingInterval`, async () => {
|
||||
const sessionId = cryptoRandomString({ length: 10 })
|
||||
const subscriptionId = cryptoRandomString({ length: 10 })
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
|
||||
const storedCheckoutSession: CheckoutSession = {
|
||||
billingInterval,
|
||||
id: sessionId,
|
||||
paymentStatus: 'unpaid',
|
||||
url: 'https://example.com',
|
||||
workspaceId,
|
||||
workspacePlan: 'business'
|
||||
}
|
||||
|
||||
let storedWorkspacePlan: PaidWorkspacePlan | undefined = undefined
|
||||
|
||||
const subscriptionData: SubscriptionData = {
|
||||
customerId: cryptoRandomString({ length: 10 }),
|
||||
subscriptionId: cryptoRandomString({ length: 10 }),
|
||||
products: [
|
||||
{
|
||||
priceId: cryptoRandomString({ length: 10 }),
|
||||
productId: cryptoRandomString({ length: 10 }),
|
||||
quantity: 10,
|
||||
subscriptionItemId: cryptoRandomString({ length: 10 })
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
let storedWorkspaceSubscriptionData: WorkspaceSubscription | undefined =
|
||||
undefined
|
||||
|
||||
await completeCheckoutSessionFactory({
|
||||
getCheckoutSession: async () => storedCheckoutSession,
|
||||
updateCheckoutSessionStatus: async ({ paymentStatus }) => {
|
||||
storedCheckoutSession.paymentStatus = paymentStatus
|
||||
},
|
||||
upsertPaidWorkspacePlan: async ({ workspacePlan }) => {
|
||||
storedWorkspacePlan = workspacePlan
|
||||
},
|
||||
getSubscriptionData: async () => subscriptionData,
|
||||
saveWorkspaceSubscription: async ({ workspaceSubscription }) => {
|
||||
storedWorkspaceSubscriptionData = workspaceSubscription
|
||||
}
|
||||
})({ sessionId, subscriptionId })
|
||||
|
||||
expect(storedCheckoutSession.paymentStatus).to.equal('paid')
|
||||
expect(storedWorkspacePlan).to.deep.equal({
|
||||
workspaceId,
|
||||
name: storedCheckoutSession.workspacePlan,
|
||||
status: 'valid'
|
||||
})
|
||||
expect(storedWorkspaceSubscriptionData!.billingInterval).to.equal(
|
||||
storedCheckoutSession.billingInterval
|
||||
)
|
||||
|
||||
expect(storedWorkspaceSubscriptionData!.subscriptionData).to.equal(
|
||||
subscriptionData
|
||||
)
|
||||
let billingCycleEndsIn: number
|
||||
const expectedCycleLength = 1
|
||||
switch (billingInterval) {
|
||||
case 'monthly':
|
||||
billingCycleEndsIn =
|
||||
storedWorkspaceSubscriptionData!.currentBillingCycleEnd.getMonth() -
|
||||
new Date().getMonth()
|
||||
break
|
||||
case 'yearly':
|
||||
billingCycleEndsIn =
|
||||
storedWorkspaceSubscriptionData!.currentBillingCycleEnd.getFullYear() -
|
||||
new Date().getFullYear()
|
||||
break
|
||||
}
|
||||
expect(billingCycleEndsIn).to.be.equal(expectedCycleLength)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user