Desktop, web, mobile, and the API each decided locally what a tier could do, so a plan change could unlock a feature on one surface and not another. Entitlement checks now live in `@d3ro/core` and are exercised by tests. The shared theme and design-system packages also gain the tokens the new surfaces consume, and the api-client exposes the dictionary and team types the clients now send.
98 lines
3.6 KiB
TypeScript
98 lines
3.6 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
adminRoleAtLeast,
|
|
entitlementFeatures,
|
|
maxEntitlementTier,
|
|
normalizeAdminRole,
|
|
normalizeEntitlementTier,
|
|
resolveEntitlement,
|
|
} from '@d3ro/core/entitlement'
|
|
import { Feature } from '@d3ro/core/types'
|
|
|
|
describe('entitlement tier normalization', () => {
|
|
it('maps cloud and desktop tiers onto the canonical set', () => {
|
|
expect(normalizeEntitlementTier('free')).toBe('free')
|
|
expect(normalizeEntitlementTier('pro')).toBe('pro')
|
|
expect(normalizeEntitlementTier('pro_plus')).toBe('pro_plus')
|
|
expect(normalizeEntitlementTier('team')).toBe('pro_plus')
|
|
expect(normalizeEntitlementTier('enterprise')).toBe('pro_plus')
|
|
})
|
|
|
|
it('falls back to free for unknown values', () => {
|
|
expect(normalizeEntitlementTier(undefined)).toBe('free')
|
|
expect(normalizeEntitlementTier(42)).toBe('free')
|
|
expect(normalizeEntitlementTier('vip')).toBe('free')
|
|
})
|
|
|
|
it('picks the higher tier', () => {
|
|
expect(maxEntitlementTier('free', 'pro')).toBe('pro')
|
|
expect(maxEntitlementTier('pro_plus', 'pro')).toBe('pro_plus')
|
|
expect(maxEntitlementTier('free', 'free')).toBe('free')
|
|
})
|
|
})
|
|
|
|
describe('admin role normalization', () => {
|
|
it('normalizes .NET and Supabase spellings', () => {
|
|
expect(normalizeAdminRole('SuperAdmin')).toBe('superadmin')
|
|
expect(normalizeAdminRole('super_admin')).toBe('superadmin')
|
|
expect(normalizeAdminRole('admin')).toBe('admin')
|
|
expect(normalizeAdminRole('Manager')).toBe('manager')
|
|
expect(normalizeAdminRole('LegacyDisabled')).toBe('user')
|
|
expect(normalizeAdminRole(undefined)).toBe('user')
|
|
})
|
|
|
|
it('compares roles by rank', () => {
|
|
expect(adminRoleAtLeast('admin', 'manager')).toBe(true)
|
|
expect(adminRoleAtLeast('manager', 'admin')).toBe(false)
|
|
expect(adminRoleAtLeast('superadmin', 'superadmin')).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('resolveEntitlement', () => {
|
|
it('takes the highest paid tier across sources', () => {
|
|
const snapshot = resolveEntitlement({ subscriptionTier: 'free', licenseTier: 'pro_plus' })
|
|
expect(snapshot.tier).toBe('pro_plus')
|
|
expect(snapshot.licenseTier).toBe('pro_plus')
|
|
expect(snapshot.adFree).toBe(true)
|
|
expect(snapshot.features).toContain(Feature.TEAM_WORKSPACE)
|
|
})
|
|
|
|
it('does not downgrade a paid subscription with a stale free license', () => {
|
|
const snapshot = resolveEntitlement({ subscriptionTier: 'pro', licenseTier: 'free' })
|
|
expect(snapshot.tier).toBe('pro')
|
|
expect(snapshot.source).toBe('supabase')
|
|
})
|
|
|
|
it('collapses an expired period to free', () => {
|
|
const snapshot = resolveEntitlement({
|
|
subscriptionTier: 'pro_plus',
|
|
expiresAt: Date.now() - 1000,
|
|
})
|
|
expect(snapshot.tier).toBe('free')
|
|
expect(snapshot.adFree).toBe(false)
|
|
expect(snapshot.features).not.toContain(Feature.TEAM_WORKSPACE)
|
|
})
|
|
|
|
it('prefers the Supabase role over the .NET role for admin capabilities', () => {
|
|
const snapshot = resolveEntitlement({ dotnetRole: 'SuperAdmin', supabaseRole: 'manager' })
|
|
expect(snapshot.adminRole).toBe('manager')
|
|
expect(snapshot.adminCapabilities).toEqual({
|
|
canManage: true,
|
|
canAdmin: false,
|
|
canSuperAdmin: false,
|
|
})
|
|
})
|
|
|
|
it('reports no source when nothing is provided', () => {
|
|
const snapshot = resolveEntitlement()
|
|
expect(snapshot.tier).toBe('free')
|
|
expect(snapshot.adminRole).toBe('user')
|
|
expect(snapshot.source).toBe('none')
|
|
})
|
|
|
|
it('grants the team feature only to paid tiers', () => {
|
|
expect(entitlementFeatures('free')).not.toContain(Feature.TEAM_WORKSPACE)
|
|
expect(entitlementFeatures('pro')).toContain(Feature.TEAM_WORKSPACE)
|
|
expect(entitlementFeatures('free')).toContain(Feature.DICTATION)
|
|
})
|
|
})
|