diff --git a/twake/backend/node/src/core/platform/services/email-pusher/types.ts b/twake/backend/node/src/core/platform/services/email-pusher/types.ts index af17e49e..6dff9e45 100644 --- a/twake/backend/node/src/core/platform/services/email-pusher/types.ts +++ b/twake/backend/node/src/core/platform/services/email-pusher/types.ts @@ -2,7 +2,6 @@ import { Channel } from "../../../../services/channels/entities/channel"; import Company from "../../../../services/user/entities/company"; import Workspace from "../../../../services/workspaces/entities/workspace"; import User from "../../../../services/user/entities/user"; -import { UserObject } from "../../../../services/user/web/types"; export type EmailBuilderDataPayload = { user: User; diff --git a/twake/backend/node/src/services/channels/types.ts b/twake/backend/node/src/services/channels/types.ts index a703d724..0b9e4a28 100644 --- a/twake/backend/node/src/services/channels/types.ts +++ b/twake/backend/node/src/services/channels/types.ts @@ -55,4 +55,4 @@ export type ChannelActivityNotification = { export interface CompanyExecutionContext extends ExecutionContext { company: { id: string }; -} \ No newline at end of file +} diff --git a/twake/backend/node/src/services/console/service.ts b/twake/backend/node/src/services/console/service.ts index 92756d77..afd60e08 100644 --- a/twake/backend/node/src/services/console/service.ts +++ b/twake/backend/node/src/services/console/service.ts @@ -1,5 +1,5 @@ import { DatabaseServiceAPI } from "../../core/platform/services/database/api"; -import { ConsoleOptions, ConsoleType, MergeProgress } from "./types"; +import { ConsoleOptions, ConsoleType } from "./types"; import { ConsoleServiceClient } from "./client-interface"; import { ConsoleClientFactory } from "./client-factory"; import User from "../user/entities/user"; diff --git a/twake/backend/node/src/services/console/web/routes.ts b/twake/backend/node/src/services/console/web/routes.ts index fee4cce3..6fc96220 100644 --- a/twake/backend/node/src/services/console/web/routes.ts +++ b/twake/backend/node/src/services/console/web/routes.ts @@ -1,10 +1,8 @@ import { FastifyInstance, FastifyPluginCallback, FastifyRequest } from "fastify"; import { authenticationSchema, consoleHookSchema, tokenRenewalSchema } from "./schemas"; -import crypto from "crypto"; // import { WorkspaceBaseRequest, WorkspaceUsersBaseRequest, WorkspaceUsersRequest } from "./types"; import { ConsoleController } from "./controller"; import { ConsoleHookBody, ConsoleHookQueryString } from "../types"; -import gr from "../../global-resolver"; const hookUrl = "/hook"; diff --git a/twake/backend/node/src/services/documents/utils.ts b/twake/backend/node/src/services/documents/utils.ts index c12527f4..31f847bf 100644 --- a/twake/backend/node/src/services/documents/utils.ts +++ b/twake/backend/node/src/services/documents/utils.ts @@ -348,7 +348,6 @@ export const getAccessLevel = async ( repository: Repository, context: CompanyExecutionContext & { public_token?: string; twake_tab_token?: string }, ): Promise => { - if (!id || id === "root") return !context?.user?.id ? "none" : (await isCompanyGuest(context)) ? "read" : "manage"; if (id === "trash") @@ -800,4 +799,4 @@ export function isFileType(fileMime: string, fileName: string, requiredExtension const secondaryExtensions = Object.keys(mimes).filter(k => mimes[k] === fileMime); const fileExtensions = [extension, ...secondaryExtensions]; return fileExtensions.some(e => requiredExtensions.includes(e)); -} \ No newline at end of file +} diff --git a/twake/backend/node/src/services/files/services/index.ts b/twake/backend/node/src/services/files/services/index.ts index e8e20bc2..efa1cdaa 100644 --- a/twake/backend/node/src/services/files/services/index.ts +++ b/twake/backend/node/src/services/files/services/index.ts @@ -6,7 +6,6 @@ import { File } from "../entities/file"; import Repository from "../../../../src/core/platform/services/database/services/orm/repository/repository"; import { CompanyExecutionContext } from "../web/types"; import { logger } from "../../../core/platform/framework"; -import _ from "lodash"; import { getDownloadRoute, getThumbnailRoute } from "../web/routes"; import { CrudException, @@ -23,9 +22,7 @@ export class FileServiceImpl { async init(): Promise { try { - await Promise.all([ - (this.repository = await gr.database.getRepository("files", File)), - ]); + await Promise.all([(this.repository = await gr.database.getRepository("files", File))]); } catch (err) { logger.error("Error while initializing files service", err); } @@ -110,7 +107,6 @@ export class FileServiceImpl { entity.upload_data.size = totalUploadedSize; await this.repository.save(entity, context); } - } return entity; diff --git a/twake/backend/node/src/services/notifications/services/bages.ts b/twake/backend/node/src/services/notifications/services/bages.ts index 41676467..4c73b5bd 100644 --- a/twake/backend/node/src/services/notifications/services/bages.ts +++ b/twake/backend/node/src/services/notifications/services/bages.ts @@ -1,261 +1,261 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { - Initializable, - RealtimeDeleted, - RealtimeSaved, - TwakeContext, - TwakeServiceProvider, - } from "../../../core/platform/framework"; - import { ResourcePath } from "../../../core/platform/services/realtime/types"; - import { - CrudException, - DeleteResult, - ExecutionContext, - ListResult, - OperationType, - Pagination, - SaveResult, - } from "../../../core/platform/framework/api/crud-service"; - import { - getUserNotificationBadgeInstance, - UserNotificationBadge, - UserNotificationBadgePrimaryKey, - UserNotificationBadgeType, - } from "../entities"; - import { NotificationExecutionContext } from "../types"; - import { getNotificationRoomName } from "./realtime"; - import Repository from "../../../core/platform/services/database/services/orm/repository/repository"; - import gr from "../../global-resolver"; - import _, { pick, uniq } from "lodash"; - - export class UserNotificationBadgeService implements TwakeServiceProvider, Initializable { - version: "1"; - repository: Repository; - - async init(context: TwakeContext): Promise { - this.repository = await gr.database.getRepository( - UserNotificationBadgeType, - UserNotificationBadge, - ); - - return this; - } - - async get( - pk: UserNotificationBadgePrimaryKey, - context: ExecutionContext, - ): Promise { - return await this.repository.findOne(pk, {}, context); - } - - @RealtimeSaved((badge, context) => { - return [ + Initializable, + RealtimeDeleted, + RealtimeSaved, + TwakeContext, + TwakeServiceProvider, +} from "../../../core/platform/framework"; +import { ResourcePath } from "../../../core/platform/services/realtime/types"; +import { + CrudException, + DeleteResult, + ExecutionContext, + ListResult, + OperationType, + Pagination, + SaveResult, +} from "../../../core/platform/framework/api/crud-service"; +import { + getUserNotificationBadgeInstance, + UserNotificationBadge, + UserNotificationBadgePrimaryKey, + UserNotificationBadgeType, +} from "../entities"; +import { NotificationExecutionContext } from "../types"; +import { getNotificationRoomName } from "./realtime"; +import Repository from "../../../core/platform/services/database/services/orm/repository/repository"; +import gr from "../../global-resolver"; +import _, { pick, uniq } from "lodash"; + +export class UserNotificationBadgeService implements TwakeServiceProvider, Initializable { + version: "1"; + repository: Repository; + + async init(context: TwakeContext): Promise { + this.repository = await gr.database.getRepository( + UserNotificationBadgeType, + UserNotificationBadge, + ); + + return this; + } + + async get( + pk: UserNotificationBadgePrimaryKey, + context: ExecutionContext, + ): Promise { + return await this.repository.findOne(pk, {}, context); + } + + @RealtimeSaved((badge, context) => { + return [ + { + room: ResourcePath.get(getNotificationRoomName(badge.user_id)), + }, + ]; + }) + async save( + badge: UserNotificationBadge, + context: ExecutionContext, + ): Promise> { + //Initiate the digest + await gr.services.notifications.digest.putBadge(badge); + + await this.repository.save(getUserNotificationBadgeInstance(badge), context); + return new SaveResult(UserNotificationBadgeType, badge, OperationType.CREATE); + } + + @RealtimeDeleted((badge, context) => { + return [ + { + room: ResourcePath.get(getNotificationRoomName(badge.user_id)), + }, + ]; + }) + async delete( + pk: UserNotificationBadgePrimaryKey, + context?: NotificationExecutionContext, + ): Promise> { + //Cancel the current digest as we just read the badges + await gr.services.notifications.digest.cancelDigest(pk.company_id, pk.user_id); + + await this.repository.remove(pk as UserNotificationBadge, context); + return new DeleteResult(UserNotificationBadgeType, pk as UserNotificationBadge, true); + } + + list(): Promise> { + throw new Error("Not implemented"); + } + + async listForUserPerCompanies( + user_id: string, + context: ExecutionContext, + ): Promise> { + //We remove all badge from current company as next block will create dupicates + const companies_ids = (await gr.services.companies.getAllForUser(user_id)).map( + gu => gu.group_id, + ); + + let result: UserNotificationBadge[] = []; + let type = ""; + for (const company_id of companies_ids) { + const find = await this.repository.find( { - room: ResourcePath.get(getNotificationRoomName(badge.user_id)), + company_id, + user_id, }, - ]; - }) - async save( - badge: UserNotificationBadge, - context: ExecutionContext, - ): Promise> { - //Initiate the digest - await gr.services.notifications.digest.putBadge(badge); - - await this.repository.save(getUserNotificationBadgeInstance(badge), context); - return new SaveResult(UserNotificationBadgeType, badge, OperationType.CREATE); - } - - @RealtimeDeleted((badge, context) => { - return [ { - room: ResourcePath.get(getNotificationRoomName(badge.user_id)), + pagination: new Pagination("", "1"), }, - ]; - }) - async delete( - pk: UserNotificationBadgePrimaryKey, - context?: NotificationExecutionContext, - ): Promise> { - //Cancel the current digest as we just read the badges - await gr.services.notifications.digest.cancelDigest(pk.company_id, pk.user_id); - - await this.repository.remove(pk as UserNotificationBadge, context); - return new DeleteResult(UserNotificationBadgeType, pk as UserNotificationBadge, true); - } - - list(): Promise> { - throw new Error("Not implemented"); - } - - async listForUserPerCompanies( - user_id: string, - context: ExecutionContext, - ): Promise> { - //We remove all badge from current company as next block will create dupicates - const companies_ids = (await gr.services.companies.getAllForUser(user_id)).map( - gu => gu.group_id, - ); - - let result: UserNotificationBadge[] = []; - let type = ""; - for (const company_id of companies_ids) { - const find = await this.repository.find( - { - company_id, - user_id, - }, - { - pagination: new Pagination("", "1"), - }, - context, - ); - type = find.type; - result = result.concat(find.getEntities()); - } - - const badges = new ListResult(type, result); - await this.ensureBadgesAreReachable(badges, context); - - return badges; - } - - async listForUser( - company_id: string, - user_id: string, - filter: Pick, - context?: ExecutionContext, - ): Promise> { - if (!company_id || !user_id) { - throw CrudException.badRequest("company_id and user_id are required"); - } - - //Cancel the current digest as we just read the badges - await gr.services.notifications.digest.cancelDigest(company_id, user_id); - - const badges = await this.repository.find( - { - ...{ - company_id, - user_id, - }, - ...pick(filter, ["workspace_id", "channel_id", "thread_id"]), - }, - {}, context, ); - - await this.ensureBadgesAreReachable(badges, context); - + type = find.type; + result = result.concat(find.getEntities()); + } + + const badges = new ListResult(type, result); + await this.ensureBadgesAreReachable(badges, context); + + return badges; + } + + async listForUser( + company_id: string, + user_id: string, + filter: Pick, + context?: ExecutionContext, + ): Promise> { + if (!company_id || !user_id) { + throw CrudException.badRequest("company_id and user_id are required"); + } + + //Cancel the current digest as we just read the badges + await gr.services.notifications.digest.cancelDigest(company_id, user_id); + + const badges = await this.repository.find( + { + ...{ + company_id, + user_id, + }, + ...pick(filter, ["workspace_id", "channel_id", "thread_id"]), + }, + {}, + context, + ); + + await this.ensureBadgesAreReachable(badges, context); + + return badges; + } + + // This will ensure we are still in the channels and if not, we'll remove the badge + // We need to also ensure more than that + // - Are we in the workspace? + // - Are we in the company? + async ensureBadgesAreReachable( + badges: ListResult, + context?: ExecutionContext, + ): Promise> { + if (badges.getEntities().length === 0) { return badges; } - - // This will ensure we are still in the channels and if not, we'll remove the badge - // We need to also ensure more than that - // - Are we in the workspace? - // - Are we in the company? - async ensureBadgesAreReachable( - badges: ListResult, - context?: ExecutionContext, - ): Promise> { - if (badges.getEntities().length === 0) { - return badges; - } - - const userId = badges.getEntities()[0].user_id; - - const channels = uniq(badges.getEntities().map(r => r.channel_id)); - for (const channelId of channels) { - const someBadge = badges.getEntities().find(b => b.channel_id === channelId); - const channelMemberPk = { - company_id: someBadge.company_id, - workspace_id: someBadge.workspace_id, - channel_id: channelId, - user_id: userId, - }; - const context = { - user: { id: channelMemberPk.user_id, server_request: true }, - channel: { id: channelId, ...channelMemberPk }, - }; - const exists = - (await gr.services.channels.channels.get( - { - id: channelId, - ..._.pick(channelMemberPk, "company_id", "workspace_id"), - }, - context, - )) && (await gr.services.channels.members.get(channelMemberPk, context)); - if (!exists) { - for (const badge of badges.getEntities()) { - if (badge.channel_id === channelId) this.removeUserChannelBadges(badge, context); - } - badges.filterEntities(b => b.channel_id !== channelId); - } - } - - const badgePerWorkspace = _.uniqBy(badges.getEntities(), r => r.workspace_id); - for (const badge of badgePerWorkspace) { - const workspaceId = badge.workspace_id; - const companyId = badge.company_id; - if (!workspaceId || workspaceId === "direct") { - continue; - } - try { - const exists = - (await gr.services.workspaces.get({ - id: workspaceId, - company_id: companyId, - })) && - (await gr.services.workspaces.getUser({ - workspaceId, - userId, - })); - if (!exists) { - await gr.services.channels.members.ensureUserNotInWorkspaceIsNotInChannel( - { id: userId }, - { id: workspaceId, company_id: companyId }, - context, - ); - for (const badge of badges.getEntities()) { - if (badge.workspace_id === workspaceId) this.removeUserChannelBadges(badge, context); - } - badges.filterEntities(b => b.workspace_id !== workspaceId); - } - } catch (e) {} - } - - return badges; - } - - /** - * FIXME: This is a temporary implementation which is sending as many websocket notifications as there are badges to remove - * A better implementation will be to do a bulk delete and have a single websocket notification event - * @param filter - * @param context - */ - async removeUserChannelBadges( - filter: Pick< - UserNotificationBadgePrimaryKey, - "workspace_id" | "company_id" | "channel_id" | "user_id" - >, - context?: ExecutionContext, - ): Promise { - const badges = ( - await this.repository.find( - _.pick(filter, ["workspace_id", "company_id", "channel_id", "user_id"]), - {}, + + const userId = badges.getEntities()[0].user_id; + + const channels = uniq(badges.getEntities().map(r => r.channel_id)); + for (const channelId of channels) { + const someBadge = badges.getEntities().find(b => b.channel_id === channelId); + const channelMemberPk = { + company_id: someBadge.company_id, + workspace_id: someBadge.workspace_id, + channel_id: channelId, + user_id: userId, + }; + const context = { + user: { id: channelMemberPk.user_id, server_request: true }, + channel: { id: channelId, ...channelMemberPk }, + }; + const exists = + (await gr.services.channels.channels.get( + { + id: channelId, + ..._.pick(channelMemberPk, "company_id", "workspace_id"), + }, context, - ) - ).getEntities(); - - return ( - await Promise.all( - badges.map(async badge => { - try { - return (await this.delete(badge)).deleted; - } catch (err) {} - }), - ) - ).filter(Boolean).length; + )) && (await gr.services.channels.members.get(channelMemberPk, context)); + if (!exists) { + for (const badge of badges.getEntities()) { + if (badge.channel_id === channelId) this.removeUserChannelBadges(badge, context); + } + badges.filterEntities(b => b.channel_id !== channelId); + } } - } \ No newline at end of file + + const badgePerWorkspace = _.uniqBy(badges.getEntities(), r => r.workspace_id); + for (const badge of badgePerWorkspace) { + const workspaceId = badge.workspace_id; + const companyId = badge.company_id; + if (!workspaceId || workspaceId === "direct") { + continue; + } + try { + const exists = + (await gr.services.workspaces.get({ + id: workspaceId, + company_id: companyId, + })) && + (await gr.services.workspaces.getUser({ + workspaceId, + userId, + })); + if (!exists) { + await gr.services.channels.members.ensureUserNotInWorkspaceIsNotInChannel( + { id: userId }, + { id: workspaceId, company_id: companyId }, + context, + ); + for (const badge of badges.getEntities()) { + if (badge.workspace_id === workspaceId) this.removeUserChannelBadges(badge, context); + } + badges.filterEntities(b => b.workspace_id !== workspaceId); + } + } catch (e) {} + } + + return badges; + } + + /** + * FIXME: This is a temporary implementation which is sending as many websocket notifications as there are badges to remove + * A better implementation will be to do a bulk delete and have a single websocket notification event + * @param filter + * @param context + */ + async removeUserChannelBadges( + filter: Pick< + UserNotificationBadgePrimaryKey, + "workspace_id" | "company_id" | "channel_id" | "user_id" + >, + context?: ExecutionContext, + ): Promise { + const badges = ( + await this.repository.find( + _.pick(filter, ["workspace_id", "company_id", "channel_id", "user_id"]), + {}, + context, + ) + ).getEntities(); + + return ( + await Promise.all( + badges.map(async badge => { + try { + return (await this.delete(badge)).deleted; + } catch (err) {} + }), + ) + ).filter(Boolean).length; + } +} diff --git a/twake/backend/node/src/services/notifications/services/digest.ts b/twake/backend/node/src/services/notifications/services/digest.ts index e410cbd0..62926306 100644 --- a/twake/backend/node/src/services/notifications/services/digest.ts +++ b/twake/backend/node/src/services/notifications/services/digest.ts @@ -3,7 +3,6 @@ import { Initializable, TwakeServiceProvider } from "../../../core/platform/fram import { Paginable, Pagination } from "../../../core/platform/framework/api/crud-service"; import Repository from "../../../core/platform/services/database/services/orm/repository/repository"; import { Channel } from "../../../services/channels/entities"; -import { UserObject } from "../../../services/user/web/types"; import Workspace from "../../../services/workspaces/entities/workspace"; import gr from "../../global-resolver"; import { UserNotificationBadge } from "../entities"; @@ -109,7 +108,6 @@ export class UserNotificationDigestService implements TwakeServiceProvider, Init for (const badge of badges.getEntities()) { if (!badge.thread_id) continue; try { - channels[badge.channel_id] = channels[badge.channel_id] || (await gr.services.channels.channels.get({ diff --git a/twake/backend/node/src/services/notifications/services/engine/processors/push-to-users.ts b/twake/backend/node/src/services/notifications/services/engine/processors/push-to-users.ts index d016df61..ba502267 100644 --- a/twake/backend/node/src/services/notifications/services/engine/processors/push-to-users.ts +++ b/twake/backend/node/src/services/notifications/services/engine/processors/push-to-users.ts @@ -167,7 +167,7 @@ export class PushNotificationToUsersMessageProcessor thread_id: badge.thread_id, message_id: badge.message_id, user_id: user, - mention_type: mentions.users.includes(user) + mention_type: mentions.users.includes(user), }); return this.saveBadge(badgeEntity, context); }), diff --git a/twake/backend/node/src/services/notifications/services/engine/processors/reaction-notification.ts b/twake/backend/node/src/services/notifications/services/engine/processors/reaction-notification.ts index c06848ba..7b0d7527 100644 --- a/twake/backend/node/src/services/notifications/services/engine/processors/reaction-notification.ts +++ b/twake/backend/node/src/services/notifications/services/engine/processors/reaction-notification.ts @@ -83,7 +83,7 @@ export class PushReactionNotification async buildNotificationMessageContent( message: ReactionNotification, ): Promise<{ title: string; text: string }> { - const { company_id, workspace_id, reaction_user_id, reaction, thread_id } = message; + const { company_id, workspace_id, reaction_user_id, reaction } = message; let title = ""; const channel: Channel = await gr.services.channels.channels.get({ @@ -102,7 +102,7 @@ export class PushReactionNotification }), gr.services.users.get({ id: reaction_user_id }), ]); - + const companyName = company?.name || ""; const workspaceName = workspace_id === "direct" ? "Direct" : workspace?.name || ""; const userName = this.getUserName(user) || "Twake"; diff --git a/twake/backend/node/src/services/notifications/web/controllers/notification.ts b/twake/backend/node/src/services/notifications/web/controllers/notification.ts index 2d8a5de4..c4416898 100644 --- a/twake/backend/node/src/services/notifications/web/controllers/notification.ts +++ b/twake/backend/node/src/services/notifications/web/controllers/notification.ts @@ -1,6 +1,6 @@ -import { FastifyReply, FastifyRequest } from "fastify"; +import { FastifyRequest } from "fastify"; import { CrudController } from "../../../../core/platform/services/webserver/types"; -import { NotificationAcknowledgeBody, NotificationListQueryParameters } from "../../types"; +import { NotificationListQueryParameters } from "../../types"; import { ResourceCreateResponse, ResourceDeleteResponse, diff --git a/twake/backend/node/src/services/workspaces/web/controllers/workspace-invite-tokens.ts b/twake/backend/node/src/services/workspaces/web/controllers/workspace-invite-tokens.ts index 46201568..6a1214a3 100644 --- a/twake/backend/node/src/services/workspaces/web/controllers/workspace-invite-tokens.ts +++ b/twake/backend/node/src/services/workspaces/web/controllers/workspace-invite-tokens.ts @@ -140,7 +140,7 @@ export class WorkspaceInviteTokensCrudController const userId = request.currentUser.id; const user = await gr.services.users.get({ id: userId }); - let companyUser = await gr.services.companies.getCompanyUser( + const companyUser = await gr.services.companies.getCompanyUser( { id: company_id }, { id: userId }, ); diff --git a/twake/backend/node/test/e2e/application/app-create-update.spec.ts b/twake/backend/node/test/e2e/application/app-create-update.spec.ts deleted file mode 100644 index 8b26d40a..00000000 --- a/twake/backend/node/test/e2e/application/app-create-update.spec.ts +++ /dev/null @@ -1,351 +0,0 @@ -import { afterAll, beforeAll, describe, expect, it } from "@jest/globals"; -import { init, TestPlatform } from "../setup"; -import { TestDbService } from "../utils.prepare.db"; -import { Api } from "../utils.api"; -import Application, { - PublicApplicationObject, -} from "../../../src/services/applications/entities/application"; -import { cloneDeep } from "lodash"; - -import { logger as log } from "../../../src/core/platform/framework"; -import { v1 as uuidv1 } from "uuid"; - -describe("Applications", () => { - const url = "/internal/services/applications/v1"; - - let platform: TestPlatform; - let testDbService: TestDbService; - let api: Api; - let appRepo; - - beforeAll(async ends => { - platform = await init(); - await platform.database.getConnector().drop(); - testDbService = await TestDbService.getInstance(platform, true); - await testDbService.createDefault(); - postPayload.company_id = platform.workspace.company_id; - api = new Api(platform); - appRepo = await testDbService.getRepository("application", Application); - ends(); - }); - - afterAll(done => { - platform.tearDown().then(() => done()); - }); - - const publishApp = async id => { - const entity = await appRepo.findOne({ id }); - if (!entity) throw new Error(`entity ${id} not found`); - entity.publication.published = true; - await appRepo.save(entity); - }; - - describe("Create application", function () { - it("should 403 if creator is not a company admin", async done => { - const payload = { resource: cloneDeep(postPayload) }; - - const user = await testDbService.createUser([testDbService.defaultWorkspace()], { - companyRole: "member", - }); - - const response = await api.post(`${url}/applications`, payload, user.id); - expect(response.statusCode).toBe(403); - done(); - }); - - it("should 200 on application create", async done => { - const payload = { resource: cloneDeep(postPayload) }; - const response = await api.post(`${url}/applications`, payload); - expect(response.statusCode).toBe(200); - - const r = response.resource; - - expect(r.company_id).toBe(payload.resource.company_id); - expect(!!r.is_default).toBe(false); - expect(r.identity).toMatchObject(payload.resource.identity); - expect(r.access).toMatchObject(payload.resource.access); - expect(r.display).toMatchObject(payload.resource.display); - expect(r.publication).toMatchObject(payload.resource.publication); - expect(r.stats).toMatchObject({ - created_at: expect.any(Number), - updated_at: expect.any(Number), - version: 0, - }); - - expect(r.api).toMatchObject({ - hooks_url: payload.resource.api.hooks_url, - allowed_ips: payload.resource.api.allowed_ips, - private_key: expect.any(String), - }); - - expect(r.api.private_key).not.toBe(""); - - const dbData = await appRepo.findOne({ id: response.resource.id }); - - expect(dbData.api).toMatchObject({ - allowed_ips: payload.resource.api.allowed_ips, - hooks_url: payload.resource.api.hooks_url, - private_key: expect.any(String), - }); - - done(); - }); - }); - describe("Update application", function () { - let createdApp: PublicApplicationObject; - - beforeAll(async done => { - const payload = { resource: cloneDeep(postPayload) }; - const response = await api.post(`${url}/applications`, payload); - createdApp = response.resource; - - done(); - }); - - it("should 403 if editor is not a company admin", async done => { - if (!createdApp) throw new Error("can't find created app"); - log.debug(createdApp); - - const user = await testDbService.createUser([testDbService.defaultWorkspace()], { - companyRole: "member", - }); - - const response = await api.post(`${url}/applications/${createdApp.id}`, postPayload, user.id); - expect(response.statusCode).toBe(403); - done(); - }); - - it("should 404 if application not found", async done => { - const response = await api.post(`${url}/applications/${uuidv1()}`, { resource: postPayload }); - expect(response.statusCode).toBe(404); - done(); - }); - - describe("Unpublished application", () => { - it("should 200 on application update", async done => { - const payload = { resource: cloneDeep(postPayload) }; - - payload.resource.is_default = true; - payload.resource.identity.name = "test2"; - payload.resource.api.hooks_url = "123123"; - payload.resource.access.read = []; - payload.resource.publication.requested = true; - - const response = await api.post(`${url}/applications/${createdApp.id}`, payload); - expect(response.statusCode).toBe(200); - - const r = response.resource; - - expect(r.company_id).toBe(payload.resource.company_id); - expect(!!r.is_default).toBe(false); - expect(r.identity).toMatchObject(payload.resource.identity); - - expect(r.access).toMatchObject(payload.resource.access); - expect(r.display).toMatchObject(payload.resource.display); - expect(r.publication).toMatchObject(payload.resource.publication); - expect(r.stats).toMatchObject({ - created_at: expect.any(Number), - updated_at: expect.any(Number), - version: 1, - }); - - expect(r.api).toBeTruthy(); - - const dbData = await appRepo.findOne({ id: response.resource.id }); - - expect(dbData.api).toMatchObject({ - allowed_ips: payload.resource.api.allowed_ips, - hooks_url: payload.resource.api.hooks_url, - private_key: expect.any(String), - }); - - done(); - }); - }); - - describe.skip("Published application", () => { - beforeAll(async done => { - const payload = { resource: cloneDeep(postPayload) }; - const response = await api.post(`${url}/applications`, payload); - createdApp = response.resource; - await publishApp(createdApp.id); - done(); - }); - - it("should 200 on update if allowed fields changed", async done => { - const payload = { resource: cloneDeep(createdApp) as Application }; - const entity = await appRepo.findOne({ id: createdApp.id }); - payload.resource.api = cloneDeep(entity.api); - payload.resource.publication.requested = true; - const response = await api.post(`${url}/applications/${createdApp.id}`, payload); - expect(response.statusCode).toBe(200); - - expect(response.resource.publication).toMatchObject({ - requested: true, - published: true, - }); - done(); - }); - - it("should 400 on update if not allowed fields changed", async done => { - const payload = { resource: cloneDeep(createdApp) as Application }; - const entity = await appRepo.findOne({ id: createdApp.id }); - payload.resource.api = cloneDeep(entity.api); - const response = await api.post(`${url}/applications/${createdApp.id}`, payload); - expect(response.statusCode).toBe(400); - done(); - }); - }); - }); - describe("Get applications", function () { - let firstApp: PublicApplicationObject; - let secondApp: PublicApplicationObject; - let thirdApp: PublicApplicationObject; - beforeAll(async done => { - const payload = { resource: cloneDeep(postPayload) }; - firstApp = (await api.post(`${url}/applications`, payload)).resource; - secondApp = (await api.post(`${url}/applications`, payload)).resource; - thirdApp = (await api.post(`${url}/applications`, payload)).resource; - - await publishApp(firstApp.id); - await publishApp(secondApp.id); - - done(); - }); - - it("should list published applications", async done => { - const response = await api.get(`${url}/applications`); - expect(response.statusCode).toBe(200); - - const published = response.resources.filter(a => a.publication.published).length; - const unpublished = response.resources.filter(a => a.publication.unpublished).length; - - expect(published).toBeGreaterThanOrEqual(2); - expect(unpublished).toEqual(0); - - expect(response.resources.map(a => a.id)).toEqual( - expect.arrayContaining([firstApp.id, secondApp.id]), - ); - - done(); - }); - - it("should return public object for published application to any user", async done => { - const response = await api.get(`${url}/applications/${firstApp.id}`, uuidv1()); - expect(response.statusCode).toBe(200); - expect(response.resource.id).toEqual(firstApp.id); - expect(response.resource.api).toBeFalsy(); - done(); - }); - - it("should return public object for unpublished application to any user", async done => { - const response = await api.get(`${url}/applications/${thirdApp.id}`, uuidv1()); - expect(response.statusCode).toBe(200); - expect(response.resource.id).toEqual(thirdApp.id); - expect(response.resource.api).toBeFalsy(); - done(); - }); - - it("should return whole object for published application to admin", async done => { - const response = await api.get(`${url}/applications/${firstApp.id}`); - expect(response.statusCode).toBe(200); - expect(response.resource.id).toEqual(firstApp.id); - expect(response.resource.api).toBeTruthy(); - - done(); - }); - - it("should return whole object for unpublished application to admin", async done => { - const response = await api.get(`${url}/applications/${thirdApp.id}`); - expect(response.statusCode).toBe(200); - expect(response.resource.id).toEqual(thirdApp.id); - expect(response.resource.api).toBeTruthy(); - done(); - }); - }); -}); - -const postPayload = { - is_default: true, - company_id: null, - identity: { - code: "code", - name: "name", - icon: "icon", - description: "description", - website: "website", - categories: [], - compatibility: [], - }, - api: { - hooks_url: "hooks_url", - allowed_ips: "allowed_ips", - }, - access: { - read: ["messages"], - write: ["messages"], - delete: ["messages"], - hooks: ["messages"], - }, - display: { - twake: { - version: 1, - - files: { - editor: { - preview_url: "string", //Open a preview inline (iframe) - edition_url: "string", //Url to edit the file (full screen) - extensions: [], //Main extensions app can read - // if file was created by the app, then the app is able to edit with or without extension - empty_files: [ - { - url: "string", // "https://[...]/empty.docx"; - filename: "string", // "Untitled.docx"; - name: "string", // "Word Document"; - }, - ], - }, - actions: [ - //List of action that can apply on a file - { - name: "string", - id: "string", - }, - ], - }, - - //Chat plugin - chat: { - input: true, - commands: [ - { - command: "string", // my_app mycommand - description: "string", - }, - ], - actions: [ - //List of action that can apply on a message - { - name: "string", - id: "string", - }, - ], - }, - - //Allow app to appear as a bot user in direct chat - direct: false, - - //Display app as a standalone application in a tab - tab: { url: "string" }, - - //Display app as a standalone application on the left bar - standalone: { url: "string" }, - - //Define where the app can be configured from - configuration: ["global", "channel"], - }, - }, - publication: { - requested: false, //Publication requested - }, -}; diff --git a/twake/backend/node/test/e2e/application/application-events.spec.ts b/twake/backend/node/test/e2e/application/application-events.spec.ts deleted file mode 100644 index 42c9bf81..00000000 --- a/twake/backend/node/test/e2e/application/application-events.spec.ts +++ /dev/null @@ -1,118 +0,0 @@ -import * as crypto from "crypto"; -import { afterAll, beforeAll, describe, expect, it } from "@jest/globals"; -import { FastifyInstance, FastifyPluginCallback, FastifyReply, FastifyRequest } from "fastify"; - -import { init, TestPlatform } from "../setup"; -import { TestDbService } from "../utils.prepare.db"; -import { Api } from "../utils.api"; - -import { logger as log } from "../../../src/core/platform/framework"; - -let signingSecret = ""; - -describe("Application events", () => { - const url = "/internal/services/applications/v1"; - let platform: TestPlatform; - let api: Api; - let appId: string; - - beforeAll(async ends => { - platform = await init(undefined, testAppHookRoute); - - await platform.database.getConnector().drop(); - - await TestDbService.getInstance(platform, true); - api = new Api(platform); - - postPayload.company_id = platform.workspace.company_id; - - const createdApplication = await api.post("/internal/services/applications/v1/applications", { - resource: postPayload, - }); - - appId = createdApplication.resource.id; - signingSecret = createdApplication.resource.api.private_key; - - ends(); - - afterAll(done => { - platform.tearDown().then(() => done()); - }); - }); - - it("Should 200 on sending well formed event", async done => { - const payload = { - company_id: platform.workspace.company_id, - workspace_id: platform.workspace.workspace_id, - type: "some type", - name: "name", - content: {}, - }; - - const response = await api.post(`${url}/applications/${appId}/event`, payload); - expect(response.statusCode).toBe(200); - expect(response.resource).toMatchObject({ a: "b" }); - done(); - }); -}); - -const postPayload = { - is_default: false, - company_id: null, - identity: { - code: "code", - name: "name", - icon: "icon", - description: "description", - website: "website", - categories: [], - compatibility: [], - }, - api: { - hooks_url: "http://localhost:3000/test/appHook", - allowed_ips: "allowed_ips", - }, - access: { - read: ["messages"], - write: ["messages"], - delete: ["messages"], - hooks: ["messages"], - }, - display: {}, - publication: { - requested: true, - }, -}; - -const testAppHookRoute = (fastify: FastifyInstance) => { - const routes: FastifyPluginCallback = (fastify: FastifyInstance, options, next) => { - fastify.route({ - method: "POST", - url: "/test/appHook", - handler: (request: FastifyRequest, reply: FastifyReply): Promise => { - const signature = request.headers["x-twake-signature"]; - if (!signature) { - reply.status(403); - reply.send({ error: "Signature is missing" }); - return undefined; - } - - const expectedSignature = crypto - .createHmac("sha256", signingSecret) - .update(JSON.stringify(request.body)) - .digest("hex"); - if (expectedSignature != signature) { - reply.status(403); - reply.send({ error: "Wrong signature" }); - return undefined; - } - - log.debug({ signatureMatched: expectedSignature == signature }); - return Promise.resolve({ a: "b" }); - }, - }); - - next(); - }; - fastify.register(routes); -}; diff --git a/twake/backend/node/test/e2e/application/application-login.spec.ts b/twake/backend/node/test/e2e/application/application-login.spec.ts deleted file mode 100644 index e07e10dd..00000000 --- a/twake/backend/node/test/e2e/application/application-login.spec.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { afterAll, beforeAll, describe, expect, it } from "@jest/globals"; - -import { init, TestPlatform } from "../setup"; -import { TestDbService } from "../utils.prepare.db"; -import { Api } from "../utils.api"; -import { logger as log } from "../../../src/core/platform/framework"; -import { ApplicationLoginResponse } from "../../../src/services/applicationsapi/web/types"; - -describe("Applications", () => { - let platform: TestPlatform; - let testDbService: TestDbService; - let api: Api; - let appId: string; - let private_key: string; - let accessToken: ApplicationLoginResponse["access_token"]; - - beforeAll(async ends => { - platform = await init(); - await platform.database.getConnector().drop(); - testDbService = await TestDbService.getInstance(platform, true); - api = new Api(platform); - - postPayload.company_id = platform.workspace.company_id; - - const createdApplication = await api.post("/internal/services/applications/v1/applications", { - resource: postPayload, - }); - - appId = createdApplication.resource.id; - private_key = createdApplication.resource.api.private_key; - - ends(); - }); - - afterAll(done => { - platform.tearDown().then(() => done()); - }); - - describe("Login", function () { - it("Should be ok on valid token", async done => { - expect(appId).toBeTruthy(); - - const response = await api.post("/api/console/v1/login", { - id: appId, - secret: private_key, - }); - expect(response.statusCode).toBe(200); - - const resource = (await response.json()).resource as ApplicationLoginResponse; - - expect(resource).toMatchObject({ - access_token: { - time: expect.any(Number), - expiration: expect.any(Number), - refresh_expiration: expect.any(Number), - value: expect.any(String), - refresh: expect.any(String), - type: expect.any(String), - }, - }); - - accessToken = resource.access_token; - - done(); - }); - }); - - describe("Get myself", function () { - it("Should be 401 on invalid token", async done => { - const response = await platform.app.inject({ - method: "GET", - url: "/api/console/v1/me", - headers: { - authorization: `Bearer ${accessToken.value + "!!"}`, - }, - }); - log.debug(response.json()); - expect(response.statusCode).toBe(401); - done(); - }); - - it("Should be 403 on auth by users (not company) token", async done => { - const userToken = await platform.auth.getJWTToken(); - - const response = await platform.app.inject({ - method: "GET", - url: "/api/console/v1/me", - headers: { - authorization: `Bearer ${userToken}`, - }, - }); - log.debug(response.json()); - expect(response.statusCode).toBe(403); - done(); - }); - - it("Should be ok on valid token", async done => { - const response = await platform.app.inject({ - method: "GET", - url: "/api/console/v1/me", - headers: { - authorization: `Bearer ${accessToken.value}`, - }, - }); - - expect(response.statusCode).toBe(200); - const resource = (await response.json()).resource; - log.debug(resource); - expect(resource).toMatchObject(postPayload); - done(); - }); - }); -}); - -const postPayload = { - is_default: false, - company_id: null, - identity: { - code: "code", - name: "name", - icon: "icon", - description: "description", - website: "website", - categories: [], - compatibility: [], - }, - api: { - hooks_url: "hooks_url", - allowed_ips: "allowed_ips", - }, - access: { - read: ["messages"], - write: ["messages"], - delete: ["messages"], - hooks: ["messages"], - }, - display: {}, - publication: { - requested: true, - }, -}; diff --git a/twake/backend/node/test/e2e/messages/messages.files.search.spec.ts b/twake/backend/node/test/e2e/messages/messages.files.search.spec.ts deleted file mode 100644 index 5da6fcb5..00000000 --- a/twake/backend/node/test/e2e/messages/messages.files.search.spec.ts +++ /dev/null @@ -1,165 +0,0 @@ -import "reflect-metadata"; -import { afterAll, beforeAll, describe, expect, it } from "@jest/globals"; -import { init, TestPlatform } from "../setup"; -import { createMessage, createParticipant, e2e_createThread } from "./utils"; -import { MessageFile } from "../../../src/services/messages/entities/message-files"; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import fs from "fs"; -import formAutoContent from "form-auto-content"; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import uuid from "node-uuid"; -import { ChannelUtils, get as getChannelUtils } from "../channels/utils"; -import gr from "../../../src/services/global-resolver"; -import User from "../../../src/services/user/entities/user"; -import { WorkspaceExecutionContext } from "../../../src/services/channels/types"; - -describe("Search files", () => { - const filesUrl = "/internal/services/files/v1"; - const messagesUrl = "/internal/services/messages/v1"; - let platform: TestPlatform; - let channelUtils: ChannelUtils; - - beforeAll(async () => { - platform = await init({ - services: ["webserver", "database", "storage", "message-queue", "files", "previews"], - }); - await platform.database.getConnector().drop(); - channelUtils = getChannelUtils(platform); - }); - - afterAll(async done => { - await platform?.tearDown(); - platform = null; - done(); - }); - - const files = [ - "../files/assets/sample.png", - "../files/assets/sample.gif", - "../files/assets/sample.pdf", - "../files/assets/sample.doc", - "../files/assets/sample.zip", - ].map(p => `${__dirname}/${p}`); - - function getContext(user?: User): WorkspaceExecutionContext { - return { - workspace: platform.workspace, - user: user || platform.currentUser, - }; - } - - it("should return uploaded files", async done => { - let channel = channelUtils.getChannel(); - channel = (await gr.services.channels.channels.save(channel, {}, getContext())).entity; - const channelId = channel.id; - - const uploadedFiles = []; - for (const i in files) { - const file = files[i]; - - const form = formAutoContent({ file: fs.createReadStream(file) }); - form.headers["authorization"] = `Bearer ${await platform.auth.getJWTToken()}`; - - const uploadedFile = await platform.app.inject({ - method: "POST", - url: `${filesUrl}/companies/${platform.workspace.company_id}/files?thumbnail_sync=1`, - ...form, - }); - - const resource = uploadedFile.json().resource; - - const messageFile: Partial = { - id: uuid.v1(), - company_id: platform.workspace.company_id, - metadata: { - source: "internal", - external_id: { - company_id: platform.workspace.company_id, - id: resource.id, - }, - ...resource.metadata, - }, - }; - - await e2e_createThread( - platform, - [ - createParticipant( - { - type: "channel", - id: channelId, - }, - platform, - ), - ], - createMessage({ text: "Some message", files: [messageFile] }), - ); - - uploadedFiles.push(uploadedFile.json().resource); - } - - await new Promise(r => setTimeout(r, 2000)); - - let resources = await search("sample"); - expect(resources.length).toEqual(5); - - resources = await search("sample", { extension: "png" }); - expect(resources.length).toEqual(1); - - resources = await search("sample", { is_file: true }); - expect(resources.length).toEqual(3); - - resources = await search("sample", { is_media: true }); - expect(resources.length).toEqual(2); - - resources = await search("sam"); - expect(resources.length).toEqual(5); - - done(); - }); - - async function search( - searchString: string, - options?: { - company_id?: string; - workspace_id?: string; - channel_id?: string; - limit?: number; - sender?: string; - is_file?: boolean; - is_media?: boolean; - extension?: string; - }, - ): Promise { - const jwtToken = await platform.auth.getJWTToken(); - - const query: any = options || {}; - - const response = await platform.app.inject({ - method: "GET", - url: `${messagesUrl}/companies/${platform.workspace.company_id}/files/search`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - query: { - ...query, - q: searchString, - }, - }); - - expect(response.statusCode).toBe(200); - const json = response.json(); - expect(json).toMatchObject({ resources: expect.any(Array) }); - const resources = json.resources; - return resources; - } -}); - -function getContext(platform: TestPlatform) { - return { - company: { id: platform.workspace.company_id }, - user: { id: platform.currentUser.id }, - }; -} diff --git a/twake/backend/node/test/e2e/messages/messages.files.spec.ts b/twake/backend/node/test/e2e/messages/messages.files.spec.ts deleted file mode 100644 index 01b50f79..00000000 --- a/twake/backend/node/test/e2e/messages/messages.files.spec.ts +++ /dev/null @@ -1,364 +0,0 @@ -import "reflect-metadata"; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "@jest/globals"; -import { init, TestPlatform } from "../setup"; -import { ResourceUpdateResponse } from "../../../src/utils/types"; -import { deserialize } from "class-transformer"; -import { Thread } from "../../../src/services/messages/entities/threads"; -import { createMessage, e2e_createThread } from "./utils"; -import { MessageFile } from "../../../src/services/messages/entities/message-files"; -import { Message } from "../../../src/services/messages/entities/messages"; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import fs from "fs"; -import formAutoContent from "form-auto-content"; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import uuid, { v1 } from "node-uuid"; - -describe("The Messages Files feature", () => { - const url = "/internal/services/messages/v1"; - let platform: TestPlatform; - - beforeEach(async () => { - platform = await init({ - services: [ - "webserver", - "database", - "applications", - "search", - "storage", - "message-queue", - "user", - "search", - "files", - "websocket", - "messages", - "auth", - "realtime", - "channels", - "counter", - "statistics", - "platform-services", - ], - }); - }); - - afterEach(async () => { - await platform.tearDown(); - }); - - describe("On user send files", () => { - it("did add the files when full information is given (external source)", async done => { - const file: MessageFile = { - cache: { channel_id: "", company_id: "", user_id: "", workspace_id: "" }, - company_id: "", - created_at: 0, - id: "", - metadata: { - source: "linshare", - external_id: "1234", - name: "My LinShare File", - thumbnails: [], - }, - }; - - const response = await e2e_createThread( - platform, - [], - createMessage({ text: "Some message", files: [file] }), - ); - const result: ResourceUpdateResponse = deserialize( - ResourceUpdateResponse, - response.body, - ); - - expect(result.resource.message.files.length).toBe(1); - expect(result.resource.message.files[0].id).not.toBeFalsy(); - expect(result.resource.message.files[0].message_id).not.toBeFalsy(); - expect(result.resource.message.files[0].metadata.external_id).toBe("1234"); - expect(result.resource.message.files[0].metadata.source).toBe("linshare"); - - done(); - }); - - it("did not deduplicate files", async done => { - const file: MessageFile = { - cache: { channel_id: "", company_id: "", user_id: "", workspace_id: "" }, - company_id: "", - created_at: 0, - id: "", - metadata: { - source: "linshare", - external_id: "1234", - name: "My LinShare File", - thumbnails: [], - }, - }; - - const file2: MessageFile = { - cache: { channel_id: "", company_id: "", user_id: "", workspace_id: "" }, - company_id: "", - created_at: 0, - id: "", - metadata: { - source: "linshare2", - external_id: "5678", - name: "My LinShare 2 File", - thumbnails: [], - }, - }; - - const response = await e2e_createThread( - platform, - [], - createMessage({ text: "Some message", files: [file] }), - ); - const result: ResourceUpdateResponse = deserialize( - ResourceUpdateResponse, - response.body, - ); - - const message = result.resource.message; - const firstFileId = message.files[0].id; - - message.files.push(file2); - - const messageUpdatedRaw = await platform.app.inject({ - method: "POST", - url: `${url}/companies/${platform.workspace.company_id}/threads/${message.thread_id}/messages/${message.id}`, - headers: { - authorization: `Bearer ${await platform.auth.getJWTToken()}`, - }, - payload: { - resource: message, - }, - }); - const messageUpdated: ResourceUpdateResponse = deserialize( - ResourceUpdateResponse, - messageUpdatedRaw.body, - ); - - expect(messageUpdated.resource.files.length).toBe(2); - expect(messageUpdated.resource.files.filter(f => f.id === firstFileId).length).toBe(1); - - done(); - }); - }); -}); - -describe("List user files", () => { - const filesUrl = "/internal/services/files/v1"; - const messagesUrl = "/internal/services/messages/v1"; - let platform: TestPlatform; - - beforeAll(async () => { - platform = await init({ - services: ["webserver", "database", "storage", "message-queue", "files", "previews"], - }); - await platform.database.getConnector().drop(); - }); - - afterAll(async done => { - await platform?.tearDown(); - platform = null; - done(); - }); - - const files = [ - "../files/assets/sample.png", - "../files/assets/sample.gif", - "../files/assets/sample.pdf", - "../files/assets/sample.doc", - "../files/assets/sample.zip", - ].map(p => `${__dirname}/${p}`); - - it("should return uploaded files", async done => { - const jwtToken = await platform.auth.getJWTToken(); - - const uploadedFiles = []; - - for (const i in files) { - const file = files[i]; - - const form = formAutoContent({ file: fs.createReadStream(file) }); - form.headers["authorization"] = `Bearer ${await platform.auth.getJWTToken()}`; - - const uploadedFile = await platform.app.inject({ - method: "POST", - url: `${filesUrl}/companies/${platform.workspace.company_id}/files?thumbnail_sync=1`, - ...form, - }); - - expect(uploadedFile.statusCode).toBe(200); - - const resource = uploadedFile.json().resource; - - const messageFile: MessageFile = { - cache: { channel_id: "", company_id: "", user_id: "", workspace_id: "" }, - created_at: 0, - id: uuid.v1(), - company_id: platform.workspace.company_id, - metadata: { - source: "internal", - external_id: { - company_id: platform.workspace.company_id, - id: resource.id, - }, - ...resource.metadata, - }, - }; - - await e2e_createThread( - platform, - [], - createMessage({ text: "Some message", files: [messageFile] }), - ); - - uploadedFiles.push(uploadedFile.json().resource); - } - - function checkResource(resource) { - expect(resource).toMatchObject({ - company_id: expect.any(String), - id: expect.any(String), - created_at: expect.any(Number), - metadata: expect.any(Object), - }); - } - - let response = await platform.app.inject({ - method: "GET", - url: `${messagesUrl}/companies/${platform.workspace.company_id}/files?type=user_upload&limit=3`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - }); - - await new Promise(resolve => setTimeout(resolve, 1000)); - - expect(response.statusCode).toBe(200); - - let resources = response.json().resources; - expect(resources.length).toBe(3); - - resources.forEach(checkResource); - - const nextPageToken = response.json().next_page_token; - - response = await platform.app.inject({ - method: "GET", - url: `${messagesUrl}/companies/${platform.workspace.company_id}/files?type=user_upload&page_token=${nextPageToken}limit=100`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - }); - - await new Promise(resolve => setTimeout(resolve, 1000)); - - expect(response.statusCode).toBe(200); - - resources = response.json().resources; - expect(resources.length).toBe(5); - - expect(response.json().resources[0]).toMatchObject({ - company_id: expect.any(String), - id: expect.any(String), - created_at: expect.any(Number), - metadata: expect.any(Object), - user: expect.any(Object), - context: expect.any(Object), - }); - - resources.forEach(checkResource); - - done(); - }); - - it("should return downloaded files", async done => { - const jwtToken = await platform.auth.getJWTToken({ sub: v1() }); - const uploadedFiles = []; - for (const i in files) { - const file = files[i]; - - const form = formAutoContent({ file: fs.createReadStream(file) }); - form.headers["authorization"] = `Bearer ${await platform.auth.getJWTToken()}`; - - const uploadedFile = await platform.app.inject({ - method: "POST", - url: `${filesUrl}/companies/${platform.workspace.company_id}/files?thumbnail_sync=1`, - ...form, - }); - - const resource = uploadedFile.json().resource; - - const messageFile: MessageFile = { - cache: { channel_id: "", company_id: "", user_id: "", workspace_id: "" }, - created_at: 0, - id: uuid.v1(), - company_id: platform.workspace.company_id, - metadata: { - source: "internal", - external_id: { - company_id: platform.workspace.company_id, - id: resource.id, - }, - ...resource.metadata, - }, - }; - - const thread = await e2e_createThread( - platform, - [], - createMessage({ text: "Some message", files: [messageFile] }), - ); - - uploadedFiles.push(uploadedFile.json().resource); - - await platform.app.inject({ - method: "POST", - url: `${messagesUrl}/companies/${platform.workspace.company_id}/threads/${ - thread.json().resource.message.thread_id - }/messages/${thread.json().resource.message.id}/download/${ - thread.json().resource.message.files[0].id - }`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - }); - } - - await new Promise(resolve => setTimeout(resolve, 1000)); - - const response = await platform.app.inject({ - method: "GET", - url: `${messagesUrl}/companies/${platform.workspace.company_id}/files?type=user_download`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - }); - - await new Promise(resolve => setTimeout(resolve, 1000)); - - expect(response.statusCode).toBe(200); - - expect(response.json().resources.length).toBe(5); - - expect(response.json().resources[0]).toMatchObject({ - company_id: expect.any(String), - id: expect.any(String), - created_at: expect.any(Number), - metadata: expect.any(Object), - user: expect.any(Object), - context: expect.any(Object), - }); - - done(); - }); -}); - -function getContext(platform: TestPlatform) { - return { - company: { id: platform.workspace.company_id }, - user: { id: platform.currentUser.id }, - }; -} diff --git a/twake/backend/node/test/e2e/messages/messages.messages.spec.ts b/twake/backend/node/test/e2e/messages/messages.messages.spec.ts deleted file mode 100644 index 2d30b256..00000000 --- a/twake/backend/node/test/e2e/messages/messages.messages.spec.ts +++ /dev/null @@ -1,199 +0,0 @@ -import "reflect-metadata"; -import { afterEach, describe, expect, it } from "@jest/globals"; -import { init, TestPlatform } from "../setup"; -import { - ResourceListResponse, - ResourceUpdateResponse, - User, - Workspace, -} from "../../../src/utils/types"; -import { deserialize } from "class-transformer"; -import { ParticipantObject, Thread } from "../../../src/services/messages/entities/threads"; -import { - createMessage, - createParticipant, - e2e_createChannel, - e2e_createMessage, - e2e_createThread, -} from "./utils"; -import { Message } from "../../../src/services/messages/entities/messages"; -import { v1 as uuidv1 } from "uuid"; -import { MessageWithReplies } from "../../../src/services/messages/types"; -import { TestDbService } from "../utils.prepare.db"; -import gr from "../../../src/services/global-resolver"; -import { ChannelVisibility, WorkspaceExecutionContext } from "../../../src/services/channels/types"; -import { ChannelSaveOptions } from "../../../src/services/channels/web/types"; -import { ChannelUtils, get as getChannelUtils } from "../channels/utils"; - -describe("The Messages feature", () => { - const url = "/internal/services/messages/v1"; - let platform: TestPlatform; - let testDbService: TestDbService; - let channelUtils: ChannelUtils; - - function getContext(user?: User): WorkspaceExecutionContext { - return { - workspace: platform.workspace, - user: user || platform.currentUser, - }; - } - - beforeAll(async () => { - platform = await init({ - services: [ - "webserver", - "database", - "search", - "storage", - "message-queue", - "applications", - "user", - "websocket", - "webserver", - "messages", - "files", - "auth", - "search", - "realtime", - "channels", - "counter", - "statistics", - "platform-services", - ], - }); - await gr.database.getConnector().drop(); - - channelUtils = getChannelUtils(platform); - }); - - afterEach(async () => { - await platform.tearDown(); - }); - - describe("On user use messages in a thread", () => { - it("should create a message in a thread", async () => { - const response = await e2e_createThread( - platform, - [], - createMessage({ text: "Initial thread message" }), - ); - const result: ResourceUpdateResponse = deserialize( - ResourceUpdateResponse, - response.body, - ); - const threadId = result.resource.id; - - await e2e_createMessage(platform, threadId, createMessage({ text: "Reply 1" })); - - await e2e_createMessage(platform, threadId, createMessage({ text: "Reply 2" })); - - const jwtToken = await platform.auth.getJWTToken(); - const listResponse = await platform.app.inject({ - method: "GET", - url: `${url}/companies/${platform.workspace.company_id}/threads/${threadId}/messages`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - }); - - const listResult: ResourceListResponse = deserialize( - ResourceListResponse, - listResponse.body, - ); - - expect(listResponse.statusCode).toBe(200); - expect(listResult.resources.length).toBe(3); - }); - }); - - describe("Inbox", () => { - it("Should get recent user messages", async done => { - const directChannelIn = channelUtils.getDirectChannel(); - const members = [platform.currentUser.id, uuidv1()]; - const directWorkspace: Workspace = { - company_id: platform.workspace.company_id, - workspace_id: ChannelVisibility.DIRECT, - }; - - await Promise.all([ - gr.services.channels.channels.save( - directChannelIn, - { - members, - }, - { ...getContext(), ...{ workspace: directWorkspace } }, - ), - ]); - - const recipient: ParticipantObject = { - created_at: 0, - created_by: "", - type: "channel", - company_id: directChannelIn.company_id, - workspace_id: "direct", - id: directChannelIn.id, - }; - - for (let i = 0; i < 6; i++) { - const response = await e2e_createThread( - platform, - [recipient], - createMessage({ text: `Initial thread message ${i}` }), - ); - const result: ResourceUpdateResponse = deserialize( - ResourceUpdateResponse, - response.body, - ); - const threadId = result.resource.id; - - const replies = []; - for (let j = 0; j < 3; j++) { - replies.push( - e2e_createMessage( - platform, - threadId, - createMessage({ text: `Reply ${j} to message ${i}` }), - ), - ); - } - await Promise.all(replies); - } - - const jwtToken = await platform.auth.getJWTToken({ sub: members[0] }); - const response = await platform.app.inject({ - method: "GET", - url: `${url}/companies/${platform.workspace.company_id}/inbox?limit=5`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - }); - - expect(response.statusCode).toEqual(200); - - const inbox: ResourceListResponse = deserialize( - ResourceListResponse, - response.body, - ); - - expect(inbox.resources.length).toEqual(5); - - for (const resource of inbox.resources) { - expect(resource).toMatchObject({ - id: expect.any(String), - thread_id: expect.any(String), - created_at: expect.any(Number), - updated_at: expect.any(Number), - user_id: platform.currentUser.id, - application_id: null, - text: expect.any(String), - cache: { - company_id: directChannelIn.company_id, - channel_id: directChannelIn.id, - }, - }); - } - - done(); - }); - }); -}); diff --git a/twake/backend/node/test/e2e/messages/messages.search.spec.ts b/twake/backend/node/test/e2e/messages/messages.search.spec.ts deleted file mode 100644 index f1c3afde..00000000 --- a/twake/backend/node/test/e2e/messages/messages.search.spec.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { afterAll, beforeAll, describe, expect, it } from "@jest/globals"; -import { init, TestPlatform } from "../setup"; -import { TestDbService } from "../utils.prepare.db"; -import { v1 as uuidv1 } from "uuid"; -import { createMessage, e2e_createChannel, e2e_createMessage, e2e_createThread } from "./utils"; -import { ResourceUpdateResponse } from "../../../src/utils/types"; -import { ParticipantObject, Thread } from "../../../src/services/messages/entities/threads"; -import { deserialize } from "class-transformer"; -import { Channel } from "../../../src/services/channels/entities"; -import { ChannelUtils, get as getChannelUtils } from "../channels/utils"; -import { MessageFile } from "../../../src/services/messages/entities/message-files"; -import gr from "../../../src/services/global-resolver"; - -describe("The /messages API", () => { - const url = "/internal/services/messages/v1"; - let platform: TestPlatform; - let channelUtils: ChannelUtils; - - beforeAll(async ends => { - platform = await init({ - services: [ - "database", - "search", - "message-queue", - "websocket", - "webserver", - "user", - "auth", - "applications", - "storage", - "counter", - "workspaces", - "console", - "statistics", - "platform-services", - ], - }); - - await platform.database.getConnector().drop(); - channelUtils = getChannelUtils(platform); - - const testDbService = new TestDbService(platform); - await testDbService.createCompany(platform.workspace.company_id); - - const workspacePk = { - id: platform.workspace.workspace_id, - company_id: platform.workspace.company_id, - }; - - const workspacePk2 = { - id: uuidv1(), - company_id: uuidv1(), - }; - await testDbService.createWorkspace(workspacePk); - - const user = await testDbService.createUser([workspacePk], {}); - - platform.currentUser.id = user.id; - - ends(); - }); - - afterAll(async ends => { - platform && (await platform.tearDown()); - platform = null; - ends(); - }); - - describe("The GET /messages/?search=... route", () => { - it("Should find the searched messages", async done => { - // await testDbService.createWorkspace(workspacePk2); - - const channel = await createChannel(); - - const participant = { - type: "channel", - id: channel.id, - company_id: platform.workspace.company_id, - workspace_id: platform.workspace.workspace_id, - } as ParticipantObject; - - const firstThreadId = await createThread("First thread", [participant]); - await createReply(firstThreadId, "First reply of first thread"); - await createReply(firstThreadId, "Second reply of first thread"); - - const secondThreadId = await createThread("Another thread", [participant]); - await createReply(secondThreadId, "First reply of second thread"); - await createReply(secondThreadId, "Second reply of second thread is also a message"); - - //Wait for indexation to happen - await new Promise(r => setTimeout(r, 3000)); - - let resources = await search("Reply"); - expect(resources.length).toEqual(4); - - resources = await search("fdfsd"); - expect(resources.length).toEqual(0); - - resources = await search("first"); - expect(resources.length).toEqual(4); - - resources = await search("second"); - expect(resources.length).toEqual(3); - - resources = await search("another"); - expect(resources.length).toEqual(1); - - resources = await search("also"); - expect(resources.length).toEqual(1); - - resources = await search("of"); - expect(resources.length).toEqual(4); - - resources = await search("a"); - - //sleep 1s - await new Promise(r => setTimeout(r, 2000)); - - expect(resources.length).toEqual(1); - - done(); - }); - it("Filter out messages from channels we are not member of", async done => { - const anotherUserId = uuidv1(); - const channel = await e2e_createChannel(platform, [platform.currentUser.id, anotherUserId]); - const anotherChannel = await e2e_createChannel(platform, [anotherUserId], anotherUserId); //Test user is not the owner - - const participant = { - type: "channel", - id: channel.resource.id, - company_id: channel.resource.company_id, - workspace_id: channel.resource.workspace_id, - } as ParticipantObject; - - const participant2 = { - type: "channel", - id: anotherChannel.resource.id, - company_id: anotherChannel.resource.company_id, - workspace_id: anotherChannel.resource.workspace_id, - } as ParticipantObject; - - const file = new MessageFile(); - file.metadata = { external_id: undefined, source: undefined, name: "test" }; - - const firstThreadId = await createThread("Filtered thread", [participant]); - await createReply(firstThreadId, "Filtered message 1-1"); - await createReply(firstThreadId, "Filtered message 1-2"); - await createReply(firstThreadId, "Filtered message 1-3"); - await createReply(firstThreadId, "Filtered message 1-4", { files: [file] }); - - const secondThreadId = await createThread("Filtered thread 2", [participant2], anotherUserId); - await createReply(secondThreadId, "Filtered message 2-1"); - await createReply(secondThreadId, "Filtered message 2-2"); - await createReply(secondThreadId, "Filtered message 2-3"); - await createReply(secondThreadId, "Filtered message 2-4"); - - const thirdThreadId = await createThread("Filtered thread 3", [participant]); - await createReply(thirdThreadId, "Filtered message 3-1"); - await createReply(thirdThreadId, "Filtered message 3-2", { userId: anotherUserId }); - await createReply(thirdThreadId, "Filtered message 3-3", { userId: anotherUserId }); - await createReply(thirdThreadId, "Filtered message 3-4", { - userId: anotherUserId, - files: [file], - }); - - //Wait for indexation to happen - await new Promise(r => setTimeout(r, 3000)); - - // no limit - const resources0 = await search("Filtered", { limit: 10000 }); - expect(resources0.length).toEqual(10); - - const resources = await search("Filtered", { limit: 9 }); - expect(resources.length).toEqual(9); - - // check for the empty result set - const resources2 = await search("Nothing", { limit: 10 }); - expect(resources2.length).toEqual(0); - - // check for the user - const resources3 = await search("Filtered", { sender: anotherUserId }); - expect(resources3.length).toEqual(3); - - // check for the files - const resources4 = await search("Filtered", { has_files: true }); - expect(resources4.length).toEqual(2); - - // check for the user and files - const resources5 = await search("Filtered", { sender: anotherUserId, has_files: true }); - expect(resources5.length).toEqual(1); - - done(); - }); - }); - - async function createChannel(userId = platform.currentUser.id): Promise { - const channel = channelUtils.getChannel(userId); - const creationResult = await gr.services.channels.channels.save( - channel, - {}, - channelUtils.getContext({ id: userId }), - ); - - return creationResult.entity; - } - - async function createThread(text, participants: ParticipantObject[], owner?: string) { - const response = await e2e_createThread( - platform, - participants, - createMessage({ text: text, user_id: owner }), - owner, - ); - - const result: ResourceUpdateResponse = deserialize( - ResourceUpdateResponse, - response.body, - ); - return result.resource.id; - } - - async function createReply(threadId, text, options?: { userId?: string; files?: MessageFile[] }) { - const cr = options?.userId ? { currentUser: { id: options?.userId } } : undefined; - - const message = { text, ...(options?.files ? { files: options.files } : {}) }; - - return e2e_createMessage(platform, threadId, createMessage(message, cr as TestPlatform)); - } - - async function search( - searchString: string, - options?: { - company_id?: string; - workspace_id?: string; - channel_id?: string; - limit?: number; - sender?: string; - has_files?: boolean; - }, - ): Promise { - const jwtToken = await platform.auth.getJWTToken(); - - const query: any = options || {}; - - const response = await platform.app.inject({ - method: "GET", - // url: `${url}/companies/${platform.workspace.company_id}/woskpaces/`, - url: `${url}/companies/${platform.workspace.company_id}/search`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - query: { - ...query, - q: searchString, - }, - }); - - expect(response.statusCode).toBe(200); - const json = response.json(); - expect(json).toMatchObject({ resources: expect.any(Array) }); - const resources = json.resources; - return resources; - } -}); diff --git a/twake/backend/node/test/e2e/messages/messages.threads.spec.ts b/twake/backend/node/test/e2e/messages/messages.threads.spec.ts deleted file mode 100644 index 98d798a2..00000000 --- a/twake/backend/node/test/e2e/messages/messages.threads.spec.ts +++ /dev/null @@ -1,249 +0,0 @@ -import "reflect-metadata"; -import { afterEach, beforeEach, describe, expect, it } from "@jest/globals"; -import { init, TestPlatform } from "../setup"; -import { ResourceUpdateResponse } from "../../../src/utils/types"; -import { deserialize } from "class-transformer"; -import { v1 as uuidv1, v4 as uuidv4 } from "uuid"; -import { Thread } from "../../../src/services/messages/entities/threads"; -import { createMessage, createParticipant, e2e_createThread } from "./utils"; -import gr from "../../../src/services/global-resolver"; - -describe("The Messages Threads feature", () => { - const url = "/internal/services/messages/v1"; - let platform: TestPlatform; - - beforeEach(async () => { - platform = await init({ - services: [ - "webserver", - "database", - "applications", - "search", - "storage", - "message-queue", - "user", - "search", - "files", - "websocket", - "messages", - "auth", - "realtime", - "channels", - "counter", - "statistics", - "platform-services", - ], - }); - }); - - afterEach(async () => { - await platform.tearDown(); - }); - - describe("On user manage threads", () => { - it("should create new thread", async done => { - const response = await e2e_createThread( - platform, - [ - createParticipant( - { - type: "user", - id: platform.currentUser.id, - }, - platform, - ), - ], - createMessage( - { - text: "Hello!", - }, - platform, - ), - ); - - const result: ResourceUpdateResponse = deserialize( - ResourceUpdateResponse, - response.body, - ); - - expect(response.statusCode).toBe(200); - expect(result.resource?.created_by).toBe(platform.currentUser.id); - expect(result.resource.participants.length).toBe(1); - expect(result.resource.participants[0]).toMatchObject({ - type: "user", - id: platform.currentUser.id, - created_by: platform.currentUser.id, - }); - expect(result.resource.participants[0].created_at).toBeDefined(); - - done(); - }); - - it("should enforce requester in thread participants", async done => { - const response = await e2e_createThread( - platform, - [ - createParticipant( - { - type: "user", - id: uuidv1(), - }, - platform, - ), - ], - createMessage( - { - text: "Hello!", - }, - platform, - ), - ); - - const result: ResourceUpdateResponse = deserialize( - ResourceUpdateResponse, - response.body, - ); - - expect(response.statusCode).toBe(200); - expect(result.resource).toMatchObject({ - created_by: platform.currentUser.id, - }); - expect(result.resource.participants.length).toBe(2); - expect( - result.resource.participants.filter(p => p.id === platform.currentUser.id)[0], - ).toMatchObject({ - type: "user", - id: platform.currentUser.id, - }); - - done(); - }); - - it("should update thread participants when add participant", async done => { - //Create thread - const thread = await gr.services.messages.threads.save( - { - id: undefined, - participants: [ - { - type: "user", - id: platform.currentUser.id, - company_id: platform.workspace.company_id, - }, - ], - }, - { - message: createMessage( - { - text: "Hello!", - }, - platform, - ), - }, - getContext(platform), - ); - - const jwtToken = await platform.auth.getJWTToken(); - const response = await platform.app.inject({ - method: "POST", - url: `${url}/companies/${platform.workspace.company_id}/threads/${thread.entity.id}`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - payload: { - resource: {}, - options: { - participants: { - add: [ - { - type: "user", - id: uuidv1(), - }, - ], - }, - }, - }, - }); - - const result: ResourceUpdateResponse = deserialize( - ResourceUpdateResponse, - response.body, - ); - - expect(response.statusCode).toBe(200); - expect(result.resource.participants.length).toBe(2); - - done(); - }); - - it("should update thread participants when remove participant", async done => { - //Create thread - const thread = await gr.services.messages.threads.save( - { - id: undefined, - participants: [ - { - type: "user", - id: platform.currentUser.id, - company_id: platform.workspace.company_id, - }, - { - type: "channel", - id: uuidv4(), - workspace_id: platform.workspace.workspace_id, - company_id: platform.workspace.company_id, - }, - ], - }, - { - message: createMessage( - { - text: "Hello!", - }, - platform, - ), - }, - getContext(platform), - ); - - const jwtToken = await platform.auth.getJWTToken(); - const response = await platform.app.inject({ - method: "POST", - url: `${url}/companies/${platform.workspace.company_id}/threads/${thread.entity.id}`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - payload: { - resource: {}, - options: { - participants: { - remove: [ - { - type: "user", - id: platform.currentUser.id, - }, - ], - }, - }, - }, - }); - - const result: ResourceUpdateResponse = deserialize( - ResourceUpdateResponse, - response.body, - ); - - expect(response.statusCode).toBe(200); - expect(result.resource.participants.length).toBe(1); - - done(); - }); - }); -}); - -function getContext(platform: TestPlatform) { - return { - company: { id: platform.workspace.company_id }, - user: { id: platform.currentUser.id }, - }; -} diff --git a/twake/backend/node/test/e2e/messages/messages.user-bookmarks.realtime.spec.ts b/twake/backend/node/test/e2e/messages/messages.user-bookmarks.realtime.spec.ts deleted file mode 100644 index a407e009..00000000 --- a/twake/backend/node/test/e2e/messages/messages.user-bookmarks.realtime.spec.ts +++ /dev/null @@ -1,143 +0,0 @@ -import "reflect-metadata"; -import { afterEach, beforeEach, describe, expect, it } from "@jest/globals"; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import io from "socket.io-client"; -import { init, TestPlatform } from "../setup"; -import gr from "../../../src/services/global-resolver"; - -describe("The Bookmarks Realtime feature", () => { - const url = "/internal/services/messages/v1"; - let platform: TestPlatform; - let socket: SocketIOClient.Socket; - - beforeEach(async () => { - platform = await init({ - services: [ - "webserver", - "database", - "search", - "storage", - "message-queue", - "files", - "user", - "websocket", - "webserver", - "messages", - "applications", - "auth", - "search", - "realtime", - "channels", - "counter", - "statistics", - "platform-services", - ], - }); - }); - - afterEach(async () => { - await platform.tearDown(); - platform = null; - }); - - function connect() { - socket = io.connect("http://localhost:3000", { path: "/socket" }); - socket.connect(); - } - - describe("On bookmark creation", () => { - it("should notify the client", async done => { - const jwtToken = await platform.auth.getJWTToken(); - const roomToken = "twake"; - - connect(); - socket.on("connect", () => { - socket - .emit("authenticate", { token: jwtToken }) - .on("authenticated", () => { - socket.on("realtime:join:error", () => done(new Error("Should not occur"))); - socket.on("realtime:join:success", async () => { - await gr.services.messages.userBookmarks.save( - { - company_id: platform.workspace.company_id, - user_id: platform.currentUser.id, - name: "mybookmarksaved", - id: undefined, - }, - getContext(platform), - ); - }); - socket.on("realtime:resource", (event: any) => { - expect(event.type).toEqual("user_message_bookmark"); - expect(event.action).toEqual("saved"); - expect(event.resource.name).toEqual("mybookmarksaved"); - done(); - }); - socket.emit("realtime:join", { - name: `/companies/${platform.workspace.company_id}/messages/bookmarks`, - token: roomToken, - }); - }) - .on("unauthorized", () => { - done(new Error("Should not occur")); - }); - }); - }); - }); - - describe("On bookmark removal", () => { - it("should notify the client", async done => { - const instance = await gr.services.messages.userBookmarks.save( - { - company_id: platform.workspace.company_id, - user_id: platform.currentUser.id, - name: "mybookmark", - id: undefined, - }, - getContext(platform), - ); - - const jwtToken = await platform.auth.getJWTToken(); - const roomToken = "twake"; - - connect(); - socket.on("connect", () => { - socket - .emit("authenticate", { token: jwtToken }) - .on("authenticated", () => { - socket.on("realtime:join:error", () => done(new Error("Should not occur"))); - socket.on("realtime:join:success", async () => { - await gr.services.messages.userBookmarks.delete( - { - company_id: platform.workspace.company_id, - user_id: platform.currentUser.id, - id: instance.entity.id, - }, - getContext(platform), - ); - }); - socket.on("realtime:resource", (event: any) => { - expect(event.type).toEqual("user_message_bookmark"); - expect(event.action).toEqual("deleted"); - done(); - }); - socket.emit("realtime:join", { - name: `/companies/${platform.workspace.company_id}/messages/bookmarks`, - token: roomToken, - }); - }) - .on("unauthorized", () => { - done(new Error("Should not occur")); - }); - }); - }); - }); -}); - -function getContext(platform: TestPlatform) { - return { - company: { id: platform.workspace.company_id }, - user: { id: platform.currentUser.id }, - }; -} diff --git a/twake/backend/node/test/e2e/messages/messages.user-bookmarks.spec.ts b/twake/backend/node/test/e2e/messages/messages.user-bookmarks.spec.ts deleted file mode 100644 index 68cb0240..00000000 --- a/twake/backend/node/test/e2e/messages/messages.user-bookmarks.spec.ts +++ /dev/null @@ -1,217 +0,0 @@ -import "reflect-metadata"; -import { afterEach, beforeEach, describe, expect, it } from "@jest/globals"; -import { init, TestPlatform } from "../setup"; -import { UserMessageBookmark } from "../../../src/services/messages/entities/user-message-bookmarks"; -import { ResourceListResponse, ResourceUpdateResponse } from "../../../src/utils/types"; -import { deserialize } from "class-transformer"; -import { v4 as uuidv4 } from "uuid"; -import gr from "../../../src/services/global-resolver"; - -describe("The Messages User Bookmarks feature", () => { - const url = "/internal/services/messages/v1"; - let platform: TestPlatform; - - beforeEach(async () => { - platform = await init({ - services: [ - "webserver", - "database", - "search", - "files", - "storage", - "applications", - "message-queue", - "user", - "search", - "websocket", - "messages", - "auth", - "realtime", - "channels", - "counter", - "statistics", - "platform-services", - ], - }); - }); - - afterEach(async () => { - await platform.tearDown(); - }); - - describe("On user manage bookmmarks", () => { - it("should create new bookmark", async done => { - const jwtToken = await platform.auth.getJWTToken(); - const response = await platform.app.inject({ - method: "POST", - url: `${url}/companies/${platform.workspace.company_id}/preferences/bookmarks/`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - payload: { - resource: { - name: "mybookmark", - }, - }, - }); - - const result: ResourceUpdateResponse = deserialize( - ResourceUpdateResponse, - response.body, - ); - - expect(response.statusCode).toBe(200); - expect(result.resource).toMatchObject({ - user_id: platform.currentUser.id, - name: "mybookmark", - }); - - const context = getContext(platform); - - const list = await gr.services.messages.userBookmarks.list({ - user_id: context.user.id, - company_id: context.company.id, - }); - expect(list.getEntities().length).toBe(1); - - done(); - }); - - it("should prevent duplicated bookmark", async done => { - // const uuid = uuidv4(); - - const context = getContext(platform); - - const data = await gr.services.messages.userBookmarks.save( - { - company_id: platform.workspace.company_id, - user_id: platform.currentUser.id, - name: "mybookmark", - test: "123", - }, - context, - ); - const uuid = data.entity.id; - - const jwtToken = await platform.auth.getJWTToken(); - const response = await platform.app.inject({ - method: "POST", - url: `${url}/companies/${platform.workspace.company_id}/preferences/bookmarks/`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - payload: { - resource: { - name: "mybookmark", - }, - }, - }); - - const result: ResourceUpdateResponse = deserialize( - ResourceUpdateResponse, - response.body, - ); - - expect(response.statusCode).toBe(200); - expect(result.resource).toMatchObject({ - id: uuid, - user_id: platform.currentUser.id, - name: "mybookmark", - }); - - const list = await gr.services.messages.userBookmarks.list({ - user_id: context.user.id, - company_id: context.company.id, - }); - expect(list.getEntities().length).toBe(1); - - done(); - }); - - it("should remove bookmark", async done => { - const id = uuidv4(); - const context = getContext(platform); - - await gr.services.messages.userBookmarks.save( - { - id, - company_id: platform.workspace.company_id, - user_id: platform.currentUser.id, - name: "mybookmark", - }, - context, - ); - - let list = await gr.services.messages.userBookmarks.list({ - user_id: context.user.id, - company_id: context.company.id, - }); - expect(list.getEntities().length).toBe(1); - - const jwtToken = await platform.auth.getJWTToken(); - const response = await platform.app.inject({ - method: "DELETE", - url: `${url}/companies/${platform.workspace.company_id}/preferences/bookmarks/${id}`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - }); - - expect(response.statusCode).toBe(200); - - list = await gr.services.messages.userBookmarks.list({ - user_id: context.user.id, - company_id: context.company.id, - }); - expect(list.getEntities().length).toBe(0); - - done(); - }); - - it("should list bookmarks", async done => { - const context = getContext(platform); - - await gr.services.messages.userBookmarks.save( - { - id: uuidv4(), - company_id: platform.workspace.company_id, - user_id: platform.currentUser.id, - name: "mybookmark", - }, - context, - ); - - const list = await gr.services.messages.userBookmarks.list({ - user_id: context.user.id, - company_id: context.company.id, - }); - expect(list.getEntities().length).toBe(1); - - const jwtToken = await platform.auth.getJWTToken(); - const response = await platform.app.inject({ - method: "GET", - url: `${url}/companies/${platform.workspace.company_id}/preferences/bookmarks`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - }); - - const result: ResourceListResponse = deserialize( - ResourceListResponse, - response.body, - ); - - expect(response.statusCode).toBe(200); - expect(result.resources.length).toBe(1); - expect(result.resources[0].name).toBe("mybookmark"); - done(); - }); - }); -}); - -function getContext(platform: TestPlatform) { - return { - company: { id: platform.workspace.company_id }, - user: { id: platform.currentUser.id }, - }; -} diff --git a/twake/backend/node/test/e2e/messages/messages.views.channel.spec.ts b/twake/backend/node/test/e2e/messages/messages.views.channel.spec.ts deleted file mode 100644 index 14b00298..00000000 --- a/twake/backend/node/test/e2e/messages/messages.views.channel.spec.ts +++ /dev/null @@ -1,144 +0,0 @@ -import "reflect-metadata"; -import { afterEach, beforeEach, describe, expect, it } from "@jest/globals"; -import { init, TestPlatform } from "../setup"; -import { ResourceListResponse, ResourceUpdateResponse } from "../../../src/utils/types"; -import { deserialize } from "class-transformer"; -import { v4 as uuidv4 } from "uuid"; -import { Thread } from "../../../src/services/messages/entities/threads"; -import { - createMessage, - createParticipant, - e2e_createChannel, - e2e_createMessage, - e2e_createThread, -} from "./utils"; -import { MessageWithReplies } from "../../../src/services/messages/types"; - -describe("The Messages feature", () => { - const url = "/internal/services/messages/v1"; - let platform: TestPlatform; - - beforeEach(async () => { - platform = await init({ - services: [ - "webserver", - "database", - "search", - "storage", - "files", - "applications", - "message-queue", - "user", - "websocket", - "messages", - "auth", - "search", - "realtime", - "channels", - "counter", - "statistics", - "platform-services", - ], - }); - }); - - afterEach(async () => { - await platform.tearDown(); - }); - - describe("On user use messages in channel view", () => { - it("should create a message and retrieve it in channel view", async () => { - const channel = await e2e_createChannel(platform, [platform.currentUser.id]); - - const response = await e2e_createThread( - platform, - [ - createParticipant( - { - type: "channel", - id: channel.resource.id, - workspace_id: channel.resource.workspace_id, - company_id: channel.resource.company_id, - }, - platform, - ), - ], - createMessage({ text: "Initial thread 1 message" }), - ); - const result: ResourceUpdateResponse = deserialize( - ResourceUpdateResponse, - response.body, - ); - const threadId = result.resource.id; - - await e2e_createMessage(platform, threadId, createMessage({ text: "Reply 1" })); - - await e2e_createMessage(platform, threadId, createMessage({ text: "Reply 2" })); - - await e2e_createThread( - platform, - [ - createParticipant( - { - type: "channel", - id: channel.resource.id, - workspace_id: channel.resource.workspace_id, - company_id: channel.resource.company_id, - }, - platform, - ), - ], - createMessage({ text: "Initial thread 2 message" }), - ); - - await e2e_createMessage(platform, threadId, createMessage({ text: "Reply 3" })); - - await e2e_createThread( - platform, - [ - createParticipant( - { - type: "channel", - id: channel.resource.id, - workspace_id: channel.resource.workspace_id, - company_id: channel.resource.company_id, - }, - platform, - ), - ], - createMessage({ text: "Initial thread 3 message" }), - ); - - const jwtToken = await platform.auth.getJWTToken(); - const listResponse = await platform.app.inject({ - method: "GET", - url: `${url}/companies/${channel.resource.company_id}/workspaces/${channel.resource.workspace_id}/channels/${channel.resource.id}/feed?replies_per_thread=3&include_users=1`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - }); - - const listResult: ResourceListResponse = deserialize( - ResourceListResponse, - listResponse.body, - ); - - await new Promise(resolve => setTimeout(resolve, 5000)); - - expect(listResponse.statusCode).toBe(200); - expect(listResult.resources.length).toBe(3); - - expect(listResult.resources[0].text).toBe("Initial thread 2 message"); - expect(listResult.resources[1].text).toBe("Initial thread 1 message"); - expect(listResult.resources[2].text).toBe("Initial thread 3 message"); - - expect(listResult.resources[0].stats.replies).toBe(1); - expect(listResult.resources[1].stats.replies).toBe(4); //Thread initial message + 3 replies - expect(listResult.resources[2].stats.replies).toBe(1); - - expect(listResult.resources[1].last_replies.length).toBe(3); //We requested 3 replies per posts - expect(listResult.resources[1].last_replies[0].text).toBe("Reply 1"); //Check order is OK - expect(listResult.resources[1].last_replies[2].text).toBe("Reply 3"); - }); - }); -}); diff --git a/twake/backend/node/test/e2e/messages/utils.ts b/twake/backend/node/test/e2e/messages/utils.ts deleted file mode 100644 index 6a5380ec..00000000 --- a/twake/backend/node/test/e2e/messages/utils.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { deserialize } from "class-transformer"; -import { - getInstance as getMessageInstance, - Message, -} from "../../../src/services/messages/entities/messages"; -import { ParticipantObject } from "../../../src/services/messages/entities/threads"; -import { Channel, ResourceCreateResponse } from "../../../src/utils/types"; -import { TestPlatform } from "../setup"; - -const url = "/internal/services/messages/v1"; - -export const e2e_createChannel = async ( - platform: TestPlatform, - members: string[], - owner?: string, -) => { - const jwtToken = await platform.auth.getJWTToken({ sub: owner || platform.currentUser.id }); - const response = await platform.app.inject({ - method: "POST", - url: `/internal/services/channels/v1/companies/${platform.workspace.company_id}/workspaces/direct/channels`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - payload: { - options: { - members, - }, - resource: { - description: "A direct channel description", - visibility: "direct", - }, - }, - }); - const channelCreateResult: ResourceCreateResponse = deserialize( - ResourceCreateResponse, - response.body, - ); - return channelCreateResult; -}; - -export const e2e_createThread = async ( - platform: TestPlatform, - participants: ParticipantObject[], - message: Message, - owner?: string, -) => { - const jwtToken = await platform.auth.getJWTToken({ sub: owner || platform.currentUser.id }); - const res = await platform.app.inject({ - method: "POST", - url: `${url}/companies/${platform.workspace.company_id}/threads`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - payload: { - resource: { - participants: participants || [], - }, - options: { - message: message, - }, - }, - }); - await new Promise(resolve => setTimeout(resolve, 200)); - return res; -}; - -export const e2e_createMessage = async ( - platform: TestPlatform, - threadId: string, - message: Message, -) => { - const jwtToken = await platform.auth.getJWTToken( - message.user_id ? { sub: message.user_id } : undefined, - ); - const res = await platform.app.inject({ - method: "POST", - url: `${url}/companies/${platform.workspace.company_id}/threads/${threadId}/messages`, - headers: { - authorization: `Bearer ${jwtToken}`, - }, - payload: { - resource: message, - }, - }); - await new Promise(resolve => setTimeout(resolve, 200)); - return res; -}; - -export const createMessage = (message: Partial, platform?: TestPlatform): Message => { - return getMessageInstance({ - //Default values - created_at: new Date().getTime(), - user_id: platform?.currentUser.id || undefined, - - ...message, - }); -}; - -export const createParticipant = ( - participant: Partial, - platform?: TestPlatform, -): ParticipantObject => { - return { - //Default values: - created_at: new Date().getTime(), - created_by: platform?.currentUser.id || "", - company_id: platform?.workspace.company_id || "", - workspace_id: platform?.workspace.workspace_id || "", - id: platform?.currentUser.id || "", - type: "user", - - ...participant, - }; -}; diff --git a/twake/backend/node/test/unit/core/services/pubsub/processor/pubsubserviceprocessor.test.ts b/twake/backend/node/test/unit/core/services/pubsub/processor/pubsubserviceprocessor.test.ts deleted file mode 100644 index 854f301e..00000000 --- a/twake/backend/node/test/unit/core/services/pubsub/processor/pubsubserviceprocessor.test.ts +++ /dev/null @@ -1,305 +0,0 @@ -import { describe, expect, it, jest, beforeEach, afterEach } from "@jest/globals"; -import { - IncomingMessageQueueMessage, - MessageQueueHandler, - MessageQueueServiceAPI, - MessageQueueServiceProcessor, -} from "../../../../../../src/core/platform/services/message-queue/api"; - -describe("The MessageQueueServiceProcessor class", () => { - let pubsubService: MessageQueueServiceAPI; - let subscribe; - let publish; - let topic; - let errorTopic; - let outTopic; - - beforeEach(() => { - topic = "inputtopic"; - errorTopic = "errorTopic"; - outTopic = "outTopic"; - subscribe = jest.fn(); - publish = jest.fn(); - pubsubService = { - publish, - subscribe, - } as unknown as MessageQueueServiceAPI; - }); - - afterEach(() => { - jest.restoreAllMocks(); - jest.resetAllMocks(); - }); - - describe("The init function", () => { - it("should not subscribe when topics is not defined", async () => { - subscribe.mockResolvedValue(true); - const processor = new MessageQueueServiceProcessor( - {} as MessageQueueHandler, - pubsubService, - ); - - await processor.init(); - expect(subscribe).not.toHaveBeenCalled; - }); - - it("should not subscribe when topics.in is not defined", async () => { - subscribe.mockResolvedValue(true); - const processor = new MessageQueueServiceProcessor( - { - topics: {}, - } as MessageQueueHandler, - pubsubService, - ); - - await processor.init(); - expect(subscribe).not.toHaveBeenCalled; - }); - - it("should subscribe when topics.in is defined", async () => { - subscribe.mockResolvedValue(true); - const processor = new MessageQueueServiceProcessor( - { - topics: { - in: topic, - }, - } as MessageQueueHandler, - pubsubService, - ); - - await processor.init(); - expect(subscribe).toHaveBeenCalledTimes(1); - expect(subscribe).toHaveBeenCalledWith(topic, expect.any(Function), undefined); - }); - }); - - describe("The function handling incoming messages", () => { - describe("When handler.validate is defined", () => { - it("should not process data when data is not valid", async () => { - const message = { - data: "foo", - } as IncomingMessageQueueMessage; - const validate = jest.fn().mockReturnValue(false); - const process = jest.fn().mockReturnValue(true); - const processor = new MessageQueueServiceProcessor( - { - topics: { - in: topic, - }, - validate, - process, - } as unknown as MessageQueueHandler, - pubsubService, - ); - - await processor.init(); - const processMessage = subscribe.mock.calls[0][1]; - await processMessage(message); - - expect(validate).toBeCalledTimes(1); - expect(validate).toBeCalledWith(message.data); - expect(process).not.toHaveBeenCalled; - }); - - it("should process data when data is valid", async () => { - const message = { - data: "foo", - } as IncomingMessageQueueMessage; - const validate = jest.fn().mockReturnValue(true); - const process = jest.fn().mockReturnValue(true); - const processor = new MessageQueueServiceProcessor( - { - topics: { - in: topic, - }, - validate, - process, - } as unknown as MessageQueueHandler, - pubsubService, - ); - - await processor.init(); - const processMessage = subscribe.mock.calls[0][1]; - await processMessage(message); - - expect(validate).toBeCalledTimes(1); - expect(validate).toBeCalledWith(message.data); - expect(process).toHaveBeenCalled; - }); - }); - - describe("When processing message", () => { - it("should not publish error when topics.error is not defined", async () => { - const message = { - data: "foo", - } as IncomingMessageQueueMessage; - const process = jest.fn().mockRejectedValue(new Error("I failed to process")); - const processor = new MessageQueueServiceProcessor( - { - topics: { - in: topic, - }, - process, - } as unknown as MessageQueueHandler, - pubsubService, - ); - - await processor.init(); - const processMessage = subscribe.mock.calls[0][1]; - await processMessage(message); - - expect(process).toBeCalledTimes(1); - expect(process).toHaveBeenCalledWith(message.data); - expect(publish).not.toBeCalled; - }); - - it("should publish error when topics.error is defined", async () => { - const message = { - data: "foo", - } as IncomingMessageQueueMessage; - const process = jest.fn().mockRejectedValue(new Error("I failed to process")); - const processor = new MessageQueueServiceProcessor( - { - topics: { - in: topic, - error: errorTopic, - }, - process, - } as unknown as MessageQueueHandler, - pubsubService, - ); - - await processor.init(); - const processMessage = subscribe.mock.calls[0][1]; - await processMessage(message); - - expect(process).toBeCalledTimes(1); - expect(process).toHaveBeenCalledWith(message.data); - expect(publish).toBeCalledTimes(1); - expect(publish).toBeCalledWith(errorTopic, expect.anything()); - }); - - it("should not publish when processing does not return result", async () => { - const message = { - data: "foo", - } as IncomingMessageQueueMessage; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const process = jest.fn().mockResolvedValue(null); - const processor = new MessageQueueServiceProcessor( - { - topics: { - in: topic, - out: outTopic, - error: errorTopic, - }, - process, - } as unknown as MessageQueueHandler, - pubsubService, - ); - - await processor.init(); - const processMessage = subscribe.mock.calls[0][1]; - await processMessage(message); - - expect(process).toBeCalledTimes(1); - expect(process).toHaveBeenCalledWith(message.data); - expect(publish).not.toBeCalled; - }); - - it("should not publish when out topic is not defined", async () => { - const message = { - data: "foo", - } as IncomingMessageQueueMessage; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const process = jest.fn().mockResolvedValue(true); - const processor = new MessageQueueServiceProcessor( - { - topics: { - in: topic, - error: errorTopic, - }, - process, - } as unknown as MessageQueueHandler, - pubsubService, - ); - - await processor.init(); - const processMessage = subscribe.mock.calls[0][1]; - await processMessage(message); - - expect(process).toBeCalledTimes(1); - expect(process).toHaveBeenCalledWith(message.data); - expect(publish).not.toBeCalled; - }); - - it("should publish when out topic is defined and process returns result", async () => { - const result = "processing result"; - const message = { - data: "foo", - } as IncomingMessageQueueMessage; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const process = jest.fn().mockResolvedValue(result); - const processor = new MessageQueueServiceProcessor( - { - topics: { - in: topic, - error: errorTopic, - out: outTopic, - }, - process, - } as unknown as MessageQueueHandler, - pubsubService, - ); - - await processor.init(); - const processMessage = subscribe.mock.calls[0][1]; - await processMessage(message); - - expect(process).toBeCalledTimes(1); - expect(process).toHaveBeenCalledWith(message.data); - expect(publish).toBeCalledTimes(1); - expect(publish).toBeCalledWith( - outTopic, - expect.objectContaining({ - data: result, - }), - ); - }); - - it("should publish error when result can not be published", async () => { - publish.mockRejectedValue(new Error("I failed to publish")); - const result = "processing result"; - const message = { - data: "foo", - } as IncomingMessageQueueMessage; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const process = jest.fn().mockResolvedValue(result); - const processor = new MessageQueueServiceProcessor( - { - topics: { - in: topic, - error: errorTopic, - out: outTopic, - }, - process, - } as unknown as MessageQueueHandler, - pubsubService, - ); - - await processor.init(); - const processMessage = subscribe.mock.calls[0][1]; - await processMessage(message); - - expect(process).toBeCalledTimes(1); - expect(process).toHaveBeenCalledWith(message.data); - expect(publish).toBeCalledTimes(2); - expect(publish.mock.calls[0][0]).toEqual(outTopic); - expect(publish.mock.calls[1][0]).toEqual(errorTopic); - }); - }); - }); -}); diff --git a/twake/backend/node/test/unit/services/notifications/services/engine/processors/new-channel-message/index.test.ts b/twake/backend/node/test/unit/services/notifications/services/engine/processors/new-channel-message/index.test.ts deleted file mode 100644 index 4688d431..00000000 --- a/twake/backend/node/test/unit/services/notifications/services/engine/processors/new-channel-message/index.test.ts +++ /dev/null @@ -1,504 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals"; -import { - ListResult, - OperationType, - SaveResult, -} from "../../../../../../../../src/core/platform/framework/api/crud-service"; -import { ChannelMemberNotificationLevel } from "../../../../../../../../src/services/channels/types"; -import { MessageNotification } from "../../../../../../../../src/services/messages/types"; -import { - ChannelMemberNotificationPreference, - ChannelThreadUsers, -} from "../../../../../../../../src/services/notifications/entities"; -import { ChannelType } from "../../../../../../../../src/utils/types"; -import gr from "../../../../../../../../src/services/global-resolver"; -import { NewChannelMessageProcessor } from "../../../../../../../../src/services/notifications/services/engine/processors/new-channel-message"; - -describe("The NewChannelMessageProcessor class", () => { - let channel_id, company_id, workspace_id, thread_id; - let service: any; - let processor: NewChannelMessageProcessor; - let getUsersInThread; - let getChannelPreferencesForUsers; - - beforeEach(() => { - channel_id = "channel_id"; - company_id = "company_id"; - workspace_id = "workspace_id"; - - getUsersInThread = jest.fn(); - getChannelPreferencesForUsers = jest.fn(); - - setPreferences(); - setUsersInThread(); - - service = { - channelThreads: { - bulkSave: jest - .fn() - .mockResolvedValue( - new SaveResult("thread", [], OperationType.CREATE) as never, - ), - getUsersInThread, - }, - channelPreferences: { - getChannelPreferencesForUsers, - }, - }; - - gr.services = { - notifications: service, - } as any; - - processor = new NewChannelMessageProcessor(); - }); - - afterEach(() => { - jest.restoreAllMocks(); - jest.resetAllMocks(); - }); - - function getMessage(newThread: boolean = false): MessageNotification { - const message = { - company_id, - workspace_id, - channel_id, - id: "id", - thread_id: "thread_id", - sender: "sender", - mentions: { - users: [], - }, - } as MessageNotification; - - if (newThread) { - // message is new thread when thread_id is not defined - message.thread_id = undefined; - } - - return message; - } - - function setPreferences(preferences: ChannelMemberNotificationPreference[] = []): void { - getChannelPreferencesForUsers.mockResolvedValue( - new ListResult("preferences", preferences), - ); - } - - function setUsersInThread(users: string[] = []) { - getUsersInThread.mockResolvedValue( - new ListResult( - "users", - users.map( - user_id => - ({ - company_id, - channel_id, - workspace_id, - thread_id, - user_id, - } as ChannelThreadUsers), - ), - ), - ); - } - - describe("The process method", () => { - describe("When message is a new thread", () => { - function setNewThreadPreferences() { - setPreferences([ - { - channel_id, - company_id, - user_id: "1", - last_read: Date.now(), - preferences: ChannelMemberNotificationLevel.ALL, - }, - { - channel_id, - company_id, - user_id: "2", - last_read: Date.now(), - preferences: ChannelMemberNotificationLevel.MENTIONS, - }, - { - channel_id, - company_id, - user_id: "3", - last_read: Date.now(), - preferences: ChannelMemberNotificationLevel.ME, - }, - { - channel_id, - company_id, - user_id: "4", - last_read: Date.now(), - preferences: ChannelMemberNotificationLevel.NONE, - }, - ]); - } - - it("should return undefined when there is no one to notify", async done => { - const message = getMessage(true); - const result = await processor.process(message); - - expect(service.channelThreads.bulkSave).toBeCalledTimes(1); - expect(service.channelThreads.bulkSave).toBeCalledWith( - [ - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.id, - user_id: message.sender, - }, - ], - undefined, - ); - - expect(service.channelThreads.getUsersInThread).not.toBeCalled; - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledTimes(1); - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledWith({ - company_id: message.company_id, - channel_id: message.channel_id, - }); - - expect(result).toBeUndefined(); - done(); - }); - - it("without mentions, should get the preferences for all channel members and return only the ones who want to be notified", async done => { - const message = getMessage(true); - setNewThreadPreferences(); - const result = await processor.process(message); - - expect(service.channelThreads.bulkSave).toBeCalledTimes(1); - expect(service.channelThreads.bulkSave).toBeCalledWith( - [ - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.id, - user_id: message.sender, - }, - ], - undefined, - ); - - expect(service.channelThreads.getUsersInThread).not.toBeCalled; - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledTimes(1); - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledWith({ - company_id: message.company_id, - channel_id: message.channel_id, - }); - - expect(result.mentions.users).toEqual(["1"]); - done(); - }); - - it("with @all mention, should get the preferences for all channel members and return only the ones who want to be notified", async done => { - const message = getMessage(true); - message.mentions.specials = ["all"]; - setNewThreadPreferences(); - const result = await processor.process(message); - - expect(service.channelThreads.bulkSave).toBeCalledTimes(1); - expect(service.channelThreads.bulkSave).toBeCalledWith( - [ - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.id, - user_id: message.sender, - }, - ], - undefined, - ); - - expect(service.channelThreads.getUsersInThread).not.toBeCalled; - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledTimes(1); - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledWith({ - company_id: message.company_id, - channel_id: message.channel_id, - }); - - expect(result.mentions.users).toEqual(["1", "2"]); - done(); - }); - - it("with @here mentions, should get the preferences for all channel members and return only the ones who want to be notified", async done => { - const message = getMessage(true); - message.mentions.specials = ["here"]; - setNewThreadPreferences(); - const result = await processor.process(message); - - expect(service.channelThreads.bulkSave).toBeCalledTimes(1); - expect(service.channelThreads.bulkSave).toBeCalledWith( - [ - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.id, - user_id: message.sender, - }, - ], - undefined, - ); - - expect(service.channelThreads.getUsersInThread).not.toBeCalled; - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledTimes(1); - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledWith({ - company_id: message.company_id, - channel_id: message.channel_id, - }); - - expect(result.mentions.users).toEqual(["1", "2"]); - done(); - }); - - it("with @user mentions, should get the preferences for all channel members and return only the ones who want to be notified", async done => { - const message = getMessage(true); - message.mentions.users = ["1", "2", "3", "4"]; - setNewThreadPreferences(); - const result = await processor.process(message); - - expect(service.channelThreads.bulkSave).toBeCalledTimes(1); - expect(service.channelThreads.bulkSave).toBeCalledWith( - [ - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.id, - user_id: message.sender, - }, - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.id, - user_id: "1", - }, - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.id, - user_id: "2", - }, - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.id, - user_id: "3", - }, - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.id, - user_id: "4", - }, - ], - undefined, - ); - - expect(service.channelThreads.getUsersInThread).not.toBeCalled; - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledTimes(1); - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledWith({ - company_id: message.company_id, - channel_id: message.channel_id, - }); - - expect(result.mentions.users).toEqual(["1", "2", "3"]); - done(); - }); - - it("When message is a direct message", async done => { - const message = getMessage(true); - message.workspace_id = ChannelType.DIRECT; - message.mentions.users = ["1", "2", "3", "4"]; - setNewThreadPreferences(); - const result = await processor.process(message); - - expect(service.channelThreads.bulkSave).toBeCalledTimes(1); - expect(service.channelThreads.bulkSave).toBeCalledWith( - [ - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.id, - user_id: message.sender, - }, - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.id, - user_id: "1", - }, - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.id, - user_id: "2", - }, - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.id, - user_id: "3", - }, - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.id, - user_id: "4", - }, - ], - undefined, - ); - - expect(service.channelThreads.getUsersInThread).not.toBeCalled; - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledTimes(1); - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledWith({ - company_id: message.company_id, - channel_id: message.channel_id, - }); - - expect(result.mentions.users).toEqual(["1", "2", "3"]); - done(); - }); - }); - - describe("When message is a response to a thread", () => { - function setThreadResponsePreferences() { - setPreferences([ - { - channel_id, - company_id, - user_id: "1", - last_read: Date.now(), - preferences: ChannelMemberNotificationLevel.ALL, - }, - { - channel_id, - company_id, - user_id: "2", - last_read: Date.now(), - preferences: ChannelMemberNotificationLevel.MENTIONS, - }, - { - channel_id, - company_id, - user_id: "3", - last_read: Date.now(), - preferences: ChannelMemberNotificationLevel.ME, - }, - { - channel_id, - company_id, - user_id: "4", - last_read: Date.now(), - preferences: ChannelMemberNotificationLevel.NONE, - }, - ]); - } - - it("should return undefined when there is no one to notify", async done => { - const message = getMessage(); - setUsersInThread(["1", "2", "3", "4"]); - const result = await processor.process(message); - - expect(service.channelThreads.bulkSave).toBeCalledTimes(1); - expect(service.channelThreads.bulkSave).toBeCalledWith( - [ - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.thread_id, - user_id: message.sender, - }, - ], - undefined, - ); - - expect(service.channelThreads.getUsersInThread).toBeCalled; - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledTimes(1); - - expect(result).toBeUndefined(); - done(); - }); - - it("without mentions, should get the preferences for all members involved and return only the ones with preference !== NONE", async done => { - const message = getMessage(); - setUsersInThread(["1", "2", "3", "4"]); - setThreadResponsePreferences(); - const result = await processor.process(message); - - expect(service.channelThreads.bulkSave).toBeCalledTimes(1); - expect(service.channelThreads.bulkSave).toBeCalledWith( - [ - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.thread_id, - user_id: message.sender, - }, - ], - undefined, - ); - - expect(service.channelThreads.getUsersInThread).toBeCalled; - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledTimes(1); - - expect(result.mentions.users).toEqual(["1", "2", "3"]); - done(); - }); - - it("with @user mentions, should get the preferences for all members involved and return only the ones who want to be notified", async done => { - const message = getMessage(); - message.mentions.users = ["1", "2", "3", "4"]; - setUsersInThread(["1", "2", "3", "4"]); - setThreadResponsePreferences(); - const result = await processor.process(message); - - expect(service.channelThreads.bulkSave).toBeCalledTimes(1); - expect(service.channelThreads.bulkSave).toBeCalledWith( - [ - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.thread_id, - user_id: message.sender, - }, - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.thread_id, - user_id: "1", - }, - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.thread_id, - user_id: "2", - }, - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.thread_id, - user_id: "3", - }, - { - company_id: message.company_id, - channel_id: message.channel_id, - thread_id: message.thread_id, - user_id: "4", - }, - ], - undefined, - ); - - expect(service.channelThreads.getUsersInThread).toBeCalled; - expect(service.channelPreferences.getChannelPreferencesForUsers).toBeCalledTimes(1); - expect(result.mentions.users).toEqual(["1", "2", "3"]); - done(); - }); - }); - }); -}); diff --git a/twake/backend/node/test/unit/services/notifications/services/engine/processors/push-to-users/index.test.ts b/twake/backend/node/test/unit/services/notifications/services/engine/processors/push-to-users/index.test.ts deleted file mode 100644 index 11207143..00000000 --- a/twake/backend/node/test/unit/services/notifications/services/engine/processors/push-to-users/index.test.ts +++ /dev/null @@ -1,244 +0,0 @@ -import "reflect-metadata"; -import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals"; -import { - ListResult, - OperationType, - SaveResult, -} from "../../../../../../../../src/core/platform/framework/api/crud-service"; -import { MessageQueueServiceAPI } from "../../../../../../../../src/core/platform/services/message-queue/api"; -import { ChannelMemberNotificationLevel } from "../../../../../../../../src/services/channels/types"; -import { - ChannelMemberNotificationPreference, - ChannelThreadUsers, -} from "../../../../../../../../src/services/notifications/entities"; -import { - TYPE as UserNotificationBadgeType, - UserNotificationBadge, -} from "../../../../../../../../src/services/notifications/entities/user-notification-badges"; -import { MentionNotification } from "../../../../../../../../src/services/notifications/types"; -import { uniqueId } from "lodash"; -import { PushNotificationToUsersMessageProcessor } from "../../../../../../../../src/services/notifications/services/engine/processors/push-to-users"; -import gr from "../../../../../../../../src/services/global-resolver"; - -describe("The PushNotificationToUsersMessageProcessor class", () => { - let channel_id, company_id, workspace_id, thread_id; - let pubsubService: MessageQueueServiceAPI; - let processor: PushNotificationToUsersMessageProcessor; - let getUsersInThread; - let getChannelPreferencesForUsers; - let saveBadge; - - beforeEach(async () => { - channel_id = "channel_id"; - company_id = "company_id"; - workspace_id = "workspace_id"; - thread_id: "thread_id"; - - getUsersInThread = jest.fn(); - getChannelPreferencesForUsers = jest.fn(); - saveBadge = jest.fn(); - - setPreferences(); - setUsersInThread(); - - pubsubService = { - publish: jest.fn(), - } as unknown as MessageQueueServiceAPI; - // - const service = { - badges: { - save: saveBadge, - }, - channelPreferences: { - getChannelPreferencesForUsers, - }, - }; - - gr.platformServices = { - messageQueue: pubsubService, - } as any; - - gr.services = { - notifications: service, - } as any; - processor = new PushNotificationToUsersMessageProcessor(); - }); - - afterEach(() => { - jest.restoreAllMocks(); - jest.resetAllMocks(); - }); - - function getMessage(newThread: boolean = false): MentionNotification { - const message = { - company_id, - workspace_id, - channel_id, - message_id: "id", - creation_date: Date.now(), - thread_id, - mentions: { - users: [], - }, - } as MentionNotification; - - if (newThread) { - // message is new thread when thread_id is not defined - message.thread_id = undefined; - } - - return message; - } - - function setPreferences(preferences: ChannelMemberNotificationPreference[] = []): void { - getChannelPreferencesForUsers.mockResolvedValue( - new ListResult("preferences", preferences), - ); - } - - function setBadgeResults(badges: UserNotificationBadge[] = []): void { - badges.forEach(badge => - saveBadge.mockResolvedValueOnce( - new SaveResult(UserNotificationBadgeType, badge, OperationType.CREATE), - ), - ); - } - - function setUsersInThread(users: string[] = []) { - getUsersInThread.mockResolvedValue( - new ListResult( - "users", - users.map( - user_id => - ({ - company_id, - channel_id, - workspace_id, - thread_id, - user_id, - } as ChannelThreadUsers), - ), - ), - ); - } - - describe("The process method", () => { - it("will fail when channel_id is not defined", async () => { - const message = getMessage(); - delete message.channel_id; - - await expect(processor.process(message)).rejects.toThrowError("Missing required fields"); - }); - - it("will fail when company_id is not defined", async () => { - const message = getMessage(); - delete message.company_id; - - await expect(processor.process(message)).rejects.toThrowError("Missing required fields"); - }); - - it("will fail when workspace_id is not defined", async () => { - const message = getMessage(); - delete message.workspace_id; - - await expect(processor.process(message)).rejects.toThrowError("Missing required fields"); - }); - - it("will fail when creation_date is not defined", async () => { - const message = getMessage(); - delete message.creation_date; - - await expect(processor.process(message)).rejects.toThrowError("Missing required fields"); - }); - - it("will do nothing when mentions is not defined", async done => { - const message = getMessage(); - delete message.mentions; - - await processor.process(message); - - expect(gr.services.notifications.channelPreferences.getChannelPreferencesForUsers).not - .toBeCalled; - done(); - }); - - it("will do nothing when mentions.users is not defined", async done => { - const message = getMessage(); - message.mentions = { users: undefined }; - - await processor.process(message); - - expect(gr.services.notifications.channelPreferences.getChannelPreferencesForUsers).not - .toBeCalled; - done(); - }); - - it("will do nothing when mentions.users is empty", async done => { - const message = getMessage(); - message.mentions = { users: [] }; - - await processor.process(message); - - expect(gr.services.notifications.channelPreferences.getChannelPreferencesForUsers).not - .toBeCalled; - done(); - }); - - it("will keep users who did not read the channel yet", async done => { - const message = getMessage(); - - setPreferences([ - { - channel_id, - company_id, - last_read: Date.now(), - user_id: "1", - preferences: ChannelMemberNotificationLevel.ALL, - }, - { - channel_id, - company_id, - last_read: Date.now(), - user_id: "3", - preferences: ChannelMemberNotificationLevel.ALL, - }, - ]); - setBadgeResults([ - { - channel_id, - company_id, - thread_id, - user_id: "1", - workspace_id, - message_id: message.message_id, - id: uniqueId(), - }, - { - channel_id, - company_id, - thread_id, - user_id: "3", - workspace_id, - message_id: message.message_id, - id: uniqueId(), - }, - ]); - - const lessThan = message.creation_date; - const users = ["1", "2", "3", "4"]; - const channel = { - channel_id, - company_id, - }; - message.mentions = { users }; - - await processor.process(message); - - expect(getChannelPreferencesForUsers).toBeCalledWith(channel, users, { lessThan }, undefined); - expect(gr.services.notifications.badges.save).toBeCalledTimes(2); - expect(pubsubService.publish).toBeCalledTimes(2); - - done(); - }); - }); -}); diff --git a/twake/backend/node/test/unit/services/previews/services/files/processing/video.test.ts b/twake/backend/node/test/unit/services/previews/services/files/processing/video.test.ts deleted file mode 100644 index a7051a34..00000000 --- a/twake/backend/node/test/unit/services/previews/services/files/processing/video.test.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { describe, expect, it, jest, beforeEach, afterEach, afterAll } from "@jest/globals"; -import { generateVideoPreview } from "../../../../../../../src/services/previews/services/files/processing/video"; -import ffmpeg, { ffprobe } from "fluent-ffmpeg"; -import { cleanFiles, getTmpFile } from "../../../../../../../src/utils/files"; -import fs from "fs"; - -jest.mock("fluent-ffmpeg"); -jest.mock("../../../../../../../src/utils/files"); - -const ffmpegMock = { - screenshot: jest.fn().mockReturnValue({ - on: function (e, cb) { - cb(); - return this; - }, - }), -}; - -beforeEach(() => { - jest.spyOn(fs, "statSync").mockReturnValue({ size: 1 } as any); - (ffmpeg as any).mockImplementation(() => ffmpegMock); - (cleanFiles as any).mockImplementation(() => jest.fn()); - (getTmpFile as any).mockImplementation(() => "/tmp/file"); - (ffprobe as any).mockImplementation((i, cb) => { - cb(null, { - streams: [ - { - width: 320, - height: 240, - }, - ], - }); - }); -}); - -afterEach(() => { - jest.clearAllMocks(); -}); - -afterAll(() => { - jest.restoreAllMocks(); -}); - -describe("the generateVideoPreview function", () => { - it("should return a promise", () => { - const result = generateVideoPreview([]); - expect(result).toBeInstanceOf(Promise); - }); - - it("should attempt to create a screenshot using ffmpeg", async () => { - await generateVideoPreview(["/foo/bar"]); - expect(ffmpeg as any).toBeCalledWith("/foo/bar"); - }); - - it("should call ffmpeg screenshot method with the original video dimentions", async () => { - (getTmpFile as any).mockImplementation(() => "/tmp/some-random-file"); - (ffprobe as any).mockImplementation((i, cb) => { - cb(null, { - streams: [ - { - width: 500, - height: 500, - }, - ], - }); - }); - - await generateVideoPreview(["/foo/bar"]); - expect(ffmpegMock.screenshot).toBeCalledWith({ - count: 1, - filename: "some-random-file.jpg", - folder: "/tmp", - timemarks: ["0"], - size: "500x500", - }); - }); - - it("should attempt to generate a temporary file", async () => { - await generateVideoPreview(["/foo/bar"]); - - expect(getTmpFile).toBeCalled(); - }); - - it("should attempt to clean up the temporary file in case of errors", async () => { - const expectedError = new Error("failed to generate video preview: Error: foo"); - (ffmpeg as any).mockImplementation(() => { - throw new Error("foo"); - }); - - (ffprobe as any).mockImplementation((i, cb) => { - cb(null, { - streams: [ - { - width: 500, - height: 500, - }, - ], - }); - }); - - await expect(generateVideoPreview(["/foo/bar"])).rejects.toThrow(expectedError); - - expect(cleanFiles).toBeCalledWith(["/tmp/file.jpg"]); - }); - - it("should return the thumbnail information", async () => { - const result = await generateVideoPreview(["/foo/bar"]); - expect(result).toEqual([ - { - width: 320, - height: 240, - type: "image/jpg", - size: 1, - path: "/tmp/file.jpg", - }, - ]); - }); - - it("should generate thumbnails for multiple files", async () => { - const result = await generateVideoPreview(["/foo/bar", "/foo/baz", "/foo/tar"]); - expect(result).toHaveLength(3); - }); - - it("should use ffprobe to get the video dimensions", async () => { - await generateVideoPreview(["/foo/bar"]); - expect(ffprobe).toBeCalledWith("/foo/bar", expect.any(Function)); - }); - - it("should use 1080p as the maximum video dimensions", async () => { - (ffprobe as any).mockImplementation((i, cb) => { - cb(null, { - streams: [ - { - width: 5000, - height: 5000, - }, - ], - }); - }); - - const result = await generateVideoPreview(["/foo/bar"]); - expect(result).toEqual([ - { - width: 1920, - height: 1080, - type: "image/jpg", - size: 1, - path: "/tmp/file.jpg", - }, - ]); - }); - - it("should use 320x240 as the minimum video dimensions", async () => { - (ffprobe as any).mockImplementation((i, cb) => { - cb(null, { - streams: [ - { - width: 320, - height: 180, - }, - ], - }); - }); - - const result = await generateVideoPreview(["/foo/bar"]); - expect(result).toEqual([ - { - width: 320, - height: 240, - type: "image/jpg", - size: 1, - path: "/tmp/file.jpg", - }, - ]); - }); -}); diff --git a/twake/backend/node/test/unit/services/previews/services/links/processing/images.test.ts b/twake/backend/node/test/unit/services/previews/services/links/processing/images.test.ts deleted file mode 100644 index c3b0550b..00000000 --- a/twake/backend/node/test/unit/services/previews/services/links/processing/images.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { describe, expect, it, jest, beforeEach, afterEach, afterAll } from "@jest/globals"; -import { generateImageUrlPreview } from "../../../../../../../src/services/previews/services/links/processing/image"; - -import getFavicons from "get-website-favicon"; -import imageProbe from "probe-image-size"; - -jest.mock("get-website-favicon"); -jest.mock("probe-image-size"); - -beforeEach(() => { - (imageProbe as any).mockImplementation(() => ({ - width: 320, - height: 240, - })); - - (getFavicons as any).mockImplementation(() => ({ - icons: [ - { - src: "http://foo.bar/favicon.ico", - }, - ], - })); -}); - -afterEach(() => { - jest.clearAllMocks(); -}); - -afterAll(() => { - jest.restoreAllMocks(); -}); - -describe("the generateImageUrlPreview function", () => { - it("should return a promise", () => { - const result = generateImageUrlPreview("http://foo.bar"); - expect(result).toBeInstanceOf(Promise); - }); - - it("should generate a preview for a given image url", async () => { - const result = await generateImageUrlPreview("https://foo.bar/image.jpg"); - expect(result).toEqual({ - title: "image.jpg", // title should be the file name - domain: "foo.bar", - favicon: "http://foo.bar/favicon.ico", - url: "https://foo.bar/image.jpg", - img_height: 240, - img_width: 320, - description: null, - img: "https://foo.bar/image.jpg", - }); - }); - - it("should return nothing in case of error", async () => { - (imageProbe as any).mockImplementation(() => { - throw new Error("failed to probe image"); - }); - - const result = await generateImageUrlPreview("https://foo.bar/image.jpg"); - expect(result).toBeUndefined(); - }); -}); diff --git a/twake/backend/node/test/unit/services/previews/services/links/processing/links.test.ts b/twake/backend/node/test/unit/services/previews/services/links/processing/links.test.ts deleted file mode 100644 index 30eaa585..00000000 --- a/twake/backend/node/test/unit/services/previews/services/links/processing/links.test.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { describe, expect, it, jest, beforeEach, afterEach, afterAll } from "@jest/globals"; -import { generateLinkPreview } from "../../../../../../../src/services/previews/services/links/processing/link"; -import { parser } from "html-metadata-parser"; -import getFavicons from "get-website-favicon"; -import imageProbe from "probe-image-size"; - -jest.mock("html-metadata-parser"); -jest.mock("get-website-favicon"); -jest.mock("probe-image-size"); - -beforeEach(() => { - (imageProbe as any).mockImplementation(() => ({ - width: 320, - height: 240, - })); - - (getFavicons as any).mockImplementation(() => ({ - icons: [ - { - src: "http://foo.bar/favicon.ico", - }, - ], - })); - - (parser as any).mockImplementation(() => ({ - og: { - title: "Foo", - description: "Bar", - image: "http://foo.bar/image.jpg", - }, - meta: { - title: "Foo", - description: "Bar", - image: "http://foo.bar/image1.jpg", - }, - images: ["http://foo.bar/image2.jpg"], - })); -}); - -afterEach(() => { - jest.clearAllMocks(); -}); - -afterAll(() => { - jest.restoreAllMocks(); -}); - -describe("the generateLinkPreview service", () => { - it("should return a promise", () => { - const result = generateLinkPreview("http://foo.bar"); - expect(result).toBeInstanceOf(Promise); - }); - - it("should return a promise that resolves to a preview", async () => { - const result = await generateLinkPreview("https://foo.bar"); - expect(result).toEqual({ - title: "Foo", - description: "Bar", - img: "http://foo.bar/image.jpg", - favicon: "http://foo.bar/favicon.ico", - img_width: 320, - img_height: 240, - domain: "foo.bar", - url: "https://foo.bar", - }); - }); - - it("should return a promise that resolves to undefined if no previews are found", async () => { - (parser as any).mockImplementation(() => { - throw new Error("failed to parse"); - }); - (getFavicons as any).mockImplementation(() => []); - (imageProbe as any).mockImplementation(() => ({})); - - const result = await generateLinkPreview("https://foo.bar"); - expect(result).toBeUndefined(); - }); - - it("should use og information as first choice", async () => { - (parser as any).mockImplementation(() => ({ - og: { - title: "test", - description: "test", - image: "http://foo.bar/test.jpg", - }, - meta: { - title: "test2", - description: "test2", - image: "http://foo.bar/test2.jpg", - }, - images: ["http://foo.bar/test3.jpg"], - })); - - const result = await generateLinkPreview("https://foo.bar"); - expect(result).toEqual({ - title: "test", - description: "test", - img: "http://foo.bar/test.jpg", - favicon: "http://foo.bar/favicon.ico", - img_width: 320, - img_height: 240, - domain: "foo.bar", - url: "https://foo.bar", - }); - }); - - it("should use meta information as second choice", async () => { - (parser as any).mockImplementation(() => ({ - meta: { - title: "test2", - description: "test2", - image: "http://foo.bar/test2.jpg", - }, - images: [], - })); - - const result = await generateLinkPreview("https://foo.bar"); - expect(result).toEqual({ - title: "test2", - description: "test2", - img: "http://foo.bar/test2.jpg", - favicon: "http://foo.bar/favicon.ico", - img_width: 320, - img_height: 240, - domain: "foo.bar", - url: "https://foo.bar", - }); - }); - - it("should use the first image found in the url when none are present in the og or meta information", async () => { - (parser as any).mockImplementation(() => ({ - og: { - title: "test", - description: "test", - }, - meta: { - title: "test2", - description: "test2", - }, - images: ["http://foo.bar/test3.jpg", "http://foo.bar/test4.jpg"], - })); - - const result = await generateLinkPreview("https://foo.bar"); - expect(result).toEqual({ - title: "test", - description: "test", - img: "http://foo.bar/test3.jpg", - favicon: "http://foo.bar/favicon.ico", - img_width: 320, - img_height: 240, - domain: "foo.bar", - url: "https://foo.bar", - }); - }); - - it("shouldn't attempt to probe for image size when none are found", async () => { - (parser as any).mockImplementation(() => ({ - og: { - title: "test", - description: "test", - }, - meta: { - title: "test2", - description: "test2", - }, - images: [], - })); - - await generateLinkPreview("https://foo.bar"); - expect(imageProbe).not.toHaveBeenCalled(); - }); - - it("should strip www from the domain", async () => { - const result = await generateLinkPreview("https://www.foo.bar"); - expect(result).toEqual({ - title: "Foo", - description: "Bar", - img: "http://foo.bar/image.jpg", - favicon: "http://foo.bar/favicon.ico", - img_width: 320, - img_height: 240, - domain: "foo.bar", - url: "https://www.foo.bar", - }); - }); -}); diff --git a/twake/backend/node/test/unit/services/previews/services/links/processing/service.test.ts b/twake/backend/node/test/unit/services/previews/services/links/processing/service.test.ts deleted file mode 100644 index b6293649..00000000 --- a/twake/backend/node/test/unit/services/previews/services/links/processing/service.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { - describe, - expect, - it, - jest, - beforeEach, - afterEach, - afterAll, - beforeAll, -} from "@jest/globals"; -import { LinkPreviewProcessService } from "../../../../../../../src/services/previews/services/links/processing/service"; -import { generateLinkPreview } from "../../../../../../../src/services/previews/services/links/processing/link"; -import { generateImageUrlPreview } from "../../../../../../../src/services/previews/services/links/processing/image"; -import axios from "axios"; - -jest.mock("axios"); -jest.mock("../../../../../../../src/services/previews/services/links/processing/link"); -jest.mock("../../../../../../../src/services/previews/services/links/processing/image"); - -let service: LinkPreviewProcessService; - -beforeEach(async () => { - (axios as any).mockImplementation(() => ({ - headers: { - "content-type": "text/html", - }, - })); - - (generateLinkPreview as any).mockImplementation(() => ({ - title: "Foo", - description: "Bar", - img: "http://foo.bar/image.jpg", - favicon: "http://foo.bar/favicon.ico", - })); - - (generateImageUrlPreview as any).mockImplementation(() => ({ - title: "image.jpg", - description: null, - img: "http://foo.bar/image.jpg", - favicon: "http://foo.bar/favicon.ico", - })); -}); - -afterEach(() => { - jest.clearAllMocks(); -}); - -afterAll(() => { - jest.restoreAllMocks(); -}); - -beforeAll(async () => { - service = await new LinkPreviewProcessService().init(); -}); - -describe("the LinkPreviewProcessService service", () => { - it("should return a promise", () => { - const result = service.generatePreviews(["http://foo.bar"]); - expect(result).toBeInstanceOf(Promise); - }); - - it("should call generateLinkPreview when it's a html page", async () => { - (axios as any).mockImplementation(() => ({ - headers: { - "content-type": "text/html", - }, - })); - - await service.generatePreviews(["http://foo.bar"]); - expect(generateLinkPreview).toHaveBeenCalled(); - }); - - it("should call generateImageUrlPreview when it's an image", async () => { - (axios as any).mockImplementation(() => ({ - headers: { - "content-type": "image/jpeg", - }, - })); - - await service.generatePreviews(["http://foo.bar/image.jpg"]); - expect(generateImageUrlPreview).toHaveBeenCalled(); - }); - - it("should skip if the url content is not supported", async () => { - (axios as any).mockImplementation(() => ({ - headers: { - "content-type": "application/json", - }, - })); - - await service.generatePreviews(["http://foo.bar"]); - expect(generateLinkPreview).not.toHaveBeenCalled(); - expect(generateImageUrlPreview).not.toHaveBeenCalled(); - }); - - // should filter null values - it("should filter null values", async () => { - (axios as any).mockImplementation(() => ({ - headers: { - "content-type": "text/html", - }, - })); - - (generateLinkPreview as any).mockImplementation(() => null); - - const result = await service.generatePreviews(["http://foo.bar", "http://foo.xyz"]); - expect(result).toEqual([]); - }); -});