snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
import { LicenseImp } from '../src';
describe('Community Restrictions', () => {
describe('Apps from marketplace', () => {
it('should respect the default if there is no license applied', async () => {
const license = new LicenseImp();
license.setLicenseLimitCounter('marketplaceApps', () => 1);
await expect(await license.shouldPreventAction('marketplaceApps')).toBe(false);
license.setLicenseLimitCounter('marketplaceApps', () => 10);
await expect(await license.shouldPreventAction('marketplaceApps')).toBe(true);
});
});
describe('Private Apps', () => {
it('should respect the default if there is no license applied', async () => {
const license = new LicenseImp();
license.setLicenseLimitCounter('privateApps', () => 0);
await expect(await license.shouldPreventAction('privateApps')).toBe(true);
license.setLicenseLimitCounter('privateApps', () => 10);
await expect(await license.shouldPreventAction('privateApps')).toBe(true);
});
});
describe('Active Users', () => {
it('should respect the default if there is no license applied', async () => {
const license = new LicenseImp();
license.setLicenseLimitCounter('activeUsers', () => 1);
await expect(await license.shouldPreventAction('activeUsers')).toBe(false);
license.setLicenseLimitCounter('activeUsers', () => 10);
await expect(await license.shouldPreventAction('activeUsers')).toBe(false);
license.setLicenseLimitCounter('activeUsers', () => 100000);
await expect(await license.shouldPreventAction('activeUsers')).toBe(false);
});
});
});

View File

@@ -0,0 +1,16 @@
import { LicenseImp } from '../src';
export { MockedLicenseBuilder } from '../src/MockedLicenseBuilder';
export const getReadyLicenseManager = async () => {
const license = new LicenseImp();
await license.setWorkspaceUrl('http://localhost:3000');
license.setLicenseLimitCounter('activeUsers', () => 0);
license.setLicenseLimitCounter('guestUsers', () => 0);
license.setLicenseLimitCounter('roomsPerGuest', async () => 0);
license.setLicenseLimitCounter('privateApps', () => 0);
license.setLicenseLimitCounter('marketplaceApps', () => 0);
license.setLicenseLimitCounter('monthlyActiveContacts', async () => 0);
return license;
};

View File

