import { LicensePlan } from '../../src/modules/licensing/domain/value-objects/LicensePlan'; import { FeatureEntitlement, FREE_FEATURES, PRO_FEATURES, ENTERPRISE_FEATURES, } from '../../src/modules/licensing/domain/value-objects/FeatureEntitlement'; import { License } from '../../src/modules/licensing/domain/entities/License'; import { LicenseService } from '../../src/modules/licensing/application/LicenseService'; import { ILicenseValidator } from '../../src/modules/licensing/domain/ports/ILicenseValidator'; import { Result, Ok, Err } from '../../src/shared/domain/Result'; import { UniqueId } from '../../src/shared/domain/UniqueId'; // ─── LicensePlan ───────────────────────────────────────────────────────────── describe('LicensePlan', () => { it('creates free plan', () => { const plan = LicensePlan.free(); expect(plan.isFree).toBe(true); expect(plan.isPro).toBe(false); expect(plan.isEnterprise).toBe(false); expect(plan.toString()).toBe('free'); }); it('creates pro plan', () => { const plan = LicensePlan.pro(); expect(plan.isPro).toBe(true); expect(plan.isFree).toBe(false); }); it('creates enterprise plan', () => { const plan = LicensePlan.enterprise(); expect(plan.isEnterprise).toBe(true); }); it('parses from string', () => { expect(LicensePlan.fromString('pro').isPro).toBe(true); expect(LicensePlan.fromString('enterprise').isEnterprise).toBe(true); }); it('throws on invalid plan string', () => { expect(() => LicensePlan.fromString('invalid')).toThrow(); }); it('equals comparison works', () => { expect(LicensePlan.free().equals(LicensePlan.free())).toBe(true); expect(LicensePlan.free().equals(LicensePlan.pro())).toBe(false); }); }); // ─── FeatureEntitlement ─────────────────────────────────────────────────────── describe('FeatureEntitlement', () => { it('free features do not include pro features', () => { const free = FeatureEntitlement.forFeatures(FREE_FEATURES); expect(free.has('exploration:basic')).toBe(true); expect(free.has('reports:pdf')).toBe(false); expect(free.has('integrations:slack')).toBe(false); expect(free.has('auth:sso')).toBe(false); }); it('pro features include free features', () => { const pro = FeatureEntitlement.forFeatures(PRO_FEATURES); expect(pro.has('exploration:basic')).toBe(true); expect(pro.has('reports:pdf')).toBe(true); expect(pro.has('integrations:slack')).toBe(true); expect(pro.has('auth:sso')).toBe(false); }); it('enterprise features include all features', () => { const ent = FeatureEntitlement.forFeatures(ENTERPRISE_FEATURES); expect(ent.has('auth:sso')).toBe(true); expect(ent.has('auth:ldap')).toBe(true); expect(ent.has('branding:whitelabel')).toBe(true); }); }); // ─── License entity ────────────────────────────────────────────────────────── describe('License entity', () => { it('createFree returns a free plan license', () => { const license = License.createFree(); expect(license.plan.isFree).toBe(true); expect(license.isExpired).toBe(false); expect(license.isValid).toBe(true); }); it('free license has only free features', () => { const license = License.createFree(); expect(license.hasFeature('exploration:basic')).toBe(true); expect(license.hasFeature('reports:pdf')).toBe(false); }); it('expired license falls back to free features', () => { const license = License.reconstitute( { plan: LicensePlan.pro(), organizationName: 'Test', email: 'test@test.com', expiresAt: new Date('2020-01-01'), issuedAt: new Date('2019-01-01'), signature: 'sig', rawKey: 'key', }, UniqueId.create() ); expect(license.isExpired).toBe(true); expect(license.isValid).toBe(false); expect(license.hasFeature('reports:pdf')).toBe(false); }); it('pro license has pro features', () => { const license = License.reconstitute( { plan: LicensePlan.pro(), organizationName: 'Acme', email: 'admin@acme.com', expiresAt: null, issuedAt: new Date(), signature: 'sig', rawKey: 'key', }, UniqueId.create() ); expect(license.isValid).toBe(true); expect(license.hasFeature('reports:pdf')).toBe(true); expect(license.hasFeature('integrations:slack')).toBe(true); expect(license.hasFeature('auth:sso')).toBe(false); }); }); // ─── LicenseService ─────────────────────────────────────────────────────────── describe('LicenseService', () => { function makeProLicense(): License { return License.reconstitute( { plan: LicensePlan.pro(), organizationName: 'Acme', email: 'admin@acme.com', expiresAt: null, issuedAt: new Date(), signature: 'sig', rawKey: 'key', }, UniqueId.create() ); } it('starts with free license', () => { const validator: ILicenseValidator = { validate: async () => Err('should not be called'), }; const service = new LicenseService(validator); const status = service.getStatus(); expect(status.plan).toBe('free'); expect(status.isValid).toBe(true); }); it('activate with valid key updates current license', async () => { const proLicense = makeProLicense(); const validator: ILicenseValidator = { validate: async () => Ok(proLicense), }; const service = new LicenseService(validator); const result = await service.activate('valid-key'); expect(result.ok).toBe(true); expect(service.getStatus().plan).toBe('pro'); expect(service.hasFeature('reports:pdf')).toBe(true); }); it('activate with invalid key returns error and keeps free license', async () => { const validator: ILicenseValidator = { validate: async () => Err('Invalid license key: signature verification failed'), }; const service = new LicenseService(validator); const result = await service.activate('invalid-key'); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error).toContain('signature verification failed'); } expect(service.getStatus().plan).toBe('free'); }); it('hasFeature checks current license entitlements', () => { const validator: ILicenseValidator = { validate: async () => Err('unused'), }; const service = new LicenseService(validator); expect(service.hasFeature('exploration:basic')).toBe(true); expect(service.hasFeature('auth:sso')).toBe(false); }); });