fase(17): licensing module with RSA validation
This commit is contained in:
195
tests/modules/licensing.test.ts
Normal file
195
tests/modules/licensing.test.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user