diff --git a/deno-runtime/deno.jsonc b/deno-runtime/deno.jsonc index e6ab99e27..60edf305a 100644 --- a/deno-runtime/deno.jsonc +++ b/deno-runtime/deno.jsonc @@ -1,5 +1,6 @@ { "imports": { + "@rocket.chat/apps-engine/": "./../src/", "acorn": "npm:acorn@8.10.0", "acorn-walk": "npm:acorn-walk@8.2.0", "astring": "npm:astring@1.8.6" diff --git a/deno-runtime/lib/accessors/_test.ts b/deno-runtime/lib/accessors/_test.ts new file mode 100644 index 000000000..903cc99a4 --- /dev/null +++ b/deno-runtime/lib/accessors/_test.ts @@ -0,0 +1,80 @@ +import { beforeEach, describe, it } from 'https://deno.land/std@0.203.0/testing/bdd.ts'; +import { assertEquals } from "https://deno.land/std@0.203.0/assert/assert_equals.ts"; + +import { AppAccessors, getProxify } from "./mod.ts"; + +describe('AppAccessors', () => { + let appAccessors: AppAccessors; + const proxify = getProxify((r) => Promise.resolve({ + id: Math.random().toString(36).substring(2), + jsonrpc: '2.0', + result: r, + serialize() { + return JSON.stringify(this); + } + })); + + beforeEach(() => { + appAccessors = new AppAccessors(proxify); + }); + + it('creates the correct format for IRead calls', async () => { + const roomRead = appAccessors.getReader().getRoomReader(); + const room = await roomRead.getById('123'); + + assertEquals(room.result, { + params: ['123'], + method: 'accessor:getReader:getRoomReader:getById', + }); + }); + + it('creates the correct format for IEnvironmentRead calls from IRead', async () => { + const reader = appAccessors.getReader().getEnvironmentReader().getEnvironmentVariables(); + const room = await reader.getValueByName('NODE_ENV'); + + assertEquals(room.result, { + params: ['NODE_ENV'], + method: 'accessor:getReader:getEnvironmentReader:getEnvironmentVariables:getValueByName', + }); + }); + + it('creates the correct format for IEvironmentRead calls', async () => { + const envRead = appAccessors.getEnvironmentRead(); + const env = await envRead.getServerSettings().getValueById('123'); + + assertEquals(env.result, { + params: ['123'], + method: 'accessor:getEnvironmentRead:getServerSettings:getValueById', + }); + }); + + it('creates the correct format for IEvironmentWrite calls', async () => { + const envRead = appAccessors.getEnvironmentWrite(); + const env = await envRead.getServerSettings().incrementValue('123', 6); + + assertEquals(env.result, { + params: ['123', 6], + method: 'accessor:getEnvironmentWrite:getServerSettings:incrementValue', + }); + }); + + it('creates the correct format for IConfigurationModify calls', async () => { + const configModify = appAccessors.getConfigurationModify(); + const command = await configModify.slashCommands.modifySlashCommand({ + command: 'test', + i18nDescription: 'test', + i18nParamsExample: 'test', + providesPreview: true, + }); + + assertEquals(command.result, { + params: [{ + command: 'test', + i18nDescription: 'test', + i18nParamsExample: 'test', + providesPreview: true, + }], + method: 'accessor:getConfigurationModify:slashCommands:modifySlashCommand', + }); + }); +}); diff --git a/deno-runtime/lib/accessors/mod.ts b/deno-runtime/lib/accessors/mod.ts index a8bd88df3..8f313a1a1 100644 --- a/deno-runtime/lib/accessors/mod.ts +++ b/deno-runtime/lib/accessors/mod.ts @@ -1,11 +1,169 @@ +// @ts-ignore - this is a hack to make the tests work +import type { IAppAccessors } from '@rocket.chat/apps-engine/definition/accessors/IAppAccessors.ts'; +import type { IEnvironmentWrite } from '@rocket.chat/apps-engine/definition/accessors/IEnvironmentWrite.ts'; +import type { IEnvironmentRead } from '@rocket.chat/apps-engine/definition/accessors/IEnvironmentRead.ts'; +import type { IConfigurationModify } from '@rocket.chat/apps-engine/definition/accessors/IConfigurationModify.ts'; +import type { IRead } from '@rocket.chat/apps-engine/definition/accessors/IRead.ts'; +import type { IModify } from '@rocket.chat/apps-engine/definition/accessors/IModify.ts'; +import type { IPersistence } from '@rocket.chat/apps-engine/definition/accessors/IPersistence.ts'; +import type { IHttp } from '@rocket.chat/apps-engine/definition/accessors/IHttp.ts'; +import type { IConfigurationExtend } from '@rocket.chat/apps-engine/definition/accessors/IConfigurationExtend.ts'; -export function proxify(namespace: string) { - return new Proxy({}, { - get(target: unknown, prop: string): unknown { - return (...args: unknown[]) => { - return {}; +import * as Messenger from '../messenger.ts'; + +export const getProxify = (call: typeof Messenger.sendRequest) => function proxify(namespace: string): T { + return new Proxy( + { __kind: namespace }, // debugging purposes + { + get: + (_target: unknown, prop: string) => + (...params: unknown[]) => + call({ + method: `accessor:${namespace}:${prop}`, + params, + }), + }, + ) as T; +} + +export class AppAccessors { + private defaultAppAccessors?: IAppAccessors; + private environmentRead?: IEnvironmentRead; + private environmentWriter?: IEnvironmentWrite; + private configModifier?: IConfigurationModify; + private configExtender?: IConfigurationExtend; + private reader?: IRead; + private modifier?: IModify; + private persistence?: IPersistence; + private http?: IHttp; + + constructor(private readonly proxify: (n: string) => T) {} + + public getEnvironmentRead(): IEnvironmentRead { + if (!this.environmentRead) { + this.environmentRead = { + getSettings: () => this.proxify('getEnvironmentRead:getSettings'), + getServerSettings: () => this.proxify('getEnvironmentRead:getServerSettings'), + getEnvironmentVariables: () => this.proxify('getEnvironmentRead:getEnvironmentVariables'), + }; + } + + return this.environmentRead; + } + + public getEnvironmentWrite() { + if (!this.environmentWriter) { + this.environmentWriter = { + getSettings: () => this.proxify('getEnvironmentWrite:getSettings'), + getServerSettings: () => this.proxify('getEnvironmentWrite:getServerSettings'), + }; + } + + return this.environmentWriter; + } + + public getConfigurationModify() { + if (!this.configModifier) { + this.configModifier = { + scheduler: this.proxify('getConfigurationModify:scheduler'), + slashCommands: this.proxify('getConfigurationModify:slashCommands'), + serverSettings: this.proxify('getConfigurationModify:serverSettings'), + }; + } + + return this.configModifier; + } + + public getConifgurationExtend() { + if (!this.configExtender) { + this.configExtender = { + ui: this.proxify('getConfigurationExtend:ui'), + api: this.proxify('getConfigurationExtend:api'), + http: this.proxify('getConfigurationExtend:http'), + settings: this.proxify('getConfigurationExtend:settings'), + scheduler: this.proxify('getConfigurationExtend:scheduler'), + slashCommands: this.proxify('getConfigurationExtend:slashCommands'), + externalComponents: this.proxify('getConfigurationExtend:externalComponents'), + videoConfProviders: this.proxify('getConfigurationExtend:videoConfProviders'), + } + } + + return this.configExtender; + } + + public getDefaultAppAccessors() { + if (!this.defaultAppAccessors) { + this.defaultAppAccessors = { + environmentReader: this.getEnvironmentRead(), + environmentWriter: this.getEnvironmentWrite(), + reader: this.getReader(), + http: this.getHttp(), + providedApiEndpoints: this.proxify('providedApiEndpoints'), }; } - }) + + return this.defaultAppAccessors; + } + + public getReader() { + if (!this.reader) { + this.reader = { + getEnvironmentReader: () => ({ + getSettings: () => this.proxify('getReader:getEnvironmentReader:getSettings'), + getServerSettings: () => this.proxify('getReader:getEnvironmentReader:getServerSettings'), + getEnvironmentVariables: () => this.proxify('getReader:getEnvironmentReader:getEnvironmentVariables'), + }), + getMessageReader: () => this.proxify('getReader:getMessageReader'), + getPersistenceReader: () => this.proxify('getReader:getPersistenceReader'), + getRoomReader: () => this.proxify('getReader:getRoomReader'), + getUserReader: () => this.proxify('getReader:getUserReader'), + getNotifier: () => this.proxify('getReader:getNotifier'), + getLivechatReader: () => this.proxify('getReader:getLivechatReader'), + getUploadReader: () => this.proxify('getReader:getUploadReader'), + getCloudWorkspaceReader: () => this.proxify('getReader:getCloudWorkspaceReader'), + getVideoConferenceReader: () => this.proxify('getReader:getVideoConferenceReader'), + getOAuthAppsReader: () => this.proxify('getReader:getOAuthAppsReader'), + getThreadReader: () => this.proxify('getReader:getThreadReader'), + getRoleReader: () => this.proxify('getReader:getRoleReader'), + }; + } + + return this.reader; + } + + public getModifier() { + if (!this.modifier) { + this.modifier = { + getCreator: () => this.proxify('getModifier:getCreator'), // can't be proxy + getUpdater: () => this.proxify('getModifier:getUpdater'), // can't be proxy + getDeleter: () => this.proxify('getModifier:getDeleter'), + getExtender: () => this.proxify('getModifier:getExtender'), // can't be proxy + getNotifier: () => this.proxify('getModifier:getNotifier'), + getUiController: () => this.proxify('getModifier:getUiController'), + getScheduler: () => this.proxify('getModifier:getScheduler'), + getOAuthAppsModifier: () => this.proxify('getModifier:getOAuthAppsModifier'), + getModerationModifier: () => this.proxify('getModifier:getModerationModifier'), + } + } + + return this.modifier; + } + + public getPersistence() { + if (!this.persistence) { + this.persistence = this.proxify('getPersistence'); + } + + return this.persistence; + } + + public getHttp() { + if (!this.http) { + this.http = this.proxify('getHttp'); + } + + return this.http; + } } +export const AppAccessorsInstance = new AppAccessors(getProxify(Messenger.sendRequest.bind(Messenger))); diff --git a/deno-runtime/lib/ast/tests/operations.test.ts b/deno-runtime/lib/ast/tests/operations.test.ts index 5758dc21a..2b00c271f 100644 --- a/deno-runtime/lib/ast/tests/operations.test.ts +++ b/deno-runtime/lib/ast/tests/operations.test.ts @@ -17,7 +17,7 @@ import { SimpleCallExpressionOfFoo, SyncFunctionDeclarationWithAsyncCallExpression, } from './data/ast_blocks.ts'; -import { AnyNode, ArrowFunctionExpression, AssignmentExpression, AwaitExpression, CallExpression, Expression, MethodDefinition, ReturnStatement, VariableDeclaration } from '../../../acorn.d.ts'; +import { AnyNode, ArrowFunctionExpression, AssignmentExpression, AwaitExpression, Expression, MethodDefinition, ReturnStatement, VariableDeclaration } from '../../../acorn.d.ts'; import { assertNotEquals } from 'https://deno.land/std@0.203.0/assert/assert_not_equals.ts'; describe('getFunctionIdentifier', () => { diff --git a/deno-runtime/main.ts b/deno-runtime/main.ts index 1bf162b18..e19a361e3 100644 --- a/deno-runtime/main.ts +++ b/deno-runtime/main.ts @@ -10,7 +10,7 @@ if (!Deno.args.includes('--subprocess')) { import { createRequire } from 'node:module'; import { sanitizeDeprecatedUsage } from "./lib/sanitizeDeprecatedUsage.ts"; -import { proxify } from "./lib/accessors/mod.ts"; +import { AppAccessorsInstance } from "./lib/accessors/mod.ts"; import * as Messenger from "./lib/messenger.ts"; const require = createRequire(import.meta.url); @@ -59,7 +59,7 @@ async function handlInitializeApp({ id, source }: { id: string; source: string } const exports = await wrapAppCode(source)(require); // This is the same naive logic we've been using in the App Compiler const appClass = Object.values(exports)[0] as typeof App; - const app = new appClass({ author: {} }, proxify('logger'), proxify('AppAccessors')); + const app = new appClass({ author: {} }, proxify('logger'), AppAccessorsInstance.getDefaultAppAccessors()); if (typeof app.getName !== 'function') { throw new Error('App must contain a getName function'); diff --git a/src/server/runtime/AppsEngineDenoRuntime.ts b/src/server/runtime/AppsEngineDenoRuntime.ts index bb93072cf..a9400cc1f 100644 --- a/src/server/runtime/AppsEngineDenoRuntime.ts +++ b/src/server/runtime/AppsEngineDenoRuntime.ts @@ -12,6 +12,25 @@ export type AppRuntimeParams = { appSource: string; }; +const ALLOWED_ACCESSOR_METHODS = [ + 'getConfigurationExtend', + 'getEnvironmentRead', + 'getEnvironmentWrite', + 'getConfigurationModify', + 'getReader', + 'getPersistence', + 'getHttp', +] as Array< + keyof Pick< + AppAccessorManager, + 'getConfigurationExtend' | 'getEnvironmentRead' | 'getEnvironmentWrite' | 'getConfigurationModify' | 'getReader' | 'getPersistence' | 'getHttp' + > +>; + +function isValidOrigin(accessor: string): accessor is typeof ALLOWED_ACCESSOR_METHODS[number] { + return ALLOWED_ACCESSOR_METHODS.includes(accessor as any); +} + /** * Resolves the absolute path of the Deno executable * installed by deno-bin. @@ -83,6 +102,7 @@ export class DenoRuntimeSubprocessController extends EventEmitter { } public getState() { + console.log(this.api); return this.state; } @@ -131,8 +151,97 @@ export class DenoRuntimeSubprocessController extends EventEmitter { this.on('ready', this.onReady.bind(this)); } + // Probable should extract this to a separate file + private async handleAccessorMessage({ payload: { method, id, params } }: jsonrpc.IParsedObjectRequest): Promise { + const accessorMethods = method.substring(9).split(':'); // First 9 characters are always 'accessor:' + const managerOrigin = accessorMethods.shift(); + const tailMethodName = accessorMethods.pop(); + + /** + * At this point, the accessorMethods array will contain the path to the accessor from the origin (AppAccessorManager) + * The accessor is the one that contains the actual method the app wants to call + * + * Most of the times, it will take one step from origin to accessor + * For example, for the call AppAccessorManager.getEnvironmentRead().getServerSettings().getValueById() we'll have + * the following: + * + * ``` + * const managerOrigin = 'getEnvironmentRead' + * const tailMethod = 'getValueById' + * const accessorMethods = ['getServerSettings'] + * ``` + * + * But sometimes there can be more steps, like in the following example: + * AppAccessorManager.getReader().getEnvironmentReader().getEnvironmentVariables().getValueByName() + * In this case, we'll have: + * + * ``` + * const managerOrigin = 'getReader' + * const tailMethod = 'getValueByName' + * const accessorMethods = ['getEnvironmentReader', 'getEnvironmentVariables'] + * ``` + **/ + + // Prevent app from trying to get properties from the manager that + // are not intended for public access + if (!isValidOrigin(managerOrigin)) { + throw new Error('Invalid accessor namespace'); + } + + // Need to fix typing of return value + const getAccessorForOrigin = ( + accessorMethods: string[], + managerOrigin: typeof ALLOWED_ACCESSOR_METHODS[number], + accessorManager: AppAccessorManager, + ) => { + const origin = accessorManager[managerOrigin](this.appId); + + // These will need special treatment + if (managerOrigin === 'getConfigurationExtend' || managerOrigin === 'getConfigurationModify') { + return origin[accessorMethods[0] as keyof typeof origin]; + } + + if (managerOrigin === 'getHttp' || managerOrigin === 'getPersistence') { + return origin; + } + + let accessor = origin; + + // Call all intermediary objects to "resolve" the accessor + accessorMethods.forEach((methodName) => { + const method = accessor[methodName as keyof typeof accessor] as unknown; + + if (typeof method !== 'function') { + throw new Error('Invalid accessor method'); + } + + accessor = method.apply(accessor); + }); + + return accessor; + }; + + const accessor = getAccessorForOrigin(accessorMethods, managerOrigin, this.accessors); + + const tailMethod = accessor[tailMethodName as keyof typeof accessor] as unknown; + + if (typeof tailMethod !== 'function') { + throw new Error('Invalid accessor method'); + } + + const result = await tailMethod.apply(accessor, params); + + return jsonrpc.success(id, result); + } + private async handleIncomingMessage(message: jsonrpc.IParsedObjectNotification | jsonrpc.IParsedObjectRequest): Promise { - const { method, id } = message.payload; + const { method } = message.payload; + + if (method.startsWith('accessor:')) { + const result = await this.handleAccessorMessage(message as jsonrpc.IParsedObjectRequest); + + this.deno.stdin.write(result.serialize()); + } switch (method) { case 'ready': @@ -198,21 +307,21 @@ type ExecRequestContext = { export class AppsEngineDenoRuntime { private readonly subprocesses: Record = {}; - // private readonly accessorManager: AppAccessorManager; + private readonly accessorManager: AppAccessorManager; - // private readonly apiManager: AppApiManager; + private readonly apiManager: AppApiManager; - // constructor(manager: AppManager) { - // this.accessorManager = manager.getAccessorManager(); - // this.apiManager = manager.getApiManager(); - // } + constructor(manager: AppManager) { + this.accessorManager = manager.getAccessorManager(); + this.apiManager = manager.getApiManager(); + } public async startRuntimeForApp({ appId, appSource }: AppRuntimeParams, options = { force: false }): Promise { if (appId in this.subprocesses && !options.force) { throw new Error('App already has an associated runtime'); } - this.subprocesses[appId] = new DenoRuntimeSubprocessController(appId, appSource); + this.subprocesses[appId] = new DenoRuntimeSubprocessController(appId, appSource, { accessors: this.accessorManager, api: this.apiManager }); await this.subprocesses[appId].setupApp(); } diff --git a/tests/server/runtime/DenoRuntimeSubprocessController.spec.ts b/tests/server/runtime/DenoRuntimeSubprocessController.spec.ts new file mode 100644 index 000000000..067dcdc2f --- /dev/null +++ b/tests/server/runtime/DenoRuntimeSubprocessController.spec.ts @@ -0,0 +1,97 @@ +import { TestFixture, Setup, SetupFixture, Expect, AsyncTest } from 'alsatian'; + +import { AppAccessorManager, AppApiManager } from '../../../src/server/managers'; +import { TestData, TestInfastructureSetup } from '../../test-data/utilities'; +import { DenoRuntimeSubprocessController } from '../../../src/server/runtime/AppsEngineDenoRuntime'; + +@TestFixture('DenoRuntimeSubprocessController') +export class DenuRuntimeSubprocessControllerTestFixture { + private accessors: AppAccessorManager; + + private api: AppApiManager; + + private simpleAppSource = 'module.exports={ default: new class { constructor() { this.name = "parangarico" } } };console.log("hi from app")'; + + private controller: DenoRuntimeSubprocessController; + + @SetupFixture + public fixture() { + const infrastructure = new TestInfastructureSetup(); + const manager = infrastructure.getMockManager(); + + this.accessors = new AppAccessorManager(manager); + + manager.getAccessorManager = () => this.accessors; + + this.api = new AppApiManager(manager); + + manager.getApiManager = () => this.api; + } + + @Setup + public setup() { + const app = TestData.getMockApp('deno-controller', 'Deno Controller test'); + + this.controller = new DenoRuntimeSubprocessController(app.getID(), this.simpleAppSource, { accessors: this.accessors, api: this.api }); + } + + @AsyncTest('correctly identifies a call to the HTTP accessor') + public async testHttpAccessor() { + // eslint-disable-next-line + const r = await this.controller['handleAccessorMessage']({ + type: 'request' as any, + payload: { + jsonrpc: '2.0', + id: 'test', + method: 'accessor:getHttp:get', + params: ['https://google.com', { content: "{ test: 'test' }" }], + serialize: () => '', + }, + }); + + Expect(r.result).toEqual({ + method: 'get', + url: 'https://google.com', + content: "{ test: 'test' }", + statusCode: 200, + headers: {}, + }); + } + + @AsyncTest('correctly identifies a call to the IRead accessor') + public async testIReadAccessor() { + // eslint-disable-next-line + const { id, result } = await this.controller['handleAccessorMessage']({ + type: 'request' as any, + payload: { + jsonrpc: '2.0', + id: 'test', + method: 'accessor:getReader:getUserReader:getByUsername', + params: ['rocket.cat'], + serialize: () => '', + }, + }); + + Expect(id).toBe('test'); + Expect((result as any).username).toEqual('rocket.cat'); + Expect((result as any).appId).toEqual('deno-controller'); + } + + @AsyncTest('correctly identifies a call to the IEnvironmentReader accessor via IRead') + public async testIEnvironmentReaderAccessor() { + // eslint-disable-next-line + const { id, result } = await this.controller['handleAccessorMessage']({ + type: 'request' as any, + payload: { + jsonrpc: '2.0', + id: 'requestId', + method: 'accessor:getReader:getEnvironmentReader:getServerSettings:getOneById', + params: ['setting test id'], + serialize: () => '', + }, + }); + + Expect(id).toBe('requestId'); + Expect((result as any).id).toEqual('setting test id'); + } +} diff --git a/tests/test-data/bridges/httpBridge.ts b/tests/test-data/bridges/httpBridge.ts index fc08a1ce6..396658f39 100644 --- a/tests/test-data/bridges/httpBridge.ts +++ b/tests/test-data/bridges/httpBridge.ts @@ -4,6 +4,12 @@ import { HttpBridge } from '../../../src/server/bridges'; export class TestsHttpBridge extends HttpBridge { public call(info: IHttpBridgeRequestInfo): Promise { - throw new Error('Method not implemented.'); + return Promise.resolve({ + url: info.url, + method: info.method, + statusCode: 200, + headers: info.request.headers, + content: info.request.content, + }); } } diff --git a/tests/test-data/bridges/serverSettingBridge.ts b/tests/test-data/bridges/serverSettingBridge.ts index 938f2de97..74e6e1f71 100644 --- a/tests/test-data/bridges/serverSettingBridge.ts +++ b/tests/test-data/bridges/serverSettingBridge.ts @@ -1,4 +1,4 @@ -import type { ISetting } from '../../../src/definition/settings'; +import { SettingType, type ISetting } from '../../../src/definition/settings'; import { ServerSettingBridge } from '../../../src/server/bridges'; export class TestsServerSettingBridge extends ServerSettingBridge { @@ -7,7 +7,16 @@ export class TestsServerSettingBridge extends ServerSettingBridge { } public getOneById(id: string, appId: string): Promise { - throw new Error('Method not implemented.'); + return Promise.resolve({ + id, + packageValue: 'packageValue', + value: 'value', + i18nLabel: 'i18nLabel', + i18nDescription: 'i18nDescription', + required: true, + public: true, + type: SettingType.STRING, + }); } public hideGroup(name: string): Promise { diff --git a/tests/test-data/bridges/userBridge.ts b/tests/test-data/bridges/userBridge.ts index c9399f5b6..79cf75b75 100644 --- a/tests/test-data/bridges/userBridge.ts +++ b/tests/test-data/bridges/userBridge.ts @@ -1,4 +1,5 @@ -import type { IUser, UserType } from '../../../src/definition/users'; +import type { IUser } from '../../../src/definition/users'; +import { UserStatusConnection, UserType } from '../../../src/definition/users'; import { UserBridge } from '../../../src/server/bridges'; export class TestsUserBridge extends UserBridge { @@ -7,7 +8,23 @@ export class TestsUserBridge extends UserBridge { } public getByUsername(username: string, appId: string): Promise { - throw new Error('Method not implemented.'); + return Promise.resolve({ + id: 'id', + username, + isEnabled: true, + emails: [], + name: 'name', + roles: [], + type: UserType.USER, + active: true, + appId, + utcOffset: 0, + status: 'offline', + statusConnection: UserStatusConnection.OFFLINE, + lastLoginAt: new Date(), + createdAt: new Date(), + updatedAt: new Date(), + }); } public create(user: Partial): Promise { diff --git a/tests/test-data/utilities.ts b/tests/test-data/utilities.ts index 5f3e714a0..8e4f90987 100644 --- a/tests/test-data/utilities.ts +++ b/tests/test-data/utilities.ts @@ -26,6 +26,14 @@ import type { AppManager } from '../../src/server/AppManager'; import type { AppBridges } from '../../src/server/bridges'; import { ProxiedApp } from '../../src/server/ProxiedApp'; import type { AppLogStorage, AppMetadataStorage, AppSourceStorage, IAppStorageItem } from '../../src/server/storage'; +import type { + AppExternalComponentManager, + AppSchedulerManager, + AppSettingsManager, + AppSlashCommandManager, + AppVideoConfProviderManager, +} from '../../src/server/managers'; +import type { UIActionButtonManager } from '../../src/server/managers/UIActionButtonManager'; export class TestInfastructureSetup { private appStorage: TestsAppStorage; @@ -36,11 +44,43 @@ export class TestInfastructureSetup { private sourceStorage: TestSourceStorage; + private appManager: AppManager; + constructor() { this.appStorage = new TestsAppStorage(); this.logStorage = new TestsAppLogStorage(); this.bridges = new TestsAppBridges(); this.sourceStorage = new TestSourceStorage(); + + this.appManager = { + getBridges: () => { + return this.bridges as AppBridges; + }, + getCommandManager() { + return {} as AppSlashCommandManager; + }, + getExternalComponentManager() { + return {} as AppExternalComponentManager; + }, + getOneById(appId: string): ProxiedApp { + return appId === 'failMePlease' ? undefined : TestData.getMockApp(appId, 'testing'); + }, + getLogStorage(): AppLogStorage { + return new TestsAppLogStorage(); + }, + getSchedulerManager() { + return {} as AppSchedulerManager; + }, + getUIActionButtonManager() { + return {} as UIActionButtonManager; + }, + getVideoConfProviderManager() { + return {} as AppVideoConfProviderManager; + }, + getSettingsManager() { + return {} as AppSettingsManager; + }, + } as AppManager; } public getAppStorage(): AppMetadataStorage { @@ -58,6 +98,10 @@ export class TestInfastructureSetup { public getSourceStorage(): AppSourceStorage { return this.sourceStorage; } + + public getMockManager(): AppManager { + return this.appManager; + } } const date = new Date(); @@ -377,10 +421,10 @@ export class TestData { { status: AppStatus.UNKNOWN } as IAppStorageItem, { getName() { - return 'testing'; + return name; }, getID() { - return 'testing'; + return id; }, getRuntime() { return { runInSandbox: (mod: string) => mod };