196 lines
6.9 KiB
TypeScript
196 lines
6.9 KiB
TypeScript
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);
|
|
});
|
|
});
|