@@ -0,0 +1,87 @@
/**
* @jest-environment node
*/
import { MockedLicenseBuilder, getReadyLicenseManager } from './MockedLicenseBuilder';
import { applyNewestLicense } from '../src/applyLicense';
const OLDER = new Date('2026-01-01T00:00:00.000Z');
const NEWER = new Date('2026-06-01T00:00:00.000Z');
describe('applyNewestLicense', () => {
it('should apply the provided license when it is newer than the stored one', async () => {
const manager = await getReadyLicenseManager();
const stored = await new MockedLicenseBuilder().withCreatedAt(OLDER).withGrantedModules(['auditing']).sign();
const provided = await new MockedLicenseBuilder().withCreatedAt(NEWER).withGrantedModules(['livechat-enterprise']).sign();
await expect(applyNewestLicense(manager, stored, provided)).resolves.toBe(true);
expect(manager.hasValidLicense()).toBe(true);
expect(manager.hasModule('livechat-enterprise')).toBe(true);
expect(manager.hasModule('auditing')).toBe(false);
});
it('should apply the dated provided license when the stored one has no issue date', async () => {
const manager = await getReadyLicenseManager();
// V2 licenses carry no signature-verified issue date, so they must sort as the oldest
const storedBuilder = new MockedLicenseBuilder().withGrantedModules(['auditing']);
storedBuilder.information.createdAt = 'not-a-date';
const stored = await storedBuilder.sign();
const provided = await new MockedLicenseBuilder().withCreatedAt(NEWER).withGrantedModules(['livechat-enterprise']).sign();
await expect(applyNewestLicense(manager, stored, provided)).resolves.toBe(true);
expect(manager.hasModule('livechat-enterprise')).toBe(true);
expect(manager.hasModule('auditing')).toBe(false);
});
it('should apply the stored license when it is newer than the provided one', async () => {
const manager = await getReadyLicenseManager();
const stored = await new MockedLicenseBuilder().withCreatedAt(NEWER).withGrantedModules(['auditing']).sign();
const provided = await new MockedLicenseBuilder().withCreatedAt(OLDER).withGrantedModules(['livechat-enterprise']).sign();
await expect(applyNewestLicense(manager, stored, provided)).resolves.toBe(true);
expect(manager.hasModule('auditing')).toBe(true);
expect(manager.hasModule('livechat-enterprise')).toBe(false);
});
it('should apply the provided license when there is no stored license', async () => {
const manager = await getReadyLicenseManager();
const provided = await new MockedLicenseBuilder().withCreatedAt(OLDER).withGrantedModules(['livechat-enterprise']).sign();
await expect(applyNewestLicense(manager, '', provided)).resolves.toBe(true);
expect(manager.hasModule('livechat-enterprise')).toBe(true);
});
it('should fall back to the provided license when the newer stored one fails to apply', async () => {
const manager = await getReadyLicenseManager();
const stored = await new MockedLicenseBuilder().withCreatedAt(NEWER).withExpiredDate().withGrantedModules(['auditing']).sign();
const provided = await new MockedLicenseBuilder().withCreatedAt(OLDER).withGrantedModules(['livechat-enterprise']).sign();
await expect(applyNewestLicense(manager, stored, provided)).resolves.toBe(true);
expect(manager.hasModule('livechat-enterprise')).toBe(true);
});
it('should fall back to the stored license when the newer provided one is corrupted', async () => {
const manager = await getReadyLicenseManager();
const stored = await new MockedLicenseBuilder().withCreatedAt(OLDER).withGrantedModules(['auditing']).sign();
const provided = `${await new MockedLicenseBuilder().withCreatedAt(NEWER).sign()}corrupted`;
await expect(applyNewestLicense(manager, stored, provided)).resolves.toBe(true);
expect(manager.hasModule('auditing')).toBe(true);
});
it('should return false when neither license is provided', async () => {
const manager = await getReadyLicenseManager();
await expect(applyNewestLicense(manager, '', '')).resolves.toBe(false);
expect(manager.hasValidLicense()).toBe(false);
});
});

View File

