diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 699185914302b..2326ac09356fe 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,6 @@ /packages/* @RocketChat/Architecture /packages/apps-engine/ @RocketChat/apps +/packages/apps/ @RocketChat/apps /packages/core-typings/ @RocketChat/Architecture /packages/rest-typings/ @RocketChat/Architecture @RocketChat/backend /packages/ui-contexts/ @RocketChat/frontend @@ -27,6 +28,8 @@ apps/meteor/server/startup/migrations @RocketChat/Architecture /apps/meteor/packages/rocketchat-livechat @RocketChat/omnichannel /apps/meteor/server/features/EmailInbox @RocketChat/omnichannel +/apps/meteor/ee/server/apps/ @RocketChat/apps +/apps/meteor/ee/tests/unit/server/apps/ @RocketChat/apps /apps/meteor/ee/app/canned-responses @RocketChat/omnichannel /apps/meteor/ee/app/livechat @RocketChat/omnichannel /apps/meteor/ee/app/livechat-enterprise @RocketChat/omnichannel diff --git a/packages/apps/deno-runtime/deno.jsonc b/packages/apps/deno-runtime/deno.jsonc index d19a3daac7d92..da7d145b4d1ed 100644 --- a/packages/apps/deno-runtime/deno.jsonc +++ b/packages/apps/deno-runtime/deno.jsonc @@ -16,7 +16,7 @@ }, "unstable": ["detect-cjs","sloppy-imports"], "tasks": { - "test": "deno test --no-check --allow-read=../../../,/tmp --allow-write=/tmp" + "test": "deno test --no-check --allow-read --allow-write" }, "fmt": { "lineWidth": 160, diff --git a/packages/apps/package.json b/packages/apps/package.json index 18a0a92ca3771..d2f922ab57020 100644 --- a/packages/apps/package.json +++ b/packages/apps/package.json @@ -38,7 +38,6 @@ }, "devDependencies": { "@rocket.chat/tsconfig": "workspace:*", - "@seald-io/nedb": "^4.1.2", "@types/adm-zip": "^0.5.7", "@types/debug": "^4.1.12", "@types/lodash.clonedeep": "^4.5.9", diff --git a/packages/apps/tests/server/runtime/DenoRuntimeSubprocessController.test.ts b/packages/apps/tests/server/runtime/DenoRuntimeSubprocessController.test.ts index df954811aa3ef..d569ca5627d65 100644 --- a/packages/apps/tests/server/runtime/DenoRuntimeSubprocessController.test.ts +++ b/packages/apps/tests/server/runtime/DenoRuntimeSubprocessController.test.ts @@ -2,7 +2,6 @@ import * as assert from 'node:assert'; import * as fs from 'node:fs/promises'; -import * as os from 'node:os'; import * as path from 'node:path'; import { describe, it, afterEach, mock, before, after } from 'node:test'; @@ -39,6 +38,8 @@ describe('DenoRuntimeSubprocessController', () => { const appPackageBuffer = await fs.readFile(path.join(__dirname, '../../test-data/apps/hello-world-test_0.0.1.zip')); appPackage = await manager.getParser().unpackageApp(appPackageBuffer); + await fs.unlink(path.join(manager.getTempFilePath(), 'deno-runtime')).catch(function noop() {}); + appStorageItem = { id: 'hello-world-test', status: AppStatus.MANUALLY_ENABLED, @@ -57,7 +58,7 @@ describe('DenoRuntimeSubprocessController', () => { after( async () => { await controller?.stopApp(); - await fs.unlink(path.join(os.tmpdir(), 'deno-runtime')).catch((reason) => { + await fs.unlink(path.join(manager.getTempFilePath(), 'deno-runtime')).catch((reason) => { console.warn('Failed to delete temporary Deno runtime symlink', reason); }); }, diff --git a/packages/apps/tests/test-data/storage/storage.ts b/packages/apps/tests/test-data/storage/storage.ts index 1e95f5372e41d..156d3492b9d5b 100644 --- a/packages/apps/tests/test-data/storage/storage.ts +++ b/packages/apps/tests/test-data/storage/storage.ts @@ -5,129 +5,106 @@ import type { ISetting } from '@rocket.chat/apps-engine/definition/settings'; import type { IMarketplaceInfo } from '../../../src/server/marketplace'; import type { IAppStorageItem } from '../../../src/server/storage'; import { AppMetadataStorage } from '../../../src/server/storage'; - -const Datastore = require('@seald-io/nedb') as typeof import('@seald-io/nedb').default; +import { AppInstallationSource } from '../../../src/server/storage/IAppStorageItem'; export class TestsAppStorage extends AppMetadataStorage { - private db: InstanceType; - - private static instance: TestsAppStorage; - - public static getInstance(): TestsAppStorage { - if (!TestsAppStorage.instance) { - TestsAppStorage.instance = new TestsAppStorage(); - } - - return TestsAppStorage.instance; - } + private db = new Map(); - private constructor() { - super('nedb'); - this.db = new Datastore({ filename: 'tests/test-data/dbs/apps.nedb', autoload: true }); - this.db.ensureIndex({ fieldName: 'id', unique: true }); + constructor() { + super('in-memory'); } public create(item: IAppStorageItem): Promise { - return new Promise((resolve, reject) => { - item.createdAt = new Date(); - item.updatedAt = new Date(); - - this.db.findOne({ $or: [{ id: item.id }, { 'info.nameSlug': item.info.nameSlug }] }, (err, doc: IAppStorageItem) => { - if (err) { - reject(err); - } else if (doc) { - reject(new Error('App already exists.')); - } else { - this.db.insert(item, (err2, doc2: IAppStorageItem) => { - if (err2) { - reject(err2); - } else { - resolve(doc2); - } - }); - } - }); - }); + for (const [id, value] of this.db) { + if (id === item.id || item.info.nameSlug === value.info.nameSlug) { + return Promise.reject(new Error('App already exists.')); + } + } + + const stored = { ...item, _id: item._id ?? item.id, createdAt: new Date(), updatedAt: new Date() }; + this.db.set(stored.id, stored); + return Promise.resolve(stored); } - public retrieveOne(id: string): Promise { - return new Promise((resolve, reject) => { - this.db.findOne({ id }, (err, doc: IAppStorageItem) => { - if (err) { - reject(err); - } else if (doc) { - resolve(doc); - } else { - reject(new Error(`No App found by the id: ${id}`)); - } - }); - }); + public retrieveOne(id: string): Promise { + return Promise.resolve(this.db.get(id) ?? null); } public retrieveAll(): Promise> { - return new Promise((resolve, reject) => { - this.db.find({}, (err: Error, docs: Array) => { - if (err) { - reject(err); - } else { - const items = new Map(); - - docs.forEach((i) => items.set(i.id, i)); - - resolve(items); - } - }); - }); + return Promise.resolve(new Map(this.db)); } public retrieveAllPrivate(): Promise> { - return new Promise((resolve, reject) => { - this.db.find({ installationSource: 'private' }, (err: Error, docs: Array) => { - if (err) { - reject(err); - } else { - const items = new Map(); - - docs.forEach((i) => items.set(i.id, i)); - - resolve(items); - } - }); - }); + const items = new Map(); + for (const [id, item] of this.db) { + if (item.installationSource === AppInstallationSource.PRIVATE) { + items.set(id, item); + } + } + return Promise.resolve(items); + } + + public clear(): void { + this.db.clear(); } public remove(id: string): Promise<{ success: boolean }> { - return new Promise((resolve, reject) => { - this.db.remove({ id }, (err) => { - if (err) { - reject(err); - } else { - resolve({ success: true }); - } - }); - }); + this.db.delete(id); + return Promise.resolve({ success: true }); } public updatePartialAndReturnDocument( item: Partial, - options?: { unsetPermissionsGranted?: boolean }, + _options?: { unsetPermissionsGranted?: boolean }, ): Promise { - throw new Error('Method not implemented.'); + const lookupId = item.id ?? item._id; + if (!lookupId) { + return Promise.reject(new Error('Cannot update: item has no id.')); + } + + const existing = this.db.get(lookupId); + if (!existing) { + return Promise.reject(new Error(`App not found: ${lookupId}`)); + } + + const updated = { ...existing, ...item, updatedAt: new Date() }; + this.db.set(updated.id, updated); + return Promise.resolve(updated); } - public updateStatus(_id: string, status: AppStatus): Promise { - throw new Error('Method not implemented.'); + public updateStatus(id: string, status: AppStatus): Promise { + const existing = this.db.get(id); + if (!existing) { + return Promise.resolve(false); + } + this.db.set(id, { ...existing, status, updatedAt: new Date() }); + return Promise.resolve(true); } - public updateSetting(_id: string, setting: ISetting): Promise { - throw new Error('Method not implemented.'); + public updateSetting(id: string, setting: ISetting): Promise { + const existing = this.db.get(id); + if (!existing) { + return Promise.resolve(false); + } + this.db.set(id, { ...existing, settings: { ...existing.settings, [setting.id]: setting }, updatedAt: new Date() }); + return Promise.resolve(true); } - public updateAppInfo(_id: string, info: IAppInfo): Promise { - throw new Error('Method not implemented.'); + public updateAppInfo(id: string, info: IAppInfo): Promise { + const existing = this.db.get(id); + if (!existing) { + return Promise.resolve(false); + } + this.db.set(id, { ...existing, info, updatedAt: new Date() }); + return Promise.resolve(true); } - public updateMarketplaceInfo(_id: string, marketplaceInfo: IMarketplaceInfo[]): Promise { - throw new Error('Method not implemented.'); + public updateMarketplaceInfo(id: string, marketplaceInfo: IMarketplaceInfo[]): Promise { + const existing = this.db.get(id); + if (!existing) { + return Promise.resolve(false); + } + this.db.set(id, { ...existing, marketplaceInfo, updatedAt: new Date() }); + return Promise.resolve(true); } } diff --git a/packages/apps/tests/test-data/utilities.ts b/packages/apps/tests/test-data/utilities.ts index df4e0b7da7a2e..1f08fbc4edff4 100644 --- a/packages/apps/tests/test-data/utilities.ts +++ b/packages/apps/tests/test-data/utilities.ts @@ -76,7 +76,7 @@ export class TestInfastructureSetup { private runtimeManager: AppRuntimeManager; constructor() { - this.appStorage = TestsAppStorage.getInstance(); + this.appStorage = new TestsAppStorage(); this.logStorage = new TestsAppLogStorage(); this.bridges = new TestsAppBridges(); this.sourceStorage = new TestSourceStorage();