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
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:
49
ee/packages/license/__tests__/DefaultRestrictions.spec.ts
Normal file
49
ee/packages/license/__tests__/DefaultRestrictions.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
16
ee/packages/license/__tests__/MockedLicenseBuilder.ts
Normal file
16
ee/packages/license/__tests__/MockedLicenseBuilder.ts
Normal 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;
|
||||
};
|
||||
87
ee/packages/license/__tests__/applyNewestLicense.spec.ts
Normal file
87
ee/packages/license/__tests__/applyNewestLicense.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
359
ee/packages/license/__tests__/emitter.spec.ts
Normal file
359
ee/packages/license/__tests__/emitter.spec.ts
Normal 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',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
30
ee/packages/license/__tests__/getLicenseCreatedAt.spec.ts
Normal file
30
ee/packages/license/__tests__/getLicenseCreatedAt.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
206
ee/packages/license/__tests__/setLicense.spec.ts
Normal file
206
ee/packages/license/__tests__/setLicense.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user