import { LicenseManager } from '@n8n_io/license-sdk'; import { InstanceSettings } from 'n8n-core'; import { mock } from 'jest-mock-extended'; import config from '@/config'; import { License } from '@/License'; import { Logger } from '@/Logger'; import { N8N_VERSION } from '@/constants'; import { mockInstance } from '../shared/mocking'; import { OrchestrationService } from '@/services/orchestration.service'; jest.mock('@n8n_io/license-sdk'); const MOCK_SERVER_URL = 'https://server.com/v1'; const MOCK_RENEW_OFFSET = 259200; const MOCK_INSTANCE_ID = 'instance-id'; const MOCK_ACTIVATION_KEY = 'activation-key'; const MOCK_FEATURE_FLAG = 'feat:sharing'; const MOCK_MAIN_PLAN_ID = '1b765dc4-d39d-4ffe-9885-c56dd67c4b26'; describe('License', () => { beforeAll(() => { config.set('license.serverUrl', MOCK_SERVER_URL); config.set('license.autoRenewEnabled', true); config.set('license.autoRenewOffset', MOCK_RENEW_OFFSET); config.set('license.tenantId', 1); }); let license: License; const logger = mockInstance(Logger); const instanceSettings = mockInstance(InstanceSettings, { instanceId: MOCK_INSTANCE_ID }); mockInstance(OrchestrationService); beforeEach(async () => { license = new License(logger, instanceSettings, mock(), mock(), mock()); await license.init(); }); test('initializes license manager', async () => { expect(LicenseManager).toHaveBeenCalledWith({ autoRenewEnabled: true, autoRenewOffset: MOCK_RENEW_OFFSET, offlineMode: false, renewOnInit: true, deviceFingerprint: expect.any(Function), productIdentifier: `n8n-${N8N_VERSION}`, logger, loadCertStr: expect.any(Function), saveCertStr: expect.any(Function), onFeatureChange: expect.any(Function), collectUsageMetrics: expect.any(Function), collectPassthroughData: expect.any(Function), server: MOCK_SERVER_URL, tenantId: 1, }); }); test('initializes license manager for worker', async () => { license = new License(logger, instanceSettings, mock(), mock(), mock()); await license.init('worker'); expect(LicenseManager).toHaveBeenCalledWith({ autoRenewEnabled: false, autoRenewOffset: MOCK_RENEW_OFFSET, offlineMode: true, renewOnInit: false, deviceFingerprint: expect.any(Function), productIdentifier: `n8n-${N8N_VERSION}`, logger, loadCertStr: expect.any(Function), saveCertStr: expect.any(Function), onFeatureChange: expect.any(Function), collectUsageMetrics: expect.any(Function), collectPassthroughData: expect.any(Function), server: MOCK_SERVER_URL, tenantId: 1, }); }); test('attempts to activate license with provided key', async () => { await license.activate(MOCK_ACTIVATION_KEY); expect(LicenseManager.prototype.activate).toHaveBeenCalledWith(MOCK_ACTIVATION_KEY); }); test('renews license', async () => { await license.renew(); expect(LicenseManager.prototype.renew).toHaveBeenCalled(); }); test('check if feature is enabled', () => { license.isFeatureEnabled(MOCK_FEATURE_FLAG); expect(LicenseManager.prototype.hasFeatureEnabled).toHaveBeenCalledWith(MOCK_FEATURE_FLAG); }); test('check if sharing feature is enabled', () => { license.isFeatureEnabled(MOCK_FEATURE_FLAG); expect(LicenseManager.prototype.hasFeatureEnabled).toHaveBeenCalledWith(MOCK_FEATURE_FLAG); }); test('check fetching entitlements', () => { license.getCurrentEntitlements(); expect(LicenseManager.prototype.getCurrentEntitlements).toHaveBeenCalled(); }); test('check fetching feature values', async () => { license.getFeatureValue(MOCK_FEATURE_FLAG); expect(LicenseManager.prototype.getFeatureValue).toHaveBeenCalledWith(MOCK_FEATURE_FLAG); }); test('check management jwt', async () => { license.getManagementJwt(); expect(LicenseManager.prototype.getManagementJwt).toHaveBeenCalled(); }); test('getMainPlan() returns the right entitlement', async () => { // mock entitlements response License.prototype.getCurrentEntitlements = jest.fn().mockReturnValue([ { id: '84a9c852-1349-478d-9ad1-b3f55510e477', productId: '670650f2-72d8-4397-898c-c249906e2cc2', productMetadata: {}, features: {}, featureOverrides: {}, validFrom: new Date(), validTo: new Date(), }, { id: MOCK_MAIN_PLAN_ID, productId: '670650f2-72d8-4397-898c-c249906e2cc2', productMetadata: { terms: { isMainPlan: true, }, }, features: {}, featureOverrides: {}, validFrom: new Date(), validTo: new Date(), }, ]); jest.fn(license.getMainPlan).mockReset(); const mainPlan = license.getMainPlan(); expect(mainPlan?.id).toBe(MOCK_MAIN_PLAN_ID); }); test('getMainPlan() returns undefined if there is no main plan', async () => { // mock entitlements response License.prototype.getCurrentEntitlements = jest.fn().mockReturnValue([ { id: '84a9c852-1349-478d-9ad1-b3f55510e477', productId: '670650f2-72d8-4397-898c-c249906e2cc2', productMetadata: {}, // has no `productMetadata.terms.isMainPlan`! features: {}, featureOverrides: {}, validFrom: new Date(), validTo: new Date(), }, { id: 'c1aae471-c24e-4874-ad88-b97107de486c', productId: '670650f2-72d8-4397-898c-c249906e2cc2', productMetadata: {}, // has no `productMetadata.terms.isMainPlan`! features: {}, featureOverrides: {}, validFrom: new Date(), validTo: new Date(), }, ]); jest.fn(license.getMainPlan).mockReset(); const mainPlan = license.getMainPlan(); expect(mainPlan).toBeUndefined(); }); }); describe('License', () => { beforeEach(() => { config.load(config.default); }); describe('init', () => { describe('in single-main setup', () => { describe('with `license.autoRenewEnabled` enabled', () => { it('should enable renewal', async () => { config.set('multiMainSetup.enabled', false); await new License(mock(), mock(), mock(), mock(), mock()).init(); expect(LicenseManager).toHaveBeenCalledWith( expect.objectContaining({ autoRenewEnabled: true, renewOnInit: true }), ); }); }); describe('with `license.autoRenewEnabled` disabled', () => { it('should disable renewal', async () => { config.set('license.autoRenewEnabled', false); await new License(mock(), mock(), mock(), mock(), mock()).init(); expect(LicenseManager).toHaveBeenCalledWith( expect.objectContaining({ autoRenewEnabled: false, renewOnInit: false }), ); }); }); }); describe('in multi-main setup', () => { describe('with `license.autoRenewEnabled` disabled', () => { test.each(['unset', 'leader', 'follower'])( 'if %s status, should disable removal', async (status) => { config.set('multiMainSetup.enabled', true); config.set('multiMainSetup.instanceType', status); config.set('license.autoRenewEnabled', false); await new License(mock(), mock(), mock(), mock(), mock()).init(); expect(LicenseManager).toHaveBeenCalledWith( expect.objectContaining({ autoRenewEnabled: false, renewOnInit: false }), ); }, ); }); describe('with `license.autoRenewEnabled` enabled', () => { test.each(['unset', 'follower'])('if %s status, should disable removal', async (status) => { config.set('multiMainSetup.enabled', true); config.set('multiMainSetup.instanceType', status); config.set('license.autoRenewEnabled', false); await new License(mock(), mock(), mock(), mock(), mock()).init(); expect(LicenseManager).toHaveBeenCalledWith( expect.objectContaining({ autoRenewEnabled: false, renewOnInit: false }), ); }); it('if leader status, should enable renewal', async () => { config.set('multiMainSetup.enabled', true); config.set('multiMainSetup.instanceType', 'leader'); await new License(mock(), mock(), mock(), mock(), mock()).init(); expect(LicenseManager).toHaveBeenCalledWith( expect.objectContaining({ autoRenewEnabled: true, renewOnInit: true }), ); }); }); }); }); describe('reinit', () => { it('should reinitialize license manager', async () => { const license = new License(mock(), mock(), mock(), mock(), mock()); await license.init(); const initSpy = jest.spyOn(license, 'init'); await license.reinit(); expect(initSpy).toHaveBeenCalledWith('main', true); expect(LicenseManager.prototype.reset).toHaveBeenCalled(); expect(LicenseManager.prototype.initialize).toHaveBeenCalled(); }); }); });