Files
upstream-rocketchat/packages/apps/tests/server/AppManager.test.ts
upstream-mirror 30810eecd7
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 2s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

278 lines
10 KiB
TypeScript

import * as assert from 'node:assert';
import { describe, it, afterEach, mock } from 'node:test';
import { AppManager } from '../../src/server/AppManager';
import { AppBridges } from '../../src/server/bridges';
import { AppCompiler, AppPackageParser } from '../../src/server/compiler';
import {
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';
describe('AppManager', () => {
const testingInfastructure = new TestInfastructureSetup();
afterEach(() => {
AppManager.Instance = undefined;
mock.restoreAll();
});
it('Setup of the AppManager', () => {
const manager = new AppManager({
metadataStorage: testingInfastructure.getAppStorage(),
logStorage: testingInfastructure.getLogStorage(),
bridges: testingInfastructure.getAppBridges(),
sourceStorage: testingInfastructure.getSourceStorage(),
tempFilePath: testingInfastructure.getTempFilePath(),
});
assert.strictEqual(manager.getStorage(), testingInfastructure.getAppStorage());
assert.strictEqual(manager.getLogStorage(), testingInfastructure.getLogStorage());
// NOTE: manager.getBridges() returns a proxy, so they are vlaue equality instead of reference equality
assert.deepStrictEqual(manager.getBridges(), testingInfastructure.getAppBridges());
assert.strictEqual(manager.areAppsLoaded(), false);
assert.throws(
() =>
new AppManager({
metadataStorage: {} as AppMetadataStorage,
logStorage: {} as AppLogStorage,
bridges: {} as AppBridges,
sourceStorage: {} as AppSourceStorage,
tempFilePath: 'temp-file-path',
}),
{
name: 'Error',
message: 'There is already a valid AppManager instance',
},
);
});
it('Invalid Storage and Bridge', () => {
const invalid = new SimpleClass();
assert.throws(
() =>
new AppManager({
metadataStorage: invalid as any,
logStorage: invalid as any,
bridges: invalid as any,
sourceStorage: invalid as any,
tempFilePath: 'temp-file-path',
}),
{
name: 'Error',
message: 'Invalid instance of the AppMetadataStorage',
},
);
assert.throws(
() =>
new AppManager({
metadataStorage: testingInfastructure.getAppStorage(),
logStorage: invalid as any,
bridges: invalid as any,
sourceStorage: invalid as any,
tempFilePath: 'temp-file-path',
}),
{
name: 'Error',
message: 'Invalid instance of the AppLogStorage',
},
);
assert.throws(
() =>
new AppManager({
metadataStorage: testingInfastructure.getAppStorage(),
logStorage: testingInfastructure.getLogStorage(),
bridges: invalid as any,
sourceStorage: invalid as any,
tempFilePath: 'temp-file-path',
}),
{
name: 'Error',
message: 'Invalid instance of the AppBridges',
},
);
assert.throws(
() =>
new AppManager({
metadataStorage: testingInfastructure.getAppStorage(),
logStorage: testingInfastructure.getLogStorage(),
bridges: testingInfastructure.getAppBridges(),
sourceStorage: invalid as any,
tempFilePath: testingInfastructure.getTempFilePath(),
}),
{
name: 'Error',
message: 'Invalid instance of the AppSourceStorage',
},
);
});
it('Ensure Managers are Valid Types', () => {
const manager = new AppManager({
metadataStorage: testingInfastructure.getAppStorage(),
logStorage: testingInfastructure.getLogStorage(),
bridges: testingInfastructure.getAppBridges(),
sourceStorage: testingInfastructure.getSourceStorage(),
tempFilePath: testingInfastructure.getTempFilePath(),
});
assert.ok(manager.getParser() instanceof AppPackageParser);
assert.ok(manager.getCompiler() instanceof AppCompiler);
assert.ok(manager.getBridges() instanceof AppBridges);
assert.ok(manager.getListenerManager() instanceof AppListenerManager);
assert.ok(manager.getCommandManager() instanceof AppSlashCommandManager);
assert.ok(manager.getExternalComponentManager() instanceof AppExternalComponentManager);
assert.ok(manager.getApiManager() instanceof AppApiManager);
assert.ok(manager.getSettingsManager() instanceof AppSettingsManager);
assert.ok(manager.getVideoConfProviderManager() instanceof AppVideoConfProviderManager);
assert.ok(manager.getOutboundCommunicationProviderManager() instanceof AppOutboundCommunicationProviderManager);
});
it('Update Apps Marketplace Info - Apps without subscription info are skipped', async () => {
const manager = new AppManager({
metadataStorage: testingInfastructure.getAppStorage(),
logStorage: testingInfastructure.getLogStorage(),
bridges: testingInfastructure.getAppBridges(),
sourceStorage: testingInfastructure.getSourceStorage(),
tempFilePath: testingInfastructure.getTempFilePath(),
});
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 = mock.method(manager.getStorage(), 'updatePartialAndReturnDocument', () => Promise.resolve());
// Should not throw and complete successfully
await manager.updateAppsMarketplaceInfo(appsOverview);
assert.strictEqual(updatePartialAndReturnDocumentSpy.mock.calls.length, 0);
});
it('Update Apps Marketplace Info - Apps not found in manager are skipped', async () => {
const manager = new AppManager({
metadataStorage: testingInfastructure.getAppStorage(),
logStorage: testingInfastructure.getLogStorage(),
bridges: testingInfastructure.getAppBridges(),
sourceStorage: testingInfastructure.getSourceStorage(),
tempFilePath: testingInfastructure.getTempFilePath(),
});
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 = mock.method(manager.getStorage(), 'updatePartialAndReturnDocument', () => Promise.resolve());
// Should not throw and complete successfully
await manager.updateAppsMarketplaceInfo(appsOverview);
assert.strictEqual(updatePartialAndReturnDocumentSpy.mock.calls.length, 0);
});
it('Update Apps Marketplace Info - Apps with same license are skipped', async () => {
const manager = new AppManager({
metadataStorage: testingInfastructure.getAppStorage(),
logStorage: testingInfastructure.getLogStorage(),
bridges: testingInfastructure.getAppBridges(),
sourceStorage: testingInfastructure.getSourceStorage(),
tempFilePath: testingInfastructure.getTempFilePath(),
});
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 = mock.method(manager.getStorage(), 'updatePartialAndReturnDocument', () => Promise.resolve());
// Should not throw and complete successfully
await manager.updateAppsMarketplaceInfo(appsOverview);
// Verify the subscription info was not updated (should remain the same)
assert.strictEqual(mockStorageItem.marketplaceInfo[0].subscriptionInfo.seats, 10); // Original value
assert.strictEqual(updatePartialAndReturnDocumentSpy.mock.calls.length, 0);
});
it('Update Apps Marketplace Info - Subscription info is updated and app is signed', async () => {
const manager = new AppManager({
metadataStorage: testingInfastructure.getAppStorage(),
logStorage: testingInfastructure.getLogStorage(),
bridges: testingInfastructure.getAppBridges(),
sourceStorage: testingInfastructure.getSourceStorage(),
tempFilePath: testingInfastructure.getTempFilePath(),
});
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);
mock.method(manager.getSignatureManager(), 'signApp', () => Promise.resolve('signed-app-data'));
mock.method(mockApp, 'validateLicense', () => Promise.resolve());
const updatePartialAndReturnDocumentSpy = mock.method(manager.getStorage(), 'updatePartialAndReturnDocument', () =>
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
assert.strictEqual(expectedStorageItem.marketplaceInfo[0].subscriptionInfo.seats, 20);
assert.strictEqual(expectedStorageItem.marketplaceInfo[0].subscriptionInfo.license.license, 'new-license-data');
assert.strictEqual(expectedStorageItem.signature, 'signed-app-data');
assert.strictEqual(updatePartialAndReturnDocumentSpy.mock.calls.length, 1);
});
});