@@ -0,0 +1,359 @@
/**
* @jest-environment node
*/
import { MockedLicenseBuilder, getReadyLicenseManager } from './MockedLicenseBuilder';
describe('Event License behaviors', () => {
it('should call the module as they are enabled/disabled', async () => {
const license = await getReadyLicenseManager();
const validFn = jest.fn();
const invalidFn = jest.fn();
license.onValidFeature('livechat-enterprise', validFn);
license.onInvalidFeature('livechat-enterprise', invalidFn);
const mocked = await new MockedLicenseBuilder();
const oldToken = await mocked.sign();
const newToken = await mocked.withGrantedModules(['livechat-enterprise']).sign();
// apply license
await expect(license.setLicense(oldToken)).resolves.toBe(true);
await expect(license.hasValidLicense()).toBe(true);
await expect(license.hasModule('livechat-enterprise')).toBe(false);
await expect(validFn).not.toHaveBeenCalled();
await expect(invalidFn).toHaveBeenCalledTimes(1);
// apply license containing livechat-enterprise module
validFn.mockClear();
invalidFn.mockClear();
await expect(license.setLicense(newToken)).resolves.toBe(true);
await expect(license.hasValidLicense()).toBe(true);
await expect(license.hasModule('livechat-enterprise')).toBe(true);
await expect(validFn).toHaveBeenCalledTimes(1);
await expect(invalidFn).toHaveBeenCalledTimes(0);
// apply the old license again
validFn.mockClear();
invalidFn.mockClear();
await expect(license.setLicense(oldToken)).resolves.toBe(true);
await expect(license.hasValidLicense()).toBe(true);
await expect(license.hasModule('livechat-enterprise')).toBe(false);
await expect(validFn).toHaveBeenCalledTimes(0);
await expect(invalidFn).toHaveBeenCalledTimes(1);
});
it('should call `onValidateLicense` when a valid license is applied', async () => {
const license = await getReadyLicenseManager();
const fn = jest.fn();
license.onValidateLicense(fn);
const mocked = await new MockedLicenseBuilder();
const token = await mocked.sign();
await expect(license.setLicense(token)).resolves.toBe(true);
await expect(license.hasValidLicense()).toBe(true);
await expect(fn).toHaveBeenCalledTimes(1);
});
describe('behavior:prevent_action event', () => {
it('should emit `behavior:prevent_action` event when the limit is reached', async () => {
const licenseManager = await getReadyLicenseManager();
const fn = jest.fn();
licenseManager.onBehaviorTriggered('prevent_action', fn);
const license = await new MockedLicenseBuilder().withLimits('activeUsers', [
{
max: 10,
behavior: 'prevent_action',
},
]);
await expect(licenseManager.setLicense(await license.sign())).resolves.toBe(true);
licenseManager.setLicenseLimitCounter('activeUsers', () => 10);
await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(true);
await expect(fn).toHaveBeenCalledTimes(1);
await expect(fn).toHaveBeenCalledWith({
reason: 'limit',
limit: 'activeUsers',
});
});
it('should emit `limitReached:activeUsers` event when the limit is reached', async () => {
const licenseManager = await getReadyLicenseManager();
const fn = jest.fn();
licenseManager.onLimitReached('activeUsers', fn);
const license = await new MockedLicenseBuilder().withLimits('activeUsers', [
{
max: 10,
behavior: 'prevent_action',
},
]);
await expect(licenseManager.setLicense(await license.sign())).resolves.toBe(true);
licenseManager.setLicenseLimitCounter('activeUsers', () => 10);
await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(true);
await expect(fn).toHaveBeenCalledTimes(1);
await expect(fn).toHaveBeenCalledWith(undefined);
});
});
/**
* This event is used to sync multiple instances of license manager
* The sync event is triggered when the license is changed, but if the validation is running due to a previous change, no sync should be triggered, avoiding multiple/loops syncs
*/
describe('sync event', () => {
it('should emit `sync` event when the license is changed', async () => {
const licenseManager = await getReadyLicenseManager();
const fn = jest.fn();
licenseManager.onChange(fn);
const license = await new MockedLicenseBuilder().withLimits('activeUsers', [
{
max: 10,
behavior: 'prevent_action',
},
{
max: 20,
behavior: 'invalidate_license',
},
]);
await expect(licenseManager.setLicense(await license.sign())).resolves.toBe(true);
licenseManager.setLicenseLimitCounter('activeUsers', () => 21);
await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(true);
await expect(fn).toHaveBeenCalledTimes(1);
});
it('should not emit `sync` event when the license validation was triggered by a the sync method', async () => {
const licenseManager = await getReadyLicenseManager();
const fn = jest.fn();
licenseManager.onChange(fn);
const license = await new MockedLicenseBuilder().withLimits('activeUsers', [
{
max: 10,
behavior: 'prevent_action',
},
{
max: 20,
behavior: 'invalidate_license',
},
]);
await expect(licenseManager.setLicense(await license.sign())).resolves.toBe(true);
licenseManager.setLicenseLimitCounter('activeUsers', () => 21);
await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(true);
await expect(fn).toHaveBeenCalledTimes(1);
fn.mockClear();
await expect(licenseManager.sync()).resolves.toBe(undefined);
await expect(fn).toHaveBeenCalledTimes(0);
});
});
/**
* this is only called when the prevent_action behavior is triggered for the first time
* it will not be called again until the behavior is toggled
*/
describe('Toggled behaviors', () => {
it('should emit `behaviorToggled:prevent_action` event when the limit is reached once but `behavior:prevent_action` twice', async () => {
const licenseManager = await getReadyLicenseManager();
const fn = jest.fn();
const toggleFn = jest.fn();
licenseManager.onBehaviorTriggered('prevent_action', fn);
licenseManager.onBehaviorToggled('prevent_action', toggleFn);
const license = await new MockedLicenseBuilder().withLimits('activeUsers', [
{
max: 10,
behavior: 'prevent_action',
},
]);
await expect(licenseManager.setLicense(await license.sign())).resolves.toBe(true);
licenseManager.setLicenseLimitCounter('activeUsers', () => 10);
await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(true);
await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(true);
await expect(fn).toHaveBeenCalledTimes(2);
await expect(toggleFn).toHaveBeenCalledTimes(1);
await expect(fn).toHaveBeenCalledWith({
reason: 'limit',
limit: 'activeUsers',
});
});
it('should emit `behaviorToggled:allow_action` event when the limit is not reached once but `behavior:allow_action` twice', async () => {
const licenseManager = await getReadyLicenseManager();
const fn = jest.fn();
const toggleFn = jest.fn();
licenseManager.onBehaviorTriggered('allow_action', fn);
licenseManager.onBehaviorToggled('allow_action', toggleFn);
const license = await new MockedLicenseBuilder().withLimits('activeUsers', [
{
max: 10,
behavior: 'prevent_action',
},
]);
await expect(licenseManager.setLicense(await license.sign())).resolves.toBe(true);
licenseManager.setLicenseLimitCounter('activeUsers', () => 9);
await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(false);
await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(false);
await expect(fn).toHaveBeenCalledTimes(2);
await expect(toggleFn).toHaveBeenCalledTimes(0);
await expect(fn).toHaveBeenCalledWith({
reason: 'limit',
limit: 'activeUsers',
});
});
it('should emit `behaviorToggled:prevent_action` and `behaviorToggled:allow_action` events when the shouldPreventAction function changes the result', async () => {
const licenseManager = await getReadyLicenseManager();
const preventFn = jest.fn();
const preventToggleFn = jest.fn();
const allowFn = jest.fn();
const allowToggleFn = jest.fn();
licenseManager.onBehaviorTriggered('prevent_action', preventFn);
licenseManager.onBehaviorToggled('prevent_action', preventToggleFn);
licenseManager.onBehaviorTriggered('allow_action', allowFn);
licenseManager.onBehaviorToggled('allow_action', allowToggleFn);
const license = await new MockedLicenseBuilder().withLimits('activeUsers', [
{
max: 10,
behavior: 'prevent_action',
},
]);
await expect(licenseManager.setLicense(await license.sign())).resolves.toBe(true);
licenseManager.setLicenseLimitCounter('activeUsers', () => 5);
await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(false);
expect(preventFn).toHaveBeenCalledTimes(0);
expect(preventToggleFn).toHaveBeenCalledTimes(0);
expect(allowFn).toHaveBeenCalledTimes(1);
expect(allowToggleFn).toHaveBeenCalledTimes(0);
preventFn.mockClear();
preventToggleFn.mockClear();
allowFn.mockClear();
allowToggleFn.mockClear();
await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(false);
expect(preventFn).toHaveBeenCalledTimes(0);
expect(preventToggleFn).toHaveBeenCalledTimes(0);
expect(allowFn).toHaveBeenCalledTimes(1);
expect(allowToggleFn).toHaveBeenCalledTimes(0);
licenseManager.setLicenseLimitCounter('activeUsers', () => 10);
preventFn.mockClear();
preventToggleFn.mockClear();
allowFn.mockClear();
allowToggleFn.mockClear();
await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(true);
expect(preventFn).toHaveBeenCalledTimes(1);
expect(preventToggleFn).toHaveBeenCalledTimes(1);
expect(allowFn).toHaveBeenCalledTimes(0);
expect(allowToggleFn).toHaveBeenCalledTimes(0);
preventFn.mockClear();
preventToggleFn.mockClear();
allowFn.mockClear();
allowToggleFn.mockClear();
await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(true);
expect(preventFn).toHaveBeenCalledTimes(1);
expect(preventToggleFn).toHaveBeenCalledTimes(0);
expect(allowFn).toHaveBeenCalledTimes(0);
expect(allowToggleFn).toHaveBeenCalledTimes(0);
licenseManager.setLicenseLimitCounter('activeUsers', () => 5);
preventFn.mockClear();
preventToggleFn.mockClear();
allowFn.mockClear();
allowToggleFn.mockClear();
await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(false);
expect(preventFn).toHaveBeenCalledTimes(0);
expect(preventToggleFn).toHaveBeenCalledTimes(0);
expect(allowFn).toHaveBeenCalledTimes(1);
expect(allowToggleFn).toHaveBeenCalledTimes(1);
});
});
describe('Allow actions', () => {
it('should emit `behavior:allow_action` event when the limit is not reached', async () => {
const licenseManager = await getReadyLicenseManager();
const fn = jest.fn();
const preventFn = jest.fn();
licenseManager.onBehaviorTriggered('allow_action', fn);
licenseManager.onBehaviorTriggered('prevent_action', preventFn);
const license = await new MockedLicenseBuilder().withLimits('activeUsers', [
{
max: 10,
behavior: 'prevent_action',
},
]);
await expect(licenseManager.setLicense(await license.sign())).resolves.toBe(true);
licenseManager.setLicenseLimitCounter('activeUsers', () => 9);
await expect(licenseManager.shouldPreventAction('activeUsers')).resolves.toBe(false);
await expect(fn).toHaveBeenCalledTimes(1);
await expect(preventFn).toHaveBeenCalledTimes(0);
await expect(fn).toHaveBeenCalledWith({
reason: 'limit',
limit: 'activeUsers',
});
});
});
});

