Skip to content

Commit

Permalink
chore: unit tests (#30504)
Browse files Browse the repository at this point in the history
  • Loading branch information
ggazzo authored Sep 28, 2023
1 parent bee11fe commit e7e84dc
Show file tree
Hide file tree
Showing 22 changed files with 868 additions and 326 deletions.
16 changes: 12 additions & 4 deletions apps/meteor/ee/app/license/server/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,24 @@ settings.watch<string>('Enterprise_License', async (license) => {
return;
}

if (!(await License.setLicense(license))) {
await Settings.updateValueById('Enterprise_License_Status', 'Invalid');
return;
try {
if (!(await License.setLicense(license))) {
await Settings.updateValueById('Enterprise_License_Status', 'Invalid');
return;
}
} catch (_error) {
// do nothing
}

await Settings.updateValueById('Enterprise_License_Status', 'Valid');
});

if (process.env.ROCKETCHAT_LICENSE) {
await License.setLicense(process.env.ROCKETCHAT_LICENSE);
try {
await License.setLicense(process.env.ROCKETCHAT_LICENSE);
} catch (_error) {
// do nothing
}

Meteor.startup(async () => {
if (settings.get('Enterprise_License')) {
Expand Down
6 changes: 5 additions & 1 deletion apps/meteor/ee/app/license/server/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ settings.watch<string>('Site_Url', (value) => {
});

callbacks.add('workspaceLicenseChanged', async (updatedLicense) => {
await License.setLicense(updatedLicense);
try {
await License.setLicense(updatedLicense);
} catch (_error) {
// Ignore
}
});

License.setLicenseLimitCounter('activeUsers', () => Users.getActiveLocalUserCount());
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/ee/server/lib/EnterpriseCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const EnterpriseCheck: ServiceSchema = {
async started(): Promise<void> {
setInterval(async () => {
try {
const hasLicense = await this.broker.call('license.hasLicense', ['scalability']);
const hasLicense = await this.broker.call('license.hasValidLicense', ['scalability']);
if (hasLicense) {
checkFails = 0;
return;
Expand Down
209 changes: 209 additions & 0 deletions ee/packages/license/__tests__/MockedLicenseBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { LicenseImp } from '../src';
import type { ILicenseTag } from '../src/definition/ILicenseTag';
import type { ILicenseV3 } from '../src/definition/ILicenseV3';
import type { LicenseLimit } from '../src/definition/LicenseLimit';
import type { LicenseModule } from '../src/definition/LicenseModule';
import type { LicensePeriod, Timestamp } from '../src/definition/LicensePeriod';
import { encrypt } from '../src/token';

export class MockedLicenseBuilder {
information: {
id?: string;
autoRenew: boolean;
visualExpiration: Timestamp;
notifyAdminsAt?: Timestamp;
notifyUsersAt?: Timestamp;
trial: boolean;
offline: boolean;
createdAt: Timestamp;
grantedBy: {
method: 'manual' | 'self-service' | 'sales' | 'support' | 'reseller';
seller?: string;
};
grantedTo?: {
name?: string;
company?: string;
email?: string;
};
legalText?: string;
notes?: string;
tags?: ILicenseTag[];
};

validation: {
serverUrls: {
value: string;
type: 'url' | 'regex' | 'hash';
}[];

serverVersions?: {
value: string;
}[];

serverUniqueId?: string;
cloudWorkspaceId?: string;
validPeriods: LicensePeriod[];
legalTextAgreement?: {
type: 'required' | 'not-required' | 'accepted';
acceptedVia?: 'cloud';
};

statisticsReport: {
required: boolean;
allowedStaleInDays?: number;
};
};

constructor() {
this.information = {
autoRenew: true,
// expires in 1 year
visualExpiration: new Date(new Date().setFullYear(new Date().getFullYear() + 1)).toISOString(),
// 15 days before expiration
notifyAdminsAt: new Date(new Date().setDate(new Date().getDate() + 15)).toISOString(),
// 30 days before expiration
notifyUsersAt: new Date(new Date().setDate(new Date().getDate() + 30)).toISOString(),
trial: false,
offline: false,
createdAt: new Date().toISOString(),
grantedBy: {
method: 'manual',
seller: 'Rocket.Cat',
},
tags: [
{
name: 'Test',
color: 'blue',
},
],
};

this.validation = {
serverUrls: [
{
value: 'localhost:3000',
type: 'url',
},
],
serverVersions: [
{
value: '3.0.0',
},
],

serverUniqueId: '1234567890',
cloudWorkspaceId: '1234567890',

validPeriods: [
{
invalidBehavior: 'disable_modules',
modules: ['livechat-enterprise'],
validFrom: new Date(new Date().setFullYear(new Date().getFullYear() - 1)).toISOString(),
validUntil: new Date(new Date().setFullYear(new Date().getFullYear() + 1)).toISOString(),
},
],

statisticsReport: {
required: true,
allowedStaleInDays: 30,
},
};
}

public resetValidPeriods(): this {
this.validation.validPeriods = [];
return this;
}

public withValidPeriod(period: LicensePeriod): this {
this.validation.validPeriods.push(period);
return this;
}

public withGrantedTo(grantedTo: { name?: string; company?: string; email?: string }): this {
this.information.grantedTo = grantedTo;
return this;
}

grantedModules: {
module: LicenseModule;
}[];

limits: {
activeUsers?: LicenseLimit[];
guestUsers?: LicenseLimit[];
roomsPerGuest?: LicenseLimit<'prevent_action'>[];
privateApps?: LicenseLimit[];
marketplaceApps?: LicenseLimit[];
monthlyActiveContacts?: LicenseLimit[];
};

cloudMeta?: Record<string, any>;

public withServerUrls(urls: { value: string; type: 'url' | 'regex' | 'hash' }): this {
this.validation.serverUrls = this.validation.serverUrls ?? [];
this.validation.serverUrls.push(urls);
return this;
}

public withServerVersions(versions: { value: string }): this {
this.validation.serverVersions = this.validation.serverVersions ?? [];
this.validation.serverVersions.push(versions);
return this;
}

public withGratedModules(modules: LicenseModule[]): this {
this.grantedModules = this.grantedModules ?? [];
this.grantedModules.push(...modules.map((module) => ({ module })));
return this;
}

withNoGratedModules(modules: LicenseModule[]): this {
this.grantedModules = this.grantedModules ?? [];
this.grantedModules = this.grantedModules.filter(({ module }) => !modules.includes(module));
return this;
}

public withLimits<K extends keyof ILicenseV3['limits']>(key: K, value: ILicenseV3['limits'][K]): this {
this.limits = this.limits ?? {};
this.limits[key] = value;
return this;
}

public build(): ILicenseV3 {
return {
version: '3.0',
information: this.information,
validation: this.validation,
grantedModules: [...new Set(this.grantedModules)],
limits: {
activeUsers: [],
guestUsers: [],
roomsPerGuest: [],
privateApps: [],
marketplaceApps: [],
monthlyActiveContacts: [],
...this.limits,
},
cloudMeta: this.cloudMeta,
};
}

public sign(): Promise<string> {
return encrypt(this.build());
}
}

export const getReadyLicenseManager = async () => {
const license = new LicenseImp();
await license.setWorkspaceUrl('http://localhost:3000');
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;
};
66 changes: 66 additions & 0 deletions ee/packages/license/__tests__/emitter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* @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.withGratedModules(['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.toBeCalled();
await expect(invalidFn).toBeCalledTimes(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).toBeCalledTimes(1);
await expect(invalidFn).toBeCalledTimes(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).toBeCalledTimes(0);
await expect(invalidFn).toBeCalledTimes(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).toBeCalledTimes(1);
});
});
43 changes: 29 additions & 14 deletions ee/packages/license/__tests__/setLicense.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,12 @@ import { LicenseImp } from '../src';
import { DuplicatedLicenseError } from '../src/errors/DuplicatedLicenseError';
import { InvalidLicenseError } from '../src/errors/InvalidLicenseError';
import { NotReadyForValidation } from '../src/errors/NotReadyForValidation';
import { MockedLicenseBuilder, getReadyLicenseManager } from './MockedLicenseBuilder';

// Same license used on ci tasks so no I didnt leak it
const VALID_LICENSE =
'WMa5i+/t/LZbYOj8u3XUkivRhWBtWO6ycUjaZoVAw2DxMfdyBIAa2gMMI4x7Z2BrTZIZhFEImfOxcXcgD0QbXHGBJaMI+eYG+eofnVWi2VA7RWbpvWTULgPFgyJ4UEFeCOzVjcBLTQbmMSam3u0RlekWJkfAO0KnmLtsaEYNNA2rz1U+CLI/CdNGfdqrBu5PZZbGkH0KEzyIZMaykOjzvX+C6vd7fRxh23HecwhkBbqE8eQsCBt2ad0qC4MoVXsDaSOmSzGW+aXjuXt/9zjvrLlsmWQTSlkrEHdNkdywm0UkGxqz3+CP99n0WggUBioUiChjMuNMoceWvDvmxYP9Ml2NpYU7SnfhjmMFyXOah8ofzv8w509Y7XODvQBz+iB4Co9YnF3vT96HDDQyAV5t4jATE+0t37EAXmwjTi3qqyP7DLGK/revl+mlcwJ5kS4zZBsm1E4519FkXQOZSyWRnPdjqvh4mCLqoispZ49wKvklDvjPxCSP9us6cVXLDg7NTJr/4pfxLPOkvv7qCgugDvlDx17bXpQFPSDxmpw66FLzvb5Id0dkWjOzrRYSXb0bFWoUQjtHFzmcpFkyVhOKrQ9zA9+Zm7vXmU9Y2l2dK79EloOuHMSYAqsPEag8GMW6vI/cT4iIjHGGDePKnD0HblvTEKzql11cfT/abf2IiaY=';

const getReadyLicenseManager = async () => {
const license = new LicenseImp();
await license.setWorkspaceUrl('http://localhost:3000');
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;
};

describe('License set license procedures', () => {
describe('Invalid formats', () => {
it('by default it should have no license', async () => {
Expand Down Expand Up @@ -85,4 +73,31 @@ describe('License set license procedures', () => {
await 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);
await expect(license.hasValidLicense()).toBe(true);
});

it('should accept new licenses', async () => {
const license = await getReadyLicenseManager();
const mocked = await new MockedLicenseBuilder();
const oldToken = await mocked.sign();

const newToken = await mocked.withGratedModules(['livechat-enterprise']).sign();

await expect(license.setLicense(oldToken)).resolves.toBe(true);
await expect(license.hasValidLicense()).toBe(true);

await expect(license.hasModule('livechat-enterprise')).toBe(false);

await expect(license.setLicense(newToken)).resolves.toBe(true);
await expect(license.hasValidLicense()).toBe(true);
await expect(license.hasModule('livechat-enterprise')).toBe(true);
});
});
});
11 changes: 11 additions & 0 deletions ee/packages/license/babel.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"presets": ["@babel/preset-typescript"],
"plugins": [
[
"transform-inline-environment-variables",
{
"include": ["LICENSE_PUBLIC_KEY_V3"]
}
]
]
}
Loading

0 comments on commit e7e84dc

Please sign in to comment.