From c97ec52a112ee24a6dac85898428816c96b20d51 Mon Sep 17 00:00:00 2001 From: montaghanmy Date: Fri, 7 Apr 2023 11:16:04 +0100 Subject: [PATCH] ref: e2e tests --- twake/backend/node/src/core/crypto/legacy.ts | 7 +- .../node/test/e2e/users/users.search.spec.ts | 149 ++++ .../backend/node/test/e2e/users/users.spec.ts | 645 ++++++++++++++++++ 3 files changed, 798 insertions(+), 3 deletions(-) create mode 100644 twake/backend/node/test/e2e/users/users.search.spec.ts create mode 100644 twake/backend/node/test/e2e/users/users.spec.ts diff --git a/twake/backend/node/src/core/crypto/legacy.ts b/twake/backend/node/src/core/crypto/legacy.ts index 63a61527..a8f3bf8b 100644 --- a/twake/backend/node/src/core/crypto/legacy.ts +++ b/twake/backend/node/src/core/crypto/legacy.ts @@ -25,9 +25,10 @@ function decrypt(data: string, encryptionKey: string): CryptoResult { const salt = encryptedArray[1] ? Buffer.from(encryptedArray[1], "utf-8") : Buffer.from("", "utf-8"); - const iv = encryptedArray.length >= 3 - ? Buffer.from(encryptedArray[2], "base64") - : Buffer.from("twake_constantiv", "utf-8"); + const iv = + encryptedArray.length >= 3 + ? Buffer.from(encryptedArray[2], "base64") + : Buffer.from("twake_constantiv", "utf-8"); const password = Buffer.concat([Buffer.from(encryptionKey, "hex"), salt], 32); const decipher = createDecipheriv("AES-256-CBC", password, iv); diff --git a/twake/backend/node/test/e2e/users/users.search.spec.ts b/twake/backend/node/test/e2e/users/users.search.spec.ts new file mode 100644 index 00000000..b95d1320 --- /dev/null +++ b/twake/backend/node/test/e2e/users/users.search.spec.ts @@ -0,0 +1,149 @@ +import { afterEach, beforeEach, describe, expect, it } from "@jest/globals"; +import { init, TestPlatform } from "../setup"; +import { TestDbService } from "../utils.prepare.db"; +import { v1 as uuidv1 } from "uuid"; + +describe("The /users API", () => { + const url = "/internal/services/users/v1"; + let platform: TestPlatform; + + beforeEach(async ends => { + platform = await init({ + services: [ + "database", + "search", + "message-queue", + "websocket", + "webserver", + "user", + "auth", + "applications", + "storage", + "counter", + "workspaces", + "console", + "statistics", + "platform-services", + ], + }); + ends(); + }); + + afterEach(async ends => { + platform && (await platform.tearDown()); + platform = null; + ends(); + }); + + describe("The GET /users/?search=... route", () => { + it("Should find the searched users", async done => { + 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); + await testDbService.createWorkspace(workspacePk2); + await testDbService.createUser([workspacePk], { + firstName: "Ha", + lastName: "Nguyen", + email: "hnguyen@twake.app", + }); + await testDbService.createUser([workspacePk], { + firstName: "Harold", + lastName: "Georges", + email: "hgeorges@twake.app", + }); + await testDbService.createUser([workspacePk], { + firstName: "Bob", + lastName: "Smith", + email: "bob@twake.app", + }); + await testDbService.createUser([workspacePk], { + firstName: "Bob", + lastName: "Rabiot", + email: "rabiot.b@twake.app", + }); + await testDbService.createUser([workspacePk, workspacePk2], { + firstName: "Bob", + lastName: "Smith-Rabiot", + email: "rbs@twake.app", + }); + await testDbService.createUser([workspacePk], { + firstName: "Alexïs", + lastName: "Goélâns", + email: "alexis.goelans@twake.app", + }); + + //Wait for indexation to happen + await new Promise(r => setTimeout(r, 5000)); + + let resources = await search("ha"); + expect(resources.length).toBe(2); + + resources = await search("bob rabiot"); + + expect(resources.map(e => e.email).includes("rabiot.b@twake.app")).toBe(true); + expect(resources.map(e => e.email).includes("rbs@twake.app")).toBe(true); + expect(resources.map(e => e.email).includes("bob@twake.app")).toBe(true); + + resources = await search("alexis"); + expect(resources[0].email).toBe("alexis.goelans@twake.app"); + + resources = await search("ALEXIS"); + expect(resources[0].email).toBe("alexis.goelans@twake.app"); + + resources = await search("AleXis"); + expect(resources[0].email).toBe("alexis.goelans@twake.app"); + + resources = await search("alex"); + expect(resources[0].email).toBe("alexis.goelans@twake.app"); + + resources = await search("àlèXïs"); + expect(resources[0].email).toBe("alexis.goelans@twake.app"); + + resources = await search("rbs"); + expect(resources[0].email).toBe("rbs@twake.app"); + + resources = await search("rbs@twake.app"); + expect(resources[0].email).toBe("rbs@twake.app"); + + resources = await search("bob", workspacePk2.company_id); + expect(resources.length).toBe(1); + + resources = await search("rbs@twake.app", workspacePk.company_id); + expect(resources[0].email).toBe("rbs@twake.app"); + + resources = await search("rbs@twake.app", uuidv1()); + expect(resources.length).toBe(0); + + done(); + }); + }); + + async function search(search: string, companyId?: string): Promise { + const jwtToken = await platform.auth.getJWTToken(); + const response = await platform.app.inject({ + method: "GET", + url: `${url}/users`, + headers: { + authorization: `Bearer ${jwtToken}`, + }, + query: { + search: search, + ...(companyId ? { search_company_id: companyId } : {}), + }, + }); + + 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/users/users.spec.ts b/twake/backend/node/test/e2e/users/users.spec.ts new file mode 100644 index 00000000..a8c03130 --- /dev/null +++ b/twake/backend/node/test/e2e/users/users.spec.ts @@ -0,0 +1,645 @@ +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "@jest/globals"; +import { init, TestPlatform } from "../setup"; +import { TestDbService } from "../utils.prepare.db"; +import { v1 as uuidv1 } from "uuid"; +import { CompanyLimitsEnum, UserObject } from "../../../src/services/user/web/types"; +import { ChannelVisibility } from "../../../src/services/channels/types"; +import gr from "../../../src/services/global-resolver"; +import { ResourceListResponse, Workspace } from "../../../src/utils/types"; +import { ChannelSaveOptions } from "../../../src/services/channels/web/types"; +import { createMessage, e2e_createThread } from "../messages/utils"; +import { deserialize } from "class-transformer"; +import { get as getChannelUtils } from "../channels/utils"; + +describe("The /users API", () => { + const url = "/internal/services/users/v1"; + let platform: TestPlatform; + + let testDbService: TestDbService; + + const nonExistentId = uuidv1(); + + beforeEach(async ends => { + platform = await init(); + ends(); + }); + afterEach(async ends => { + await platform.tearDown(); + platform = null; + ends(); + }); + + beforeAll(async ends => { + const platform = await init({ + services: [ + "database", + "search", + "message-queue", + "websocket", + "applications", + "webserver", + "user", + "auth", + "storage", + "counter", + "console", + "workspaces", + "statistics", + "platform-services", + ], + }); + + testDbService = await TestDbService.getInstance(platform); + await testDbService.createCompany(); + const workspacePk = { id: uuidv1(), company_id: testDbService.company.id }; + await testDbService.createWorkspace(workspacePk); + await testDbService.createUser([workspacePk], { + workspaceRole: "moderator", + companyRole: "admin", + email: "admin@admin.admin", + username: "adminuser", + firstName: "admin", + }); + await testDbService.createUser([workspacePk]); + + ends(); + }); + + afterAll(async ends => { + ends(); + }); + + describe("The GET /users/:id route", () => { + it("should 401 when not authenticated", async done => { + const response = await platform.app.inject({ + method: "GET", + url: `${url}/users/1`, + }); + + expect(response.statusCode).toBe(401); + done(); + }); + + it("should 404 when user does not exists", async done => { + const jwtToken = await platform.auth.getJWTToken({ sub: testDbService.users[0].id }); + const response = await platform.app.inject({ + method: "GET", + url: `${url}/users/${nonExistentId}`, + headers: { + authorization: `Bearer ${jwtToken}`, + }, + }); + + expect(response.statusCode).toBe(404); + expect(response.json()).toEqual({ + error: "Not Found", + message: `User ${nonExistentId} not found`, + statusCode: 404, + }); + done(); + }); + + it("should 200 and big response for myself", async done => { + const myId = testDbService.users[0].id; + const jwtToken = await platform.auth.getJWTToken({ sub: myId }); + const response = await platform.app.inject({ + method: "GET", + url: `${url}/users/${myId}`, + headers: { + authorization: `Bearer ${jwtToken}`, + }, + }); + + expect(response.statusCode).toBe(200); + + const resource = response.json()["resource"]; + + expect(resource).toMatchObject({ + id: myId, + provider: expect.any(String), + provider_id: expect.any(String), + email: expect.any(String), + is_verified: expect.any(Boolean), + picture: expect.any(String), + first_name: expect.any(String), + last_name: expect.any(String), + created_at: expect.any(Number), + deleted: expect.any(Boolean), + status: expect.any(String), + last_activity: expect.any(Number), + + //Below is only if this is myself + + preference: expect.objectContaining({ + locale: expect.any(String), + timezone: expect.any(Number), + }), + }); + + expect(resource["companies"]).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + role: expect.stringMatching(/owner|admin|member|guest/), + status: expect.stringMatching(/active|deactivated|invited/), + company: { + id: expect.any(String), + name: expect.any(String), + logo: expect.any(String), + }, + }), + ]), + ); + + done(); + }); + + it("should 200 and short response for another user", async done => { + const myId = testDbService.users[0].id; + const anotherUserId = testDbService.users[1].id; + + const jwtToken = await platform.auth.getJWTToken({ sub: myId }); + const response = await platform.app.inject({ + method: "GET", + url: `${url}/users/${anotherUserId}`, + headers: { + authorization: `Bearer ${jwtToken}`, + }, + }); + + expect(response.statusCode).toBe(200); + + const resource = response.json()["resource"]; + + expect(resource).toMatchObject({ + id: anotherUserId, + provider: expect.any(String), + provider_id: expect.any(String), + email: expect.any(String), + is_verified: expect.any(Boolean), + picture: expect.any(String), + first_name: expect.any(String), + last_name: expect.any(String), + created_at: expect.any(Number), + deleted: expect.any(Boolean), + status: expect.any(String), + last_activity: expect.any(Number), + }); + + expect(resource).not.toMatchObject({ + locale: expect.anything(), + timezone: expect.anything(), + companies: expect.anything(), + }); + + done(); + }); + }); + + describe("The GET /users route", () => { + it("should 401 when user is not authenticated", async done => { + const response = await platform.app.inject({ + method: "GET", + url: `${url}/users`, + }); + + expect(response.statusCode).toBe(401); + done(); + }); + + it("should 200 with array of users", async done => { + const myId = testDbService.users[0].id; + const anotherUserId = testDbService.users[1].id; + + const jwtToken = await platform.auth.getJWTToken({ sub: myId }); + const response = await platform.app.inject({ + method: "GET", + url: `${url}/users`, + headers: { + authorization: `Bearer ${jwtToken}`, + }, + query: { + user_ids: `${myId},${anotherUserId}`, + company_ids: "fd96c8a8-ae77-11eb-a1a1-0242ac120005", + }, + }); + + expect(response.statusCode).toBe(200); + const json = response.json(); + expect(json).toMatchObject({ resources: expect.any(Array) }); + const resources = json.resources; + + done(); + }); + }); + + describe("The GET /users/:user_id/companies route", () => { + it("should 401 when not authenticated", async done => { + const response = await platform.app.inject({ + method: "GET", + url: `${url}/users/1/companies`, + }); + + expect(response.statusCode).toBe(401); + done(); + }); + + it("should 404 when user does not exists", async done => { + const jwtToken = await platform.auth.getJWTToken({ sub: testDbService.users[0].id }); + const response = await platform.app.inject({ + method: "GET", + url: `${url}/users/${nonExistentId}/companies`, + headers: { + authorization: `Bearer ${jwtToken}`, + }, + }); + + expect(response.statusCode).toBe(404); + expect(response.json()).toEqual({ + error: "Not Found", + message: `User ${nonExistentId} not found`, + statusCode: 404, + }); + done(); + }); + + it("should 200 and on correct request", async done => { + const myId = testDbService.users[0].id; + const anotherUserId = testDbService.users[1].id; + + const jwtToken = await platform.auth.getJWTToken({ sub: myId }); + const response = await platform.app.inject({ + method: "GET", + url: `${url}/users/${anotherUserId}/companies`, + headers: { + authorization: `Bearer ${jwtToken}`, + }, + }); + + expect(response.statusCode).toBe(200); + + const resources = response.json()["resources"]; + expect(resources.length).toBeGreaterThan(0); + + for (const resource of resources) { + expect(resource).toMatchObject({ + id: expect.any(String), + name: expect.any(String), + logo: expect.any(String), + role: expect.stringMatching(/owner|admin|member|guest/), + status: expect.stringMatching(/active|deactivated|invited/), + }); + + if (resource.plan) { + expect(resource.plan).toMatchObject({ + name: expect.any(String), + limits: expect.objectContaining({ + [CompanyLimitsEnum.CHAT_MESSAGE_HISTORY_LIMIT]: expect.any(Number || undefined), + [CompanyLimitsEnum.COMPANY_MEMBERS_LIMIT]: expect.any(Number || undefined), + }), + }); + } + if (resources.stats) { + expect(resource.plan).toMatchObject({ + created_at: expect.any(Number), + total_members: expect.any(Number), + total_guests: expect.any(Number), + }); + } + } + done(); + }); + }); + + describe("The GET /companies/:company_id route", () => { + it("should 404 when company does not exists", async done => { + const response = await platform.app.inject({ + method: "GET", + url: `${url}/companies/11111111-1111-1111-1111-111111111111`, + }); + expect(response.statusCode).toBe(404); + done(); + }); + + it("should 200 when company exists", async done => { + const companyId = testDbService.company.id; + + const response = await platform.app.inject({ + method: "GET", + url: `${url}/companies/${companyId}`, + }); + expect(response.statusCode).toBe(200); + + const json = response.json(); + + expect(json.resource).toMatchObject({ + id: expect.any(String), + name: expect.any(String), + logo: expect.any(String), + }); + + if (json.resource.plan) { + expect(json.resource.plan).toMatchObject({ + name: expect.any(String), + limits: expect.objectContaining({ + [CompanyLimitsEnum.CHAT_MESSAGE_HISTORY_LIMIT]: expect.any(Number || undefined), + [CompanyLimitsEnum.COMPANY_MEMBERS_LIMIT]: expect.any(Number || undefined), + }), + }); + } + + expect(json.resource.stats).toMatchObject({ + created_at: expect.any(Number), + total_members: expect.any(Number), + total_guests: expect.any(Number), + total_messages: expect.any(Number), + }); + + done(); + }); + }); + + describe("User's device management", () => { + const deviceToken = "testDeviceToken"; + + describe("Register device (POST)", () => { + it("should 400 when type is not FCM", async done => { + const myId = testDbService.users[0].id; + + const jwtToken = await platform.auth.getJWTToken({ sub: myId }); + const response = await platform.app.inject({ + method: "POST", + url: `${url}/devices`, + headers: { + authorization: `Bearer ${jwtToken}`, + }, + payload: { + resource: { + type: "another", + value: "value", + version: "version", + }, + }, + }); + + const resp = response.json(); + expect(response.statusCode).toBe(400); + expect(resp).toMatchObject({ + statusCode: 400, + error: "Bad Request", + message: "Type should be FCM only", + }); + done(); + }); + + it("should 200 when ok", async done => { + const firstId = testDbService.users[0].id; + + const jwtToken = await platform.auth.getJWTToken({ sub: firstId }); + const response = await platform.app.inject({ + method: "POST", + url: `${url}/devices`, + headers: { + authorization: `Bearer ${jwtToken}`, + }, + payload: { + resource: { + type: "FCM", + value: deviceToken, + version: "1", + }, + }, + }); + + const resp = response.json(); + expect(response.statusCode).toBe(200); + + expect(resp.resource).toMatchObject({ + type: "FCM", + value: deviceToken, + version: "1", + }); + + const user = await testDbService.getUserFromDb({ id: firstId }); + expect(user.devices).toMatchObject([deviceToken]); + const device = await testDbService.getDeviceFromDb(deviceToken); + expect(device).toMatchObject({ + id: deviceToken, + user_id: firstId, + type: "FCM", + version: "1", + }); + + done(); + }); + + it("should 200 when register token to another person", async done => { + const firstId = testDbService.users[0].id; + const secondId = testDbService.users[1].id; + + const jwtToken = await platform.auth.getJWTToken({ sub: secondId }); + const response = await platform.app.inject({ + method: "POST", + url: `${url}/devices`, + headers: { + authorization: `Bearer ${jwtToken}`, + }, + payload: { + resource: { + type: "FCM", + value: deviceToken, + version: "1", + }, + }, + }); + + const resp = response.json(); + expect(response.statusCode).toBe(200); + + expect(resp.resource).toMatchObject({ + type: "FCM", + value: deviceToken, + version: "1", + }); + + // second user should have now this token + let user = await testDbService.getUserFromDb({ id: secondId }); + expect(user.devices).toMatchObject([deviceToken]); + const device = await testDbService.getDeviceFromDb(deviceToken); + expect(device).toMatchObject({ + id: deviceToken, + user_id: secondId, + type: "FCM", + version: "1", + }); + + // and first — not + + user = await testDbService.getUserFromDb({ id: firstId }); + expect(user.devices).toMatchObject([]); + + done(); + }); + }); + describe("List registered devices (GET)", () => { + it("should 200 when request devices", async done => { + const myId = testDbService.users[1].id; + + const jwtToken = await platform.auth.getJWTToken({ sub: myId }); + const response = await platform.app.inject({ + method: "GET", + url: `${url}/devices`, + headers: { + authorization: `Bearer ${jwtToken}`, + }, + }); + + const resp = response.json(); + expect(response.statusCode).toBe(200); + expect(resp).toMatchObject({ + resources: [ + { + type: "FCM", + value: "testDeviceToken", + version: "1", + }, + ], + }); + done(); + }); + }); + + describe("De-register device (DELETE)", () => { + it("should 200 when device not found for the user", async done => { + const myId = testDbService.users[1].id; + + const jwtToken = await platform.auth.getJWTToken({ sub: myId }); + const response = await platform.app.inject({ + method: "DELETE", + url: `${url}/devices/somethingRandom`, + headers: { + authorization: `Bearer ${jwtToken}`, + }, + }); + expect(response.statusCode).toBe(204); + + const user = await testDbService.getUserFromDb({ id: myId }); + expect(user.devices).toMatchObject([deviceToken]); + const device = await testDbService.getDeviceFromDb(deviceToken); + expect(device).toMatchObject({ + id: deviceToken, + user_id: myId, + type: "FCM", + version: "1", + }); + + done(); + }); + + it("should 200 when device found and device should be removed", async done => { + const myId = testDbService.users[1].id; + + const jwtToken = await platform.auth.getJWTToken({ sub: myId }); + const response = await platform.app.inject({ + method: "DELETE", + url: `${url}/devices/${deviceToken}`, + headers: { + authorization: `Bearer ${jwtToken}`, + }, + }); + expect(response.statusCode).toBe(204); + + const user = await testDbService.getUserFromDb({ id: myId }); + expect(user.devices).toMatchObject([]); + const device = await testDbService.getDeviceFromDb(deviceToken); + expect(device).toBeFalsy(); + + done(); + }); + }); + }); + + describe("Recent contacts", () => { + it("should return list of recent contacts of user", async done => { + // api = new Api(platform); + const channelUtils = getChannelUtils(platform); + + await testDbService.createDefault(platform); + + const channels = []; + + for (let i = 0; i < 5; i++) { + // const channel = channelUtils.getChannel(); + const directChannelIn = channelUtils.getDirectChannel(); + + const nextUser = await testDbService.createUser( + [{ id: platform.workspace.workspace_id, company_id: platform.workspace.company_id }], + { firstName: "FirstName" + i, lastName: "LastName" + i }, + ); + + const members = [platform.currentUser.id, nextUser.id]; + const directWorkspace: Workspace = { + company_id: platform.workspace.company_id, + workspace_id: ChannelVisibility.DIRECT, + }; + await Promise.all([ + // gr.services.channels.channels.save(channel, {}, getContext()), + gr.services.channels.channels.save( + directChannelIn, + { + members, + }, + { + ...{ + workspace: platform.workspace, + user: platform.currentUser, + }, + ...{ workspace: directWorkspace }, + }, + ), + ]); + channels.push(directChannelIn); + } + + await new Promise(resolve => setTimeout(resolve, 1000)); + + await e2e_createThread( + platform, + [ + { + company_id: platform.workspace.company_id, + created_at: 0, + created_by: "", + id: channels[2].id, + type: "channel", + workspace_id: "direct", + }, + ], + createMessage({ text: "Some message" }), + ); + + await new Promise(resolve => setTimeout(resolve, 1000)); + + const jwtToken = await platform.auth.getJWTToken(); + + const response = await platform.app.inject({ + method: "GET", + url: `${url}/companies/${platform.workspace.company_id}/users/recent`, + headers: { + authorization: `Bearer ${jwtToken}`, + }, + }); + + expect(response.statusCode).toBe(200); + + const result: ResourceListResponse = deserialize( + ResourceListResponse, + response.body, + ); + + expect(result.resources.length).toEqual(5); + expect(result.resources[0].first_name).toEqual("FirstName2"); + + done(); + }); + }); +});