View File

@@ -0,0 +1,30 @@
/**
* @jest-environment node
*/
import { MockedLicenseBuilder } from './MockedLicenseBuilder';
import { getLicenseCreatedAt } from '../src/getLicenseCreatedAt';
describe('getLicenseCreatedAt', () => {
it('should return the createdAt of a signed V3 license', async () => {
const createdAt = new Date('2026-01-02T03:04:05.000Z');
const encrypted = await new MockedLicenseBuilder().withCreatedAt(createdAt).sign();
await expect(getLicenseCreatedAt(encrypted)).resolves.toEqual(createdAt);
});
it('should return undefined for an empty license string', async () => {
await expect(getLicenseCreatedAt('')).resolves.toBeUndefined();
await expect(getLicenseCreatedAt(' ')).resolves.toBeUndefined();
});
it('should return undefined for a non-V3 license string', async () => {
await expect(getLicenseCreatedAt('not-a-license')).resolves.toBeUndefined();
});
it('should return undefined for a V3 license with a broken signature', async () => {
const encrypted = await new MockedLicenseBuilder().sign();
await expect(getLicenseCreatedAt(`${encrypted}corrupted`)).resolves.toBeUndefined();
});
});

View File

@@ -0,0 +1,206 @@
/**
* @jest-environment node
*/
import { LicenseImp } from '../src';
import { MockedLicenseBuilder, getReadyLicenseManager } from './MockedLicenseBuilder';
import { DuplicatedLicenseError } from '../src/errors/DuplicatedLicenseError';
import { InvalidLicenseError } from '../src/errors/InvalidLicenseError';
import { NotReadyForValidation } from '../src/errors/NotReadyForValidation';
// Same license used on ci tasks so no I didnt leak it
const VALID_LICENSE =
process.env.ENTERPRISE_LICENSE ||
'MK+bpK5NveUuNlWGaQXGoy+8b74Luet82M3ZGcBB8b5P9Y+m67NEtpW64dc1d5lEWi6d0nFjCjtCMneVD7bKxodz/Cml8URKEo5P7cQb/9wmeT0MzAhYNaRFZlIGkZ3ITF59pDV2u4HZuosEDJikVRwnaJ5ZoU/pOsHSPUPhTyGNIqLeKynODtUpfwDdIKEmHxpf2yVkKjgRiIJmbWjM6A4k+MNNYXWVXHzye7GggqWVg/ZcT7nKU1CCadpLhTJiIrgrrPzil1G5DQ4xnLs3Q2tu2dILSDiW5OYw/ywu2yCMicTjMq4MLL5SXDQJj6WoJzZ54HosbvsDzOXvsdC9gI1CjhPL2uRuvC8XLrzn3vL2UgXnifzD1VrLTtdZ+aSADveqtlzYlRWtqoUFBbNw8o+YVHdhbZGR0beMoAyRbHi5EMpxpad3L+NyztUIT/Uh/IjQ/C2SQZ6jB0GKPBOPxFLN56FNhTGrffLFR++TVoBu0Iquc7kajWkNit3bVbZvbx+oFcVW2PcjQ/+i2jpJjbgtUFUKrTKxGMAXTWoDzIQQ35zNzGAy268IM4Ymp5JmsVEnBOEUkbF9yx6fzkO6xZhpsHf0muklnW0kA+Tlore/TUrBWh1/RwWlQeZlxM5NyWoRM5onQmr/k/4BmObtL1Hpmbk8oMG29z89xtE9y/4=';
describe('License set license procedures', () => {
describe('Invalid formats', () => {
it('should have no license by default', async () => {
const license = new LicenseImp();
expect(license.hasValidLicense()).toBe(false);
expect(license.getLicense()).toBeUndefined();
});
it('should throw an error if the license applied is empty', async () => {
const license = new LicenseImp();
await expect(license.setLicense('')).rejects.toThrow(InvalidLicenseError);
});
it('should throw an error if the license applied is invalid', async () => {
const license = new LicenseImp();
await expect(license.setLicense('invalid')).rejects.toThrow(InvalidLicenseError);
});
});
describe('Invalid periods', () => {
it('should throw an error if the license is expired', async () => {
const license = await getReadyLicenseManager();
const mocked = new MockedLicenseBuilder();
const token = await mocked.withExpiredDate().sign();
await license.setLicense(token);
expect(license.hasValidLicense()).toBe(false);
});
describe('license that is not not started yet is applied', () => {
it('should throw an error if the license is not started yet', async () => {
const license = await getReadyLicenseManager();
const mocked = new MockedLicenseBuilder();
const token = await mocked.withNotStartedDate().sign();
await license.setLicense(token);
expect(license.hasValidLicense()).toBe(false);
});
it('should be allowed to set the same license again if the license is not started yet', async () => {
const license = await getReadyLicenseManager();
const mocked = new MockedLicenseBuilder();
const as = mocked.resetValidPeriods().withNotStartedDate();
const token = await as.sign();
await license.setLicense(token);
expect(license.hasValidLicense()).toBe(false);
// 5 minutes in the future
const mockedData = new Date();
mockedData.setMinutes(mockedData.getMinutes() + 5);
jest.useFakeTimers();
jest.setSystemTime(mockedData);
await license.setLicense(token);
jest.useRealTimers();
expect(license.hasValidLicense()).toBe(true);
});
});
});
it('should throw an error if the license is duplicated', async () => {
const license = await getReadyLicenseManager();
await expect(license.setLicense(VALID_LICENSE)).resolves.toBe(true);
await expect(license.setLicense(VALID_LICENSE)).rejects.toThrow(DuplicatedLicenseError);
});
it('should keep a valid license if a new invalid formatted license is applied', async () => {
const license = await getReadyLicenseManager();
await expect(license.setLicense(VALID_LICENSE)).resolves.toBe(true);
expect(license.hasValidLicense()).toBe(true);
await expect(license.setLicense('invalid')).rejects.toThrow(InvalidLicenseError);
expect(license.hasValidLicense()).toBe(true);
});
describe('Pending cases', () => {
it('should return an error if the license is not ready for validation yet - missing workspace url', async () => {
const license = new LicenseImp();
await expect(license.setLicense(VALID_LICENSE)).rejects.toThrow(NotReadyForValidation);
});
it('should return an error if the license is not ready for validation yet - missing counters', async () => {
const license = new LicenseImp();
await license.setWorkspaceUrl('http://localhost:3000');
expect(license.getWorkspaceUrl()).toBe('localhost:3000');
await expect(license.setLicense(VALID_LICENSE)).rejects.toThrow(NotReadyForValidation);
expect(license.hasValidLicense()).toBe(false);
});
it('should return a valid license if the license is ready for validation', async () => {
const license = await getReadyLicenseManager();
await expect(license.setLicense(VALID_LICENSE)).resolves.toBe(true);
expect(license.hasValidLicense()).toBe(true);
});
});
describe('License V3', () => {
it('should return a valid license if the license is ready for validation', async () => {
const license = await getReadyLicenseManager();
const token = await new MockedLicenseBuilder().sign();
await expect(license.setLicense(token)).resolves.toBe(true);
expect(license.hasValidLicense()).toBe(true);
});
it('should accept new licenses', async () => {
const license = await getReadyLicenseManager();
const mocked = new MockedLicenseBuilder();
const oldToken = await mocked.sign();
const newToken = await mocked.withGrantedModules(['livechat-enterprise', 'chat.rocket.test-addon']).sign();
await expect(license.setLicense(oldToken)).resolves.toBe(true);
expect(license.hasValidLicense()).toBe(true);
expect(license.hasModule('livechat-enterprise')).toBe(false);
expect(license.hasModule('chat.rocket.test-addon')).toBe(false);
await expect(license.setLicense(newToken)).resolves.toBe(true);
expect(license.hasValidLicense()).toBe(true);
expect(license.hasModule('livechat-enterprise')).toBe(true);
expect(license.hasModule('chat.rocket.test-addon')).toBe(true);
});
it('should call a validated event after set a valid license', async () => {
const license = await getReadyLicenseManager();
const validateCallback = jest.fn();
license.onValidateLicense(validateCallback);
await expect(license.setLicense(VALID_LICENSE)).resolves.toBe(true);
expect(license.hasValidLicense()).toBe(true);
expect(validateCallback).toHaveBeenCalledTimes(1);
});
describe('License limits', () => {
describe('invalidate license', () => {
it('should trigger an invalidation event when a license with invalid limits is set after a valid one', async () => {
const invalidationCallback = jest.fn();
const licenseManager = await getReadyLicenseManager();
const mocked = new MockedLicenseBuilder();
const oldToken = await mocked
.withLimits('activeUsers', [
{
max: 10,
behavior: 'invalidate_license',
},
])
.sign();
const newToken = await mocked
.withLimits('activeUsers', [
{
max: 1,
behavior: 'invalidate_license',
},
])
.sign();
licenseManager.onInvalidateLicense(invalidationCallback);
licenseManager.setLicenseLimitCounter('activeUsers', () => 5);
await expect(licenseManager.setLicense(oldToken)).resolves.toBe(true);
expect(licenseManager.hasValidLicense()).toBe(true);
await expect(licenseManager.setLicense(newToken)).resolves.toBe(true);
expect(licenseManager.hasValidLicense()).toBe(false);
expect(invalidationCallback).toHaveBeenCalledTimes(1);
});
});
});
});
});