The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Rocket.Chat/packages/apps-engine/tests/server/AppManager.spec.ts

262 lines
10 KiB

import { AsyncTest, Expect, SetupFixture, SpyOn, Teardown, Test } from 'alsatian';
import { AppManager } from '../../src/server/AppManager';
import { AppBridges } from '../../src/server/bridges';
import { AppCompiler, AppPackageParser } from '../../src/server/compiler';
import {
AppAccessorManager,
AppApiManager,
AppExternalComponentManager,
AppListenerManager,
AppSettingsManager,
AppSlashCommandManager,
AppVideoConfProviderManager,
AppOutboundCommunicationProviderManager,
} from '../../src/server/managers';
import type { AppLogStorage, AppMetadataStorage, AppSourceStorage } from '../../src/server/storage';
import { SimpleClass, TestData, TestInfastructureSetup } from '../test-data/utilities';
export class AppManagerTestFixture {
private testingInfastructure: TestInfastructureSetup;
@SetupFixture
public setupFixture() {
this.testingInfastructure = new TestInfastructureSetup();
}
@Teardown
public teardown() {
AppManager.Instance = undefined;
}
@Test('Setup of the AppManager')
public setupAppManager() {
const manager = new AppManager({
metadataStorage: this.testingInfastructure.getAppStorage(),
logStorage: this.testingInfastructure.getLogStorage(),
bridges: this.testingInfastructure.getAppBridges(),
sourceStorage: this.testingInfastructure.getSourceStorage(),
});
Expect(manager.getStorage()).toBe(this.testingInfastructure.getAppStorage());
Expect(manager.getLogStorage()).toBe(this.testingInfastructure.getLogStorage());
// NOTE: manager.getBridges() returns a proxy, so they are vlaue equality instead of reference equality
Expect(manager.getBridges()).toEqual(this.testingInfastructure.getAppBridges());
Expect(manager.areAppsLoaded()).toBe(false);
Expect(
() =>
new AppManager({
metadataStorage: {} as AppMetadataStorage,
logStorage: {} as AppLogStorage,
bridges: {} as AppBridges,
sourceStorage: {} as AppSourceStorage,
}),
).toThrowError(Error, 'There is already a valid AppManager instance');
}
@Test('Invalid Storage and Bridge')
public invalidInstancesPassed() {
const invalid = new SimpleClass();
Expect(
() =>
new AppManager({
metadataStorage: invalid as any,
logStorage: invalid as any,
bridges: invalid as any,
sourceStorage: invalid as any,
}),
).toThrowError(Error, 'Invalid instance of the AppMetadataStorage');
Expect(
() =>
new AppManager({
metadataStorage: this.testingInfastructure.getAppStorage(),
logStorage: invalid as any,
bridges: invalid as any,
sourceStorage: invalid as any,
}),
).toThrowError(Error, 'Invalid instance of the AppLogStorage');
Expect(
() =>
new AppManager({
metadataStorage: this.testingInfastructure.getAppStorage(),
logStorage: this.testingInfastructure.getLogStorage(),
bridges: invalid as any,
sourceStorage: invalid as any,
}),
).toThrowError(Error, 'Invalid instance of the AppBridges');
Expect(
() =>
new AppManager({
metadataStorage: this.testingInfastructure.getAppStorage(),
logStorage: this.testingInfastructure.getLogStorage(),
bridges: this.testingInfastructure.getAppBridges(),
sourceStorage: invalid as any,
}),
).toThrowError(Error, 'Invalid instance of the AppSourceStorage');
}
@Test('Ensure Managers are Valid Types')
public verifyManagers() {
const manager = new AppManager({
metadataStorage: this.testingInfastructure.getAppStorage(),
logStorage: this.testingInfastructure.getLogStorage(),
bridges: this.testingInfastructure.getAppBridges(),
sourceStorage: this.testingInfastructure.getSourceStorage(),
});
Expect(manager.getParser() instanceof AppPackageParser).toBe(true);
Expect(manager.getCompiler() instanceof AppCompiler).toBe(true);
Expect(manager.getAccessorManager() instanceof AppAccessorManager).toBe(true);
Expect(manager.getBridges() instanceof AppBridges).toBe(true);
Expect(manager.getListenerManager() instanceof AppListenerManager).toBe(true);
Expect(manager.getCommandManager() instanceof AppSlashCommandManager).toBe(true);
Expect(manager.getExternalComponentManager() instanceof AppExternalComponentManager).toBe(true);
Expect(manager.getApiManager() instanceof AppApiManager).toBe(true);
Expect(manager.getSettingsManager() instanceof AppSettingsManager).toBe(true);
Expect(manager.getVideoConfProviderManager() instanceof AppVideoConfProviderManager).toBe(true);
Expect(manager.getOutboundCommunicationProviderManager() instanceof AppOutboundCommunicationProviderManager).toBe(true);
}
@AsyncTest('Update Apps Marketplace Info - Apps without subscription info are skipped')
public async updateAppsMarketplaceInfoSkipsAppsWithoutSubscriptionInfo() {
const manager = new AppManager({
metadataStorage: this.testingInfastructure.getAppStorage(),
logStorage: this.testingInfastructure.getLogStorage(),
bridges: this.testingInfastructure.getAppBridges(),
sourceStorage: this.testingInfastructure.getSourceStorage(),
});
const appsOverview = TestData.getAppsOverview();
appsOverview[0].latest.subscriptionInfo = undefined; // No subscription info
// Mock the apps Map to return our mock app
(manager as any).apps = new Map([['test-app', TestData.getMockApp(TestData.getAppStorageItem(), manager)]]);
const updatePartialAndReturnDocumentSpy = SpyOn(manager.getStorage(), 'updatePartialAndReturnDocument');
updatePartialAndReturnDocumentSpy.andReturn(Promise.resolve());
// Should not throw and complete successfully
await manager.updateAppsMarketplaceInfo(appsOverview);
Expect(updatePartialAndReturnDocumentSpy).not.toHaveBeenCalled();
}
@AsyncTest('Update Apps Marketplace Info - Apps not found in manager are skipped')
public async updateAppsMarketplaceInfoSkipsAppsNotInManager() {
const manager = new AppManager({
metadataStorage: this.testingInfastructure.getAppStorage(),
logStorage: this.testingInfastructure.getLogStorage(),
bridges: this.testingInfastructure.getAppBridges(),
sourceStorage: this.testingInfastructure.getSourceStorage(),
});
const appsOverview = TestData.getAppsOverview();
appsOverview[0].latest.id = 'nonexistent-app'; // App not in manager
// Mock the apps Map to return our mock app
(manager as any).apps = new Map([['test-app', TestData.getMockApp(TestData.getAppStorageItem(), manager)]]);
const updatePartialAndReturnDocumentSpy = SpyOn(manager.getStorage(), 'updatePartialAndReturnDocument');
updatePartialAndReturnDocumentSpy.andReturn(Promise.resolve());
// Should not throw and complete successfully
await manager.updateAppsMarketplaceInfo(appsOverview);
Expect(updatePartialAndReturnDocumentSpy).not.toHaveBeenCalled();
}
@AsyncTest('Update Apps Marketplace Info - Apps with same license are skipped')
public async updateAppsMarketplaceInfoSkipsAppsWithSameLicense() {
const manager = new AppManager({
metadataStorage: this.testingInfastructure.getAppStorage(),
logStorage: this.testingInfastructure.getLogStorage(),
bridges: this.testingInfastructure.getAppBridges(),
sourceStorage: this.testingInfastructure.getSourceStorage(),
});
const sameLicenseData = 'same-license-data';
const existingSubscriptionInfo = TestData.getMarketplaceSubscriptionInfo({
license: { license: sameLicenseData, version: 1, expireDate: new Date('2023-01-01') },
});
const mockStorageItem = TestData.getAppStorageItem({
marketplaceInfo: [TestData.getMarketplaceInfo({ subscriptionInfo: existingSubscriptionInfo })],
});
const mockApp = TestData.getMockApp(mockStorageItem, manager);
// Mock the apps Map to return our mock app
(manager as any).apps = new Map([['test-app', mockApp]]);
const appsOverview = TestData.getAppsOverview(
TestData.getMarketplaceSubscriptionInfo({
license: { license: sameLicenseData, version: 1, expireDate: new Date('2023-01-01') },
}),
);
const updatePartialAndReturnDocumentSpy = SpyOn(manager.getStorage(), 'updatePartialAndReturnDocument');
updatePartialAndReturnDocumentSpy.andReturn(Promise.resolve());
// Should not throw and complete successfully
await manager.updateAppsMarketplaceInfo(appsOverview);
// Verify the subscription info was not updated (should remain the same)
Expect(mockStorageItem.marketplaceInfo[0].subscriptionInfo.seats).toBe(10); // Original value
Expect(updatePartialAndReturnDocumentSpy).not.toHaveBeenCalled();
}
@AsyncTest('Update Apps Marketplace Info - Subscription info is updated and app is signed')
public async updateAppsMarketplaceInfoUpdatesSubscriptionAndSignsApp() {
const manager = new AppManager({
metadataStorage: this.testingInfastructure.getAppStorage(),
logStorage: this.testingInfastructure.getLogStorage(),
bridges: this.testingInfastructure.getAppBridges(),
sourceStorage: this.testingInfastructure.getSourceStorage(),
});
const existingSubscriptionInfo = TestData.getMarketplaceSubscriptionInfo({
license: { license: 'old-license-data', version: 1, expireDate: new Date('2023-01-01') },
});
const newSubscriptionInfo = TestData.getMarketplaceSubscriptionInfo({
seats: 20,
maxSeats: 200,
startDate: '2023-02-01',
periodEnd: '2024-01-31',
license: { license: 'new-license-data', version: 1, expireDate: new Date('2026-01-01') },
});
const mockStorageItem = TestData.getAppStorageItem({
marketplaceInfo: [TestData.getMarketplaceInfo({ subscriptionInfo: existingSubscriptionInfo })],
});
const mockApp = TestData.getMockApp(mockStorageItem, manager);
// eslint-disable-next-line no-return-assign
SpyOn(manager.getSignatureManager(), 'signApp').andReturn(Promise.resolve('signed-app-data'));
SpyOn(mockApp, 'validateLicense').andReturn(Promise.resolve());
const updatePartialAndReturnDocumentSpy = SpyOn(manager.getStorage(), 'updatePartialAndReturnDocument');
updatePartialAndReturnDocumentSpy.andReturn(Promise.resolve(mockStorageItem));
// Mock the apps Map and dependencies
(manager as any).apps = new Map([['test-app', mockApp]]);
const appsOverview = TestData.getAppsOverview(newSubscriptionInfo);
await manager.updateAppsMarketplaceInfo(appsOverview);
const expectedStorageItem = mockApp.getStorageItem();
// Verify the subscription info was updated
Expect(expectedStorageItem.marketplaceInfo[0].subscriptionInfo.seats).toBe(20);
Expect(expectedStorageItem.marketplaceInfo[0].subscriptionInfo.license.license).toBe('new-license-data');
Expect(expectedStorageItem.signature).toBe('signed-app-data');
Expect(updatePartialAndReturnDocumentSpy).toHaveBeenCalled().exactly(1).times;
}
}