102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import type { NextFunction, Request, Response } from 'express'
|
|
|
|
vi.mock('../../lib/prisma', () => ({
|
|
prisma: {
|
|
subscription: {
|
|
findUnique: vi.fn(),
|
|
},
|
|
},
|
|
}))
|
|
|
|
import { prisma } from '../../lib/prisma'
|
|
import { requireActiveSubscription, requireFullAccess } from './subscription.entitlement'
|
|
|
|
function createRes() {
|
|
const res = {
|
|
status: vi.fn().mockReturnThis(),
|
|
json: vi.fn().mockReturnThis(),
|
|
}
|
|
return res as unknown as Response & { status: ReturnType<typeof vi.fn>; json: ReturnType<typeof vi.fn> }
|
|
}
|
|
|
|
describe('subscription.entitlement middleware', () => {
|
|
beforeEach(() => vi.clearAllMocks())
|
|
|
|
it('rejects unauthenticated requests before touching subscription storage', async () => {
|
|
const req = {} as Request
|
|
const res = createRes()
|
|
const next = vi.fn() as NextFunction
|
|
|
|
await requireActiveSubscription(req, res, next)
|
|
|
|
expect(res.status).toHaveBeenCalledWith(401)
|
|
expect(res.json).toHaveBeenCalledWith({ error: 'unauthenticated' })
|
|
expect(prisma.subscription.findUnique).not.toHaveBeenCalled()
|
|
expect(next).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('rejects missing or expired subscriptions with a precise entitlement payload', async () => {
|
|
vi.mocked(prisma.subscription.findUnique).mockResolvedValue(null as never)
|
|
const req = { companyId: 'company_1' } as Request
|
|
const res = createRes()
|
|
const next = vi.fn() as NextFunction
|
|
|
|
await requireActiveSubscription(req, res, next)
|
|
|
|
expect(prisma.subscription.findUnique).toHaveBeenCalledWith({ where: { companyId: 'company_1' } })
|
|
expect(res.status).toHaveBeenCalledWith(403)
|
|
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
|
|
error: 'subscription_required',
|
|
subscriptionStatus: 'EXPIRED',
|
|
accessLevel: 'none',
|
|
}))
|
|
expect(next).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('allows limited subscriptions through requireActiveSubscription but annotates the request', async () => {
|
|
vi.mocked(prisma.subscription.findUnique).mockResolvedValue({ status: 'PAST_DUE' } as never)
|
|
const req = { companyId: 'company_1' } as Request & { subscriptionStatus?: string; accessLevel?: string }
|
|
const res = createRes()
|
|
const next = vi.fn() as NextFunction
|
|
|
|
await requireActiveSubscription(req, res, next)
|
|
|
|
expect(req.subscriptionStatus).toBe('PAST_DUE')
|
|
expect(req.accessLevel).toBe('limited')
|
|
expect(next).toHaveBeenCalledOnce()
|
|
expect(res.status).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('requires full access for write-protected actions', async () => {
|
|
vi.mocked(prisma.subscription.findUnique).mockResolvedValue({ status: 'SUSPENDED' } as never)
|
|
const req = { companyId: 'company_1' } as Request
|
|
const res = createRes()
|
|
const next = vi.fn() as NextFunction
|
|
|
|
await requireFullAccess(req, res, next)
|
|
|
|
expect(res.status).toHaveBeenCalledWith(403)
|
|
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
|
|
error: 'insufficient_access',
|
|
subscriptionStatus: 'SUSPENDED',
|
|
accessLevel: 'read_only',
|
|
}))
|
|
expect(next).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('passes active subscriptions through requireFullAccess', async () => {
|
|
vi.mocked(prisma.subscription.findUnique).mockResolvedValue({ status: 'ACTIVE' } as never)
|
|
const req = { companyId: 'company_1' } as Request & { subscriptionStatus?: string; accessLevel?: string }
|
|
const res = createRes()
|
|
const next = vi.fn() as NextFunction
|
|
|
|
await requireFullAccess(req, res, next)
|
|
|
|
expect(req.subscriptionStatus).toBe('ACTIVE')
|
|
expect(req.accessLevel).toBe('full')
|
|
expect(next).toHaveBeenCalledOnce()
|
|
expect(res.status).not.toHaveBeenCalled()
|
|
})
|
|
})
|