From f1bf507865600463cb6a40403b813077a33d93a4 Mon Sep 17 00:00:00 2001 From: Anton Shepilov Date: Wed, 7 Jun 2023 10:48:44 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20#46=20Shared=20with=20me=20(#67)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🚧 #46 Shared with me --- .run/Tests in e2e_.run.xml | 18 ++ ...arch a file shared by another user.run.xml | 21 ++ .run/the Drive feature.run.xml | 20 ++ tdrive/backend/node/config/test.json | 6 + .../platform/framework/api/crud-service.ts | 8 +- .../orm/connectors/mongodb/query-builder.ts | 2 +- .../services/orm/repository/repository.ts | 4 +- .../search/adapters/elasticsearch/index.ts | 3 + .../search/adapters/elasticsearch/search.ts | 40 ++- .../search/adapters/mongosearch/index.ts | 4 +- .../search/adapters/mongosearch/search.ts | 4 + .../src/core/platform/services/search/api.ts | 2 +- .../documents/entities/drive-file.search.ts | 31 +-- .../services/documents/entities/drive-file.ts | 12 +- .../src/services/documents/services/index.ts | 50 +++- .../node/src/services/documents/types.ts | 15 +- .../node/src/services/documents/utils.ts | 6 +- .../documents/web/controllers/documents.ts | 20 +- .../node/src/services/files/services/index.ts | 3 + tdrive/backend/node/src/utils/types.ts | 2 + .../e2e/{files => common}/assets/sample.doc | Bin .../e2e/{files => common}/assets/sample.gif | Bin .../e2e/{files => common}/assets/sample.mp4 | Bin .../e2e/{files => common}/assets/sample.pdf | 0 .../e2e/{files => common}/assets/sample.png | Bin .../e2e/{files => common}/assets/sample.zip | Bin .../test/e2e/common/common_test_helpers.ts | 192 +++++++++++++ .../test/e2e/common/entities/mock_entities.ts | 42 +++ .../node/test/e2e/documents/documents.spec.ts | 255 ++++++++++++++++-- .../backend/node/test/e2e/files/files.spec.ts | 59 ++-- tdrive/backend/node/test/e2e/setup/index.ts | 8 +- .../setup/{default.test.json => test.json} | 6 + tdrive/backend/node/test/http/badges.http | 19 -- .../node/test/http/channel-as-read.http | 61 ----- tdrive/backend/node/test/http/channels.http | 133 --------- .../node/test/http/direct-channels.http | 163 ----------- .../backend/node/test/http/elasticsearch.http | 251 +++++++++++++++++ .../http/private-channels-membership.http | 108 -------- .../node/test/http/private-channels.http | 70 ----- .../test/http/public-channels-membership.http | 108 -------- tdrive/backend/node/test/http/tabs.http | 85 ------ .../adapters/elasticsearch/search.test.ts | 158 +++++++++++ 42 files changed, 1122 insertions(+), 867 deletions(-) create mode 100644 .run/Tests in e2e_.run.xml create mode 100644 .run/the Drive feature.did search a file shared by another user.run.xml create mode 100644 .run/the Drive feature.run.xml rename tdrive/backend/node/test/e2e/{files => common}/assets/sample.doc (100%) rename tdrive/backend/node/test/e2e/{files => common}/assets/sample.gif (100%) rename tdrive/backend/node/test/e2e/{files => common}/assets/sample.mp4 (100%) rename tdrive/backend/node/test/e2e/{files => common}/assets/sample.pdf (100%) rename tdrive/backend/node/test/e2e/{files => common}/assets/sample.png (100%) rename tdrive/backend/node/test/e2e/{files => common}/assets/sample.zip (100%) create mode 100644 tdrive/backend/node/test/e2e/common/common_test_helpers.ts create mode 100644 tdrive/backend/node/test/e2e/common/entities/mock_entities.ts rename tdrive/backend/node/test/e2e/setup/{default.test.json => test.json} (74%) delete mode 100644 tdrive/backend/node/test/http/badges.http delete mode 100644 tdrive/backend/node/test/http/channel-as-read.http delete mode 100644 tdrive/backend/node/test/http/channels.http delete mode 100644 tdrive/backend/node/test/http/direct-channels.http create mode 100644 tdrive/backend/node/test/http/elasticsearch.http delete mode 100644 tdrive/backend/node/test/http/private-channels-membership.http delete mode 100644 tdrive/backend/node/test/http/private-channels.http delete mode 100644 tdrive/backend/node/test/http/public-channels-membership.http delete mode 100644 tdrive/backend/node/test/http/tabs.http create mode 100644 tdrive/backend/node/test/unit/core/services/search/adapters/elasticsearch/search.test.ts diff --git a/.run/Tests in e2e_.run.xml b/.run/Tests in e2e_.run.xml new file mode 100644 index 00000000..ea3f0662 --- /dev/null +++ b/.run/Tests in e2e_.run.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.run/the Drive feature.did search a file shared by another user.run.xml b/.run/the Drive feature.did search a file shared by another user.run.xml new file mode 100644 index 00000000..402f7364 --- /dev/null +++ b/.run/the Drive feature.did search a file shared by another user.run.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.run/the Drive feature.run.xml b/.run/the Drive feature.run.xml new file mode 100644 index 00000000..2537ff97 --- /dev/null +++ b/.run/the Drive feature.run.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tdrive/backend/node/config/test.json b/tdrive/backend/node/config/test.json index 5918383c..851c4e70 100644 --- a/tdrive/backend/node/config/test.json +++ b/tdrive/backend/node/config/test.json @@ -19,5 +19,11 @@ "amqp": { "urls": ["amqp://guest:guest@rabbitmq:5672"] } + }, + "search": { + "type": "elasticsearch", + "elasticsearch": { + "endpoint": "https://localhost:9200" + } } } diff --git a/tdrive/backend/node/src/core/platform/framework/api/crud-service.ts b/tdrive/backend/node/src/core/platform/framework/api/crud-service.ts index e0ed0e40..373e4943 100644 --- a/tdrive/backend/node/src/core/platform/framework/api/crud-service.ts +++ b/tdrive/backend/node/src/core/platform/framework/api/crud-service.ts @@ -149,6 +149,10 @@ export class CrudException extends Error { static badGateway(details: string): CrudException { return new CrudException(details, 502); } + + static throwMe(cause: Error, newOne: CrudException): void { + throw cause instanceof CrudException ? cause : newOne; + } } export interface Paginable { @@ -202,7 +206,7 @@ export interface CRUDService( diff --git a/tdrive/backend/node/src/core/platform/services/database/services/orm/connectors/mongodb/query-builder.ts b/tdrive/backend/node/src/core/platform/services/database/services/orm/connectors/mongodb/query-builder.ts index decb7391..5efa386c 100644 --- a/tdrive/backend/node/src/core/platform/services/database/services/orm/connectors/mongodb/query-builder.ts +++ b/tdrive/backend/node/src/core/platform/services/database/services/orm/connectors/mongodb/query-builder.ts @@ -49,7 +49,7 @@ export function buildComparison(where: any, options: FindOptions = {}): string[] if (operator === "$gt" || operator === "$gte" || operator === "$lt" || operator === "$lte") { (options[operator] || []).forEach(element => { if (!where[element[0]]) where[element[0]] = {}; - where[element[0]][operator] = element[1]; + where[element[0]][operator] = parseInt(element[1]); }); } }); diff --git a/tdrive/backend/node/src/core/platform/services/database/services/orm/repository/repository.ts b/tdrive/backend/node/src/core/platform/services/database/services/orm/repository/repository.ts index 51b9cd96..374fcef1 100644 --- a/tdrive/backend/node/src/core/platform/services/database/services/orm/repository/repository.ts +++ b/tdrive/backend/node/src/core/platform/services/database/services/orm/repository/repository.ts @@ -16,9 +16,9 @@ export type FindFilter = { [key: string]: any }; export type RemoveFilter = { [key: string]: any }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -type comparisonType = [string, any]; +export type comparisonType = [string, any]; // eslint-disable-next-line @typescript-eslint/no-explicit-any -type inType = [string, Array]; +export type inType = [string, Array]; // eslint-disable-next-line @typescript-eslint/no-explicit-any type likeType = [string, any]; diff --git a/tdrive/backend/node/src/core/platform/services/search/adapters/elasticsearch/index.ts b/tdrive/backend/node/src/core/platform/services/search/adapters/elasticsearch/index.ts index 342ff301..01835399 100644 --- a/tdrive/backend/node/src/core/platform/services/search/adapters/elasticsearch/index.ts +++ b/tdrive/backend/node/src/core/platform/services/search/adapters/elasticsearch/index.ts @@ -42,6 +42,9 @@ export default class ElasticSearch extends SearchAdapter implements SearchAdapte try { this.client = new Client({ node: this.configuration.endpoint, + ssl: { + rejectUnauthorized: false, + }, }); } catch (e) { logger.error(`Unable to connect to ElasticSearch at ${this.configuration.endpoint}`); diff --git a/tdrive/backend/node/src/core/platform/services/search/adapters/elasticsearch/search.ts b/tdrive/backend/node/src/core/platform/services/search/adapters/elasticsearch/search.ts index 5446149d..bfd2c7c7 100644 --- a/tdrive/backend/node/src/core/platform/services/search/adapters/elasticsearch/search.ts +++ b/tdrive/backend/node/src/core/platform/services/search/adapters/elasticsearch/search.ts @@ -3,6 +3,7 @@ import { TransportRequestOptions } from "@elastic/elasticsearch/lib/Transport"; import { logger } from "../../../../../../core/platform/framework/logger"; import { EntityTarget, FindFilter, FindOptions, getEntityDefinition } from "../../api"; import { asciiFold } from "../utils"; +import { comparisonType } from "src/core/platform/services/database/services/orm/repository/repository"; export function buildSearchQuery( entityType: EntityTarget, @@ -45,7 +46,44 @@ export function buildSearchQuery( } } - //TODO implement $gte, $lt, etc + function buildRangeQuery( + lteOperations: comparisonType[], + key: "gte" | "lte" | "lt" | "gt", + ): void { + for (const lteOperation of lteOperations) { + if (lteOperation?.length == 2 && lteOperation[0]) { + const field_name = lteOperation[0]; + esBody.query.bool.must.push({ + range: { + [field_name]: { + [key]: lteOperation[1] ? parseInt(lteOperation[1]) : 0, + }, + }, + }); + } else { + logger.warn(`Not enough data to include to the query: ${lteOperation}`); + } + } + } + + if (options.$lte?.length) { + esBody.query.bool.must = esBody.query.bool.must || []; + buildRangeQuery(options.$lte, "lte"); + } + + if (options.$gte?.length) { + esBody.query.bool.must = esBody.query.bool.must || []; + buildRangeQuery(options.$gte, "gte"); + } + + if (options.$lt?.length) { + esBody.query.bool.must = esBody.query.bool.must || []; + buildRangeQuery(options.$lt, "lt"); + } + if (options.$gt?.length) { + esBody.query.bool.must = esBody.query.bool.must || []; + buildRangeQuery(options.$gt, "gt"); + } if (options.$text) { esBody.query.bool.minimum_should_match = 1; diff --git a/tdrive/backend/node/src/core/platform/services/search/adapters/mongosearch/index.ts b/tdrive/backend/node/src/core/platform/services/search/adapters/mongosearch/index.ts index 302cb0d5..fc513d60 100644 --- a/tdrive/backend/node/src/core/platform/services/search/adapters/mongosearch/index.ts +++ b/tdrive/backend/node/src/core/platform/services/search/adapters/mongosearch/index.ts @@ -84,7 +84,9 @@ export default class MongoSearch extends SearchAdapter implements SearchAdapterI //Create one index for each type of indexes ["text"] Object.keys(indexedFields).forEach(k => { - collection.createIndex(indexedFields[k], { default_language: "none" }); + collection.createIndex(indexedFields[k], { + default_language: "none", + }); }); } diff --git a/tdrive/backend/node/src/core/platform/services/search/adapters/mongosearch/search.ts b/tdrive/backend/node/src/core/platform/services/search/adapters/mongosearch/search.ts index fa9d68f0..4db7af22 100644 --- a/tdrive/backend/node/src/core/platform/services/search/adapters/mongosearch/search.ts +++ b/tdrive/backend/node/src/core/platform/services/search/adapters/mongosearch/search.ts @@ -58,6 +58,10 @@ export function buildSearchQuery( }); } + if (options.$sort) { + sort = options.$sort; + } + return { project, query, diff --git a/tdrive/backend/node/src/core/platform/services/search/api.ts b/tdrive/backend/node/src/core/platform/services/search/api.ts index e64d574d..12ef3cdd 100644 --- a/tdrive/backend/node/src/core/platform/services/search/api.ts +++ b/tdrive/backend/node/src/core/platform/services/search/api.ts @@ -26,7 +26,7 @@ type TextType = { $diacriticSensitive?: boolean; //Default false }; -type SortType = { +export type SortType = { [key: string]: "asc" | "desc"; }; diff --git a/tdrive/backend/node/src/services/documents/entities/drive-file.search.ts b/tdrive/backend/node/src/services/documents/entities/drive-file.search.ts index 722b4a42..d1e6a449 100644 --- a/tdrive/backend/node/src/services/documents/entities/drive-file.search.ts +++ b/tdrive/backend/node/src/services/documents/entities/drive-file.search.ts @@ -8,43 +8,36 @@ export default { creator: entity.creator, added: entity.added, name: entity.name, + //especially for ES because it doesn't allow to sort by not a keyword fields + name_keyword: entity.name, company_id: entity.company_id, - access_users: ["user_1234", "user_454"], - access_users_x_initiator: ["user_1234_x_user_4343", "user_1234_x_user_4343"], - - access_entities: [ - { - type: "user", - level: "read", - target: "user_1234", - source: "user_5678", - }, - { - type: "user", - level: "read", - target: "user_abcd", - source: "user_5678", - }, - ], + access_entities: entity.access_info?.entities?.filter(e => e.level != "none").map(e => e.id), + last_modified: entity.last_modified, + mime_type: entity.last_version_cache?.file_metadata?.mime, }), mongoMapping: { text: { content_keywords: "text", tags: "text", creator: "text", - added: "text", name: "text", + name_keyword: "text", company_id: "text", + mime_type: "text", }, }, esMapping: { properties: { name: { type: "text", index_prefixes: { min_chars: 1 } }, + name_keyword: { type: "keyword" }, content_keywords: { type: "text", index_prefixes: { min_chars: 1 } }, tags: { type: "keyword" }, creator: { type: "keyword" }, - added: { type: "keyword" }, + added: { type: "unsigned_long" }, company_id: { type: "keyword" }, + access_entities: { type: "keyword" }, + mime_type: { type: "keyword" }, + last_modified: { type: "unsigned_long" }, }, }, }; diff --git a/tdrive/backend/node/src/services/documents/entities/drive-file.ts b/tdrive/backend/node/src/services/documents/entities/drive-file.ts index fda31453..bdbc0dcd 100644 --- a/tdrive/backend/node/src/services/documents/entities/drive-file.ts +++ b/tdrive/backend/node/src/services/documents/entities/drive-file.ts @@ -48,13 +48,13 @@ export class DriveFile { @Column("tags", "encoded_json") tags: string[]; - @Type(() => String) - @Column("added", "string") - added: string; + @Type(() => Number) + @Column("added", "number") + added: number; - @Type(() => String) - @Column("last_modified", "string") - last_modified: string; + @Type(() => Number) + @Column("last_modified", "number") + last_modified: number; @Column("access_info", "encoded_json") access_info: AccessInformation; diff --git a/tdrive/backend/node/src/services/documents/services/index.ts b/tdrive/backend/node/src/services/documents/services/index.ts index 922fa187..85caed6e 100644 --- a/tdrive/backend/node/src/services/documents/services/index.ts +++ b/tdrive/backend/node/src/services/documents/services/index.ts @@ -1,7 +1,10 @@ import SearchRepository from "../../../core/platform/services/search/repository"; import { getLogger, logger, TdriveLogger } from "../../../core/platform/framework"; import { CrudException, ListResult } from "../../../core/platform/framework/api/crud-service"; -import Repository from "../../../core/platform/services/database/services/orm/repository/repository"; +import Repository, { + inType, + comparisonType, +} from "../../../core/platform/services/database/services/orm/repository/repository"; import { PublicFile } from "../../../services/files/entities/file"; import globalResolver from "../../../services/global-resolver"; import { hasCompanyAdminLevel } from "../../../utils/company"; @@ -291,9 +294,9 @@ export class DocumentsService { await this.repository.save(driveItem); await updateItemSize(driveItem.parent_id, this.repository, context); - this.notifyWebsocket(driveItem.parent_id, context); + await this.notifyWebsocket(driveItem.parent_id, context); - globalResolver.platformServices.messageQueue.publish( + await globalResolver.platformServices.messageQueue.publish( "services:documents:process", { data: { @@ -307,7 +310,7 @@ export class DocumentsService { return driveItem; } catch (error) { this.logger.error("Failed to create drive item", error); - throw new CrudException("Failed to create item", 500); + CrudException.throwMe(error, new CrudException("Failed to create item", 500)); } }; @@ -768,12 +771,39 @@ export class DocumentsService { pagination: { limitStr: "100", }, - ...(options.company_id ? { $in: [["company_id", [options.company_id]]] } : {}), - ...(options.creator ? { $in: [["creator", [options.creator]]] } : {}), - ...(options.added ? { $in: [["added", [options.added]]] } : {}), - $text: { - $search: options.search, - }, + $in: [ + ["access_entities", [context.user.id, context.company.id]], + ...(options.company_id ? [["company_id", [options.company_id]] as inType] : []), + ...(options.creator ? [["creator", [options.creator]] as inType] : []), + ...(options.mime_type + ? [ + [ + "mime_type", + Array.isArray(options.mime_type) ? options.mime_type : [options.mime_type], + ] as inType, + ] + : []), + ], + $lte: [ + ...(options.last_modified_lt + ? [["last_modified", options.last_modified_lt] as comparisonType] + : []), + ...(options.added_lt ? [["added", options.added_lt] as comparisonType] : []), + ], + $gte: [ + ...(options.last_modified_gt + ? [["last_modified", options.last_modified_gt] as comparisonType] + : []), + ...(options.added_gt ? [["added", options.added_gt] as comparisonType] : []), + ], + ...(options.search + ? { + $text: { + $search: options.search, + }, + } + : {}), + ...(options.sort ? { $sort: options.sort } : {}), }, context, ); diff --git a/tdrive/backend/node/src/services/documents/types.ts b/tdrive/backend/node/src/services/documents/types.ts index 2d77c6bc..81c8b86d 100644 --- a/tdrive/backend/node/src/services/documents/types.ts +++ b/tdrive/backend/node/src/services/documents/types.ts @@ -1,6 +1,7 @@ import { ExecutionContext } from "../../core/platform/framework/api/crud-service"; import { DriveFile } from "./entities/drive-file"; import { FileVersion } from "./entities/file-version"; +import { SortType } from "src/core/platform/services/search/api"; export interface CompanyExecutionContext extends ExecutionContext { company: { id: string }; @@ -40,14 +41,24 @@ export type SearchDocumentsOptions = { search?: string; company_id?: string; creator?: string; - added?: string; + added_gt?: number; + added_lt?: number; + mime_type?: Array; + last_modified_gt?: number; + last_modified_lt?: number; + sort?: SortType; }; export type SearchDocumentsBody = { search?: string; company_id?: string; creator?: string; - added?: string; + added_gt?: number; + added_lt?: number; + mime_type?: [string]; + last_modified_gt?: number; + last_modified_lt?: number; + sort?: SortType; }; export type DocumentsMessageQueueRequest = { diff --git a/tdrive/backend/node/src/services/documents/utils.ts b/tdrive/backend/node/src/services/documents/utils.ts index aa82ee81..6e5a9be1 100644 --- a/tdrive/backend/node/src/services/documents/utils.ts +++ b/tdrive/backend/node/src/services/documents/utils.ts @@ -47,11 +47,11 @@ export const getDefaultDriveItem = ( ): DriveFile => { const defaultDriveItem = merge>(new DriveFile(), { company_id: context.company.id, - added: item.added || new Date().getTime().toString(), + added: item.added || new Date().getTime(), creator: item.creator || context.user?.id, is_directory: item.is_directory || false, is_in_trash: item.is_in_trash || false, - last_modified: new Date().getTime().toString(), + last_modified: new Date().getTime(), parent_id: item.parent_id || "root", content_keywords: item.content_keywords || "", description: item.description || "", @@ -422,7 +422,9 @@ export const getFileMetadata = async ( * Finds a suitable name for an item based on items inside the same folder. * * @param {string} parent_id - the parent id. + * @param id * @param {string} name - the item name. + * @param is_directory * @param {Repository} repository - the drive repository. * @param {CompanyExecutionContext} context - the execution context. * @returns {Promise} - the drive item name. diff --git a/tdrive/backend/node/src/services/documents/web/controllers/documents.ts b/tdrive/backend/node/src/services/documents/web/controllers/documents.ts index 875cec45..acfd4057 100644 --- a/tdrive/backend/node/src/services/documents/web/controllers/documents.ts +++ b/tdrive/backend/node/src/services/documents/web/controllers/documents.ts @@ -65,7 +65,7 @@ export class DocumentsController { ); } catch (error) { logger.error("Failed to create Drive item", error); - throw new CrudException("Failed to create Drive item", 500); + CrudException.throwMe(error, new CrudException("Failed to create Drive item", 500)); } }; @@ -73,6 +73,7 @@ export class DocumentsController { * Deletes a DriveFile item or empty the trash or delete root folder contents * * @param {FastifyRequest} request + * @param {FastifyReply} reply * @returns {Promise} */ delete = async ( @@ -227,7 +228,7 @@ export class DocumentsController { * If the item is a folder, a zip will be automatically generated. * * @param {FastifyRequest} request - * @param {FastifyReply} reply + * @param {FastifyReply} response */ download = async ( request: FastifyRequest<{ @@ -342,26 +343,27 @@ export class DocumentsController { ): Promise> => { try { const context = getDriveExecutionContext(request); - const { search = "", added = "", company_id = "", creator = "" } = request.body; const options: SearchDocumentsOptions = { - company_id: company_id || context.company.id, - ...(search ? { search } : {}), - ...(added ? { added } : {}), - ...(creator ? { creator } : {}), + ...request.body, + company_id: request.body.company_id || context.company.id, }; if (!Object.keys(options).length) { - throw Error("Search options are empty"); + this.throw500Search(); } return await globalResolver.services.documents.documents.search(options, context); } catch (error) { logger.error("error while searching for document", error); - throw new CrudException("Failed to search for documents", 500); + this.throw500Search(); } }; + private throw500Search() { + throw new CrudException("Failed to search for documents", 500); + } + getTab = async ( request: FastifyRequest<{ Params: { tab_id: string; company_id: string }; diff --git a/tdrive/backend/node/src/services/files/services/index.ts b/tdrive/backend/node/src/services/files/services/index.ts index cb422ee6..0f6fe021 100644 --- a/tdrive/backend/node/src/services/files/services/index.ts +++ b/tdrive/backend/node/src/services/files/services/index.ts @@ -187,6 +187,9 @@ export class FileServiceImpl { }, ): Promise { let entity = await this.repository.findOne(pk, {}, context); + if (entity == null) { + throw new CrudException(`File not found ${pk.company_id}|${pk.id}`, 404); + } if (options?.waitForThumbnail) { for (let i = 1; i < 100; i++) { diff --git a/tdrive/backend/node/src/utils/types.ts b/tdrive/backend/node/src/utils/types.ts index 1f36f642..015fe5b1 100644 --- a/tdrive/backend/node/src/utils/types.ts +++ b/tdrive/backend/node/src/utils/types.ts @@ -103,7 +103,9 @@ export interface ResourceEventsPayload { } export interface PaginationQueryParameters { + // It is offset for MongoDB and pointer to the next page for ScyllaDB page_token?: string; + // Page size limit?: string; websockets?: boolean; } diff --git a/tdrive/backend/node/test/e2e/files/assets/sample.doc b/tdrive/backend/node/test/e2e/common/assets/sample.doc similarity index 100% rename from tdrive/backend/node/test/e2e/files/assets/sample.doc rename to tdrive/backend/node/test/e2e/common/assets/sample.doc diff --git a/tdrive/backend/node/test/e2e/files/assets/sample.gif b/tdrive/backend/node/test/e2e/common/assets/sample.gif similarity index 100% rename from tdrive/backend/node/test/e2e/files/assets/sample.gif rename to tdrive/backend/node/test/e2e/common/assets/sample.gif diff --git a/tdrive/backend/node/test/e2e/files/assets/sample.mp4 b/tdrive/backend/node/test/e2e/common/assets/sample.mp4 similarity index 100% rename from tdrive/backend/node/test/e2e/files/assets/sample.mp4 rename to tdrive/backend/node/test/e2e/common/assets/sample.mp4 diff --git a/tdrive/backend/node/test/e2e/files/assets/sample.pdf b/tdrive/backend/node/test/e2e/common/assets/sample.pdf similarity index 100% rename from tdrive/backend/node/test/e2e/files/assets/sample.pdf rename to tdrive/backend/node/test/e2e/common/assets/sample.pdf diff --git a/tdrive/backend/node/test/e2e/files/assets/sample.png b/tdrive/backend/node/test/e2e/common/assets/sample.png similarity index 100% rename from tdrive/backend/node/test/e2e/files/assets/sample.png rename to tdrive/backend/node/test/e2e/common/assets/sample.png diff --git a/tdrive/backend/node/test/e2e/files/assets/sample.zip b/tdrive/backend/node/test/e2e/common/assets/sample.zip similarity index 100% rename from tdrive/backend/node/test/e2e/files/assets/sample.zip rename to tdrive/backend/node/test/e2e/common/assets/sample.zip diff --git a/tdrive/backend/node/test/e2e/common/common_test_helpers.ts b/tdrive/backend/node/test/e2e/common/common_test_helpers.ts new file mode 100644 index 00000000..b71f7644 --- /dev/null +++ b/tdrive/backend/node/test/e2e/common/common_test_helpers.ts @@ -0,0 +1,192 @@ +// @ts-ignore +import fs from "fs"; +import {ResourceUpdateResponse, Workspace} from "../../../src/utils/types"; +import {File} from "../../../src/services/files/entities/file"; +import {deserialize} from "class-transformer"; +import formAutoContent from "form-auto-content"; +import {TestPlatform, User} from "../setup"; +import {v1 as uuidv1} from "uuid"; +import {TestDbService} from "../utils.prepare.db"; +import {DriveFile} from "../../../src/services/documents/entities/drive-file"; +import {FileVersion} from "../../../src/services/documents/entities/file-version"; +import {SearchResultMockClass} from "./entities/mock_entities"; +import {logger} from "../../../src/core/platform/framework"; + +export default class TestHelpers { + + private static readonly DOC_URL = "/internal/services/documents/v1"; + + static readonly ALL_FILES = [ + "sample.png", + "sample.gif", + "sample.pdf", + "sample.doc", + "sample.zip", + "sample.mp4", + ] + + platform: TestPlatform; + dbService: TestDbService; + user: User; + workspace: Workspace; + jwt: string; + + private constructor( + platform: TestPlatform, + ) { + this.platform = platform + } + + private async init(newUser: boolean) { + this.dbService = await TestDbService.getInstance(this.platform, true); + if (newUser) { + this.workspace = this.platform.workspace; + const workspacePK = {id: this.workspace.workspace_id, company_id: this.workspace.company_id}; + this.user = await this.dbService.createUser([workspacePK], {}, uuidv1()); + } else { + this.user = this.platform.currentUser; + this.workspace = this.platform.workspace; + } + this.jwt = this.getJWTTokenForUser(this.user.id); + } + + public static async getInstance(platform: TestPlatform, newUser = false): Promise { + const helpers = new TestHelpers(platform); + await helpers.init(newUser) + return helpers; + } + + async uploadFiles() { + return Promise.all(TestHelpers.ALL_FILES.map(f => this.uploadFile(f))); + } + + async uploadRandomFile() { + return await this.uploadFile(TestHelpers.ALL_FILES[Math.floor((Math.random()*TestHelpers.ALL_FILES.length))]) + } + + async uploadFile(filename: string) { + logger.info(`Upload ${filename} for the user: ${this.user.id}`); + const fullPath = `${__dirname}/assets/${filename}` + const url = "/internal/services/files/v1"; + const form = formAutoContent({file: fs.createReadStream(fullPath)}); + form.headers["authorization"] = `Bearer ${this.jwt}`; + + const filesUploadRaw = await this.platform.app.inject({ + method: "POST", + url: `${url}/companies/${this.platform.workspace.company_id}/files?thumbnail_sync=1`, + ...form, + }); + + const filesUpload: ResourceUpdateResponse = deserialize( + ResourceUpdateResponse, + filesUploadRaw.body, + ); + return filesUpload.resource; + } + + private getJWTTokenForUser(userId: string): string { + const payload = { + sub: userId, + role: "", + } + return this.platform.authService.sign(payload); + } + + async uploadFileAndCreateDocument( + filename: string + ) { + return this.uploadFile(filename).then(f => this.createDocumentFromFile(f)); + }; + + async uploadRandomFileAndCreateDocument() { + return this.uploadRandomFile().then(f => this.createDocumentFromFile(f)); + }; + + async uploadAllFilesAndCreateDocuments() { + return await Promise.all(TestHelpers.ALL_FILES.map(f => this.uploadFileAndCreateDocument(f))) + }; + + async uploadAllFilesOneByOne() { + const files: Array = []; + for (const idx in TestHelpers.ALL_FILES) { + const f = await this.uploadFile(TestHelpers.ALL_FILES[idx]); + const doc = await this.createDocumentFromFile(f); + files.push(doc); + } + return files; + }; + + async createDocument( + platform: TestPlatform, + item: Partial, + version: Partial + ) { + + return await platform.app.inject({ + method: "POST", + url: `${TestHelpers.DOC_URL}/companies/${platform.workspace.company_id}/item`, + headers: { + authorization: `Bearer ${this.jwt}`, + }, + payload: { + item, + version, + }, + }); + }; + + async createDocumentFromFile( + file: File + ) { + const item = { + name: file.metadata.name, + parent_id: "root", + company_id: file.company_id, + }; + + const version = { + file_metadata: { + name: file.metadata.name, + size: file.upload_data?.size, + thumbnails: [], + external_id: file.id + } + } + + const response = await this.createDocument(this.platform, item, version); + return deserialize(DriveFile, response.body); + }; + + async updateDocument( + id: string | "root" | "trash", + item: Partial + ) { + return await this.platform.app.inject({ + method: "POST", + url: `${TestHelpers.DOC_URL}/companies/${this.platform.workspace.company_id}/item/${id}`, + headers: { + authorization: `Bearer ${this.jwt}`, + }, + payload: item, + }); + }; + + async searchDocument ( + payload: Record + ){ + const response = await this.platform.app.inject({ + method: "POST", + url: `${TestHelpers.DOC_URL}/companies/${this.platform.workspace.company_id}/search`, + headers: { + authorization: `Bearer ${this.jwt}`, + }, + payload, + }); + + return deserialize( + SearchResultMockClass, + response.body) + }; + +} + diff --git a/tdrive/backend/node/test/e2e/common/entities/mock_entities.ts b/tdrive/backend/node/test/e2e/common/entities/mock_entities.ts new file mode 100644 index 00000000..fea587ba --- /dev/null +++ b/tdrive/backend/node/test/e2e/common/entities/mock_entities.ts @@ -0,0 +1,42 @@ +import {DriveFileAccessLevel, publicAccessLevel} from "../../../../src/services/documents/types"; + +export type MockAccessInformation = { + public?: { + token: string; + password: string; + expiration: number; + level: publicAccessLevel; + }; + entities: MockAuthEntity[]; +}; + +export type MockAuthEntity = { + type: "user" | "channel" | "company" | "folder"; + id: string | "parent"; + level: publicAccessLevel | DriveFileAccessLevel; +}; + +export class DriveFileMockClass { + id: string; + name: string; + size: number; + added: string; + parent_id: string; + extension: string; + description: string; + tags: string[]; + last_modified: string; + access_info: MockAccessInformation; + creator: string; +} + +export class DriveItemDetailsMockClass { + path: string[]; + item: DriveFileMockClass; + children: DriveFileMockClass[]; + versions: Record[]; +} + +export class SearchResultMockClass { + entities: DriveFileMockClass[]; +} \ No newline at end of file diff --git a/tdrive/backend/node/test/e2e/documents/documents.spec.ts b/tdrive/backend/node/test/e2e/documents/documents.spec.ts index 51537b3e..42bc5c24 100644 --- a/tdrive/backend/node/test/e2e/documents/documents.spec.ts +++ b/tdrive/backend/node/test/e2e/documents/documents.spec.ts @@ -1,5 +1,5 @@ import { describe, beforeEach, afterEach, it, expect, afterAll } from "@jest/globals"; -import { deserialize } from "class-transformer"; +import {deserialize} from "class-transformer"; import { File } from "../../../src/services/files/entities/file"; import { ResourceUpdateResponse } from "../../../src/utils/types"; import { init, TestPlatform } from "../setup"; @@ -13,28 +13,13 @@ import { e2e_searchDocument, e2e_updateDocument, } from "./utils"; +import TestHelpers from "../common/common_test_helpers"; +import {DriveFileMockClass, DriveItemDetailsMockClass, SearchResultMockClass} from "../common/entities/mock_entities"; describe("the Drive feature", () => { let platform: TestPlatform; - - class DriveFileMockClass { - id: string; - name: string; - size: number; - added: string; - parent_id: string; - } - - class DriveItemDetailsMockClass { - path: string[]; - item: DriveFileMockClass; - children: DriveFileMockClass[]; - versions: Record[]; - } - - class SearchResultMockClass { - entities: DriveFileMockClass[]; - } + let currentUser: TestHelpers; + let dbService: TestDbService; beforeEach(async () => { platform = await init({ @@ -59,6 +44,8 @@ describe("the Drive feature", () => { "documents", ], }); + currentUser = await TestHelpers.getInstance(platform); + dbService = await TestDbService.getInstance(platform, true); }); afterEach(async () => { @@ -170,8 +157,8 @@ describe("the Drive feature", () => { done?.(); }); - // TODO: wait for elastic search index it("did search for an item", async done => { + jest.setTimeout(10000); const createItemResult = await createItem(); expect(createItemResult.id).toBeDefined(); @@ -237,4 +224,230 @@ describe("the Drive feature", () => { done?.(); }); + + it("did search by mime type", async done => { + // given:: all the sample files uploaded and documents for them created + await Promise.all((await currentUser.uploadFiles()).map(f => currentUser.createDocumentFromFile(f))) + + const filters = { + mime_type: "application/pdf", + }; + + jest.setTimeout(10000); + await new Promise(r => setTimeout(r, 5000)); + + + let documents = await currentUser.searchDocument(filters); + expect(documents.entities).toHaveLength(1); + + const actualFile = documents.entities[0]; + expect(actualFile.name).toEqual("sample.pdf"); + + done?.(); + }); + + it("did search by last modified", async done => { + jest.setTimeout(10000); + const user = await TestHelpers.getInstance(platform, true); + // given:: all the sample files uploaded and documents for them created + const start = new Date().getTime(); + await user.uploadAllFilesOneByOne() + const end = new Date().getTime(); + await user.uploadAllFilesOneByOne() + //wait for putting docs to elastic and its indexing + await new Promise(r => setTimeout(r, 3000)); + + //then:: all the files are searchable without filters + let documents = await user.searchDocument({}); + expect(documents.entities).toHaveLength(TestHelpers.ALL_FILES.length * 2); + + //then:: only file uploaded in the [start, end] interval are shown in the search results + const filters = { + last_modified_gt: start.toString(), + last_modified_lt: end.toString() + }; + documents = await user.searchDocument(filters); + expect(documents.entities).toHaveLength(TestHelpers.ALL_FILES.length); + + done?.(); + }); + + it("did search a file shared by another user", async done => { + jest.setTimeout(30000); + //given: + const oneUser = await TestHelpers.getInstance(platform, true); + const anotherUser = await TestHelpers.getInstance(platform, true); + //upload files + let files = await oneUser.uploadAllFilesOneByOne() + + await new Promise(r => setTimeout(r, 5000)); + + //then:: files are not searchable for user without permissions + expect((await anotherUser.searchDocument({})).entities).toHaveLength(0); + + //and searchable for user that have + expect((await oneUser.searchDocument({})).entities).toHaveLength(TestHelpers.ALL_FILES.length); + + //give permissions to the file + files[0].access_info.entities.push({ + type: "user", + id: anotherUser.user.id, + level: "read" + }) + await oneUser.updateDocument(files[0].id, files[0]); + await new Promise(r => setTimeout(r, 3000)); + + //then file become searchable + expect((await anotherUser.searchDocument({})).entities).toHaveLength(1); + + done?.(); + }); + + it("did search a file by file owner", async done => { + jest.setTimeout(30000); + //given: + const oneUser = await TestHelpers.getInstance(platform, true); + const anotherUser = await TestHelpers.getInstance(platform, true); + //upload files + let files = await oneUser.uploadAllFilesOneByOne() + await anotherUser.uploadAllFilesOneByOne() + //give permissions for all files to 'another user' + await Promise.all(files.map(f => { + f.access_info.entities.push({ + type: "user", + id: anotherUser.user.id, + level: "read" + }) + return oneUser.updateDocument(f.id, f); + })); + + await new Promise(r => setTimeout(r, 5000)); + + //then:: all files are searchable for 'another user' + expect((await anotherUser.searchDocument({})).entities).toHaveLength(TestHelpers.ALL_FILES.length * 2); + + //and searchable for user that have + expect((await oneUser.searchDocument({ + creator: oneUser.user.id, + })).entities).toHaveLength(TestHelpers.ALL_FILES.length); + + done?.(); + + }); + + it("did search by 'added' date", async done => { + jest.setTimeout(10000); + const user = await TestHelpers.getInstance(platform, true); + // given:: all the sample files uploaded and documents for them created + await user.uploadRandomFileAndCreateDocument(); + const start = new Date().getTime(); + await user.uploadAllFilesAndCreateDocuments() + const end = new Date().getTime(); + await user.uploadRandomFileAndCreateDocument(); + //wait for putting docs to elastic and its indexing + await new Promise(r => setTimeout(r, 3000)); + + //then:: all the files are searchable without filters + let documents = await user.searchDocument({}); + expect(documents.entities).toHaveLength(TestHelpers.ALL_FILES.length + 2); + + //then:: only file uploaded in the [start, end] interval are shown in the search results + const filters = { + added_gt: start.toString(), + added_lt: end.toString() + }; + documents = await user.searchDocument(filters); + expect(documents.entities).toHaveLength(TestHelpers.ALL_FILES.length); + + done?.(); + }); + + it("did search order by name", async done => { + jest.setTimeout(10000); + const user = await TestHelpers.getInstance(platform, true); + // given:: all the sample files uploaded and documents for them created + await user.uploadAllFilesAndCreateDocuments() + //wait for putting docs to elastic and its indexing + await new Promise(r => setTimeout(r, 5000)); + + //when:: sort files by name is ascending order + const options = { + sort: { + name_keyword: "asc", + } + }; + const documents = await user.searchDocument(options); + + //then all the files are sorted properly by name + expect(documents.entities.map(e => e.name)).toEqual(TestHelpers.ALL_FILES.sort()); + done?.(); + }); + + it("did search order by name desc", async done => { + jest.setTimeout(10000); + const user = await TestHelpers.getInstance(platform, true); + // given:: all the sample files uploaded and documents for them created + await user.uploadAllFilesOneByOne() + //wait for putting docs to elastic and its indexing + await new Promise(r => setTimeout(r, 5000)); + + //when:: sort files by name is ascending order + const options = { + sort: { + name_keyword: "desc", + } + }; + const documents = await user.searchDocument(options); + + //then all the files are sorted properly by name + expect(documents.entities.map(e => e.name)).toEqual(TestHelpers.ALL_FILES.sort().reverse()); + done?.(); + }); + + it("did search order by added date", async done => { + jest.setTimeout(10000); + const user = await TestHelpers.getInstance(platform, true); + // given:: all the sample files uploaded and documents for them created + await user.uploadAllFilesOneByOne(); + //wait for putting docs to elastic and its indexing + await new Promise(r => setTimeout(r, 5000)); + + //when:: ask to sort files by the 'added' field + const options = { + sort: { + added: "asc", + } + }; + const documents = await user.searchDocument(options); + + //then:: files should be sorted properly + expect(documents.entities.map(e => e.name)).toEqual(TestHelpers.ALL_FILES); + + done?.(); + }); + + it("did search order by added date desc", async done => { + jest.setTimeout(10000); + const user = await TestHelpers.getInstance(platform, true); + // given:: all the sample files uploaded and documents for them created + await user.uploadAllFilesOneByOne(); + //wait for putting docs to elastic and its indexing + await new Promise(r => setTimeout(r, 5000)); + + //when:: ask to sort files by the 'added' field desc + const options = { + sort: { + added: "desc", + } + }; + const documents = await user.searchDocument(options); + + //then:: files should be sorted properly + expect(documents.entities.map(e => e.name)).toEqual(TestHelpers.ALL_FILES.reverse()); + + done?.(); + }); + + + }); diff --git a/tdrive/backend/node/test/e2e/files/files.spec.ts b/tdrive/backend/node/test/e2e/files/files.spec.ts index 9f00e252..5a6de1ac 100644 --- a/tdrive/backend/node/test/e2e/files/files.spec.ts +++ b/tdrive/backend/node/test/e2e/files/files.spec.ts @@ -1,25 +1,24 @@ import "reflect-metadata"; import { afterAll, beforeAll, describe, expect, it } from "@jest/globals"; import { init, TestPlatform } from "../setup"; -import { ResourceUpdateResponse } from "../../../src/utils/types"; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import fs from "fs"; -import { File } from "../../../src/services/files/entities/file"; -import { deserialize } from "class-transformer"; -import formAutoContent from "form-auto-content"; import LocalConnectorService from "../../../src/core/platform/services/storage/connectors/local/service"; +import TestHelpers from "../common/common_test_helpers"; describe("The Files feature", () => { const url = "/internal/services/files/v1"; let platform: TestPlatform; + let helpers: TestHelpers; beforeAll(async () => { platform = await init({ services: ["webserver", "database", "storage", "files", "previews"], }); await platform.database.getConnector().init(); + helpers = await TestHelpers.getInstance(platform) }); afterAll(async done => { @@ -28,37 +27,13 @@ describe("The Files feature", () => { done(); }); - async function uploadFile(file: string) { - const form = formAutoContent({file: fs.createReadStream(file)}); - form.headers["authorization"] = `Bearer ${await platform.auth.getJWTToken()}`; - - const filesUploadRaw = await platform.app.inject({ - method: "POST", - url: `${url}/companies/${platform.workspace.company_id}/files?thumbnail_sync=1`, - ...form, - }); - const filesUpload: ResourceUpdateResponse = deserialize( - ResourceUpdateResponse, - filesUploadRaw.body, - ); - return filesUpload; - } - describe("On user send files", () => { - const files = [ - "assets/sample.png", - "assets/sample.gif", - "assets/sample.pdf", - "assets/sample.doc", - "assets/sample.zip", - "assets/sample.mp4", - ].map(p => `${__dirname}/${p}`); const thumbnails = [1, 1, 2, 5, 0, 1]; it("Download file should return 500 if file doesn't exists", async () => { //given file - const filesUpload = await uploadFile(files[0]); - expect(filesUpload.resource.id).toBeTruthy(); + const filesUpload = await helpers.uploadRandomFile(); + expect(filesUpload.id).toBeTruthy(); //clean files directory expect(platform.storage.getConnector()).toBeInstanceOf(LocalConnectorService) const path = (platform.storage.getConnector()).configuration.path; @@ -66,7 +41,7 @@ describe("The Files feature", () => { //when try to download the file const fileDownloadResponse = await platform.app.inject({ method: "GET", - url: `${url}/companies/${platform.workspace.company_id}/files/${filesUpload.resource.id}/download`, + url: `${url}/companies/${platform.workspace.company_id}/files/${filesUpload.id}/download`, }); //then file should be not found with 404 error and "File not found message" expect(fileDownloadResponse).toBeTruthy(); @@ -76,15 +51,15 @@ describe("The Files feature", () => { it("Download file should return 200 if file exists", async () => { //given file - const filesUpload = await uploadFile(files[0]); - expect(filesUpload.resource.id).toBeTruthy(); + const filesUpload = await helpers.uploadRandomFile() + expect(filesUpload.id).toBeTruthy(); //clean files directory expect(platform.storage.getConnector()).toBeInstanceOf(LocalConnectorService) //when try to download the file const fileDownloadResponse = await platform.app.inject({ method: "GET", - url: `${url}/companies/${platform.workspace.company_id}/files/${filesUpload.resource.id}/download`, + url: `${url}/companies/${platform.workspace.company_id}/files/${filesUpload.id}/download`, }); //then file should be not found with 404 error and "File not found message" expect(fileDownloadResponse).toBeTruthy(); @@ -94,20 +69,20 @@ describe("The Files feature", () => { it.skip("should save file and generate previews", async done => { - for (const i in files) { - const file = files[i]; + for (const i in TestHelpers.ALL_FILES) { + const file = TestHelpers.ALL_FILES[i]; - const filesUpload = await uploadFile(file); + const filesUpload = await helpers.uploadFile(file); - expect(filesUpload.resource.id).not.toBeFalsy(); - expect(filesUpload.resource.encryption_key).toBeFalsy(); //This must not be disclosed - expect(filesUpload.resource.thumbnails.length).toBe(thumbnails[i]); + expect(filesUpload.id).not.toBeFalsy(); + expect(filesUpload.encryption_key).toBeFalsy(); //This must not be disclosed + expect(filesUpload.thumbnails.length).toBe(thumbnails[i]); - for (const thumb of filesUpload.resource.thumbnails) { + for (const thumb of filesUpload.thumbnails) { const thumbnails = await platform.app.inject({ headers: {"authorization": `Bearer ${await platform.auth.getJWTToken()}`}, method: "GET", - url: `${url}/companies/${platform.workspace.company_id}/files/${filesUpload.resource.id}/thumbnails/${thumb.index}`, + url: `${url}/companies/${platform.workspace.company_id}/files/${filesUpload.id}/thumbnails/${thumb.index}`, }); expect(thumbnails.statusCode).toBe(200); } diff --git a/tdrive/backend/node/test/e2e/setup/index.ts b/tdrive/backend/node/test/e2e/setup/index.ts index 6e5e94da..44273491 100644 --- a/tdrive/backend/node/test/e2e/setup/index.ts +++ b/tdrive/backend/node/test/e2e/setup/index.ts @@ -13,6 +13,7 @@ import config from "config"; import globalResolver from "../../../src/services/global-resolver"; import {FileServiceImpl} from "../../../src/services/files/services"; import StorageAPI from "../../../src/core/platform/services/storage/provider"; +import {SearchServiceAPI} from "../../../src/core/platform/services/search/api"; type TokenPayload = { sub: string; @@ -23,7 +24,7 @@ type TokenPayload = { }; }; -type User = { +export type User = { id: string; isWorkspaceModerator?: boolean; }; @@ -42,6 +43,7 @@ export interface TestPlatform { getJWTToken(payload?: TokenPayload): Promise; }; tearDown(): Promise; + search: SearchServiceAPI; } export interface TestPlatformConfiguration { @@ -76,6 +78,7 @@ export async function init( const messageQueue = platform.getProvider("message-queue"); const auth = platform.getProvider("auth"); const storage: StorageAPI = platform.getProvider("storage"); + const search: SearchServiceAPI = platform.getProvider("search"); testPlatform = { platform, @@ -91,6 +94,7 @@ export async function init( getJWTToken, }, tearDown, + search, }; } @@ -112,7 +116,7 @@ export async function init( payload.sub = testPlatform.currentUser.id; } - if (testPlatform .currentUser.isWorkspaceModerator) { + if (testPlatform.currentUser.isWorkspaceModerator) { payload.org = {}; payload.org[testPlatform.workspace.company_id] = { role: "", diff --git a/tdrive/backend/node/test/e2e/setup/default.test.json b/tdrive/backend/node/test/e2e/setup/test.json similarity index 74% rename from tdrive/backend/node/test/e2e/setup/default.test.json rename to tdrive/backend/node/test/e2e/setup/test.json index ec387742..c12ad975 100644 --- a/tdrive/backend/node/test/e2e/setup/default.test.json +++ b/tdrive/backend/node/test/e2e/setup/test.json @@ -6,6 +6,12 @@ "level": "warn" } }, + "search": { + "type": "elasticsearch", + "elasticsearch": { + "endpoint": "http://localhost:9200" + } + }, "websocket": { "path": "/socket", "adapters": { diff --git a/tdrive/backend/node/test/http/badges.http b/tdrive/backend/node/test/http/badges.http deleted file mode 100644 index 932df6ea..00000000 --- a/tdrive/backend/node/test/http/badges.http +++ /dev/null @@ -1,19 +0,0 @@ -@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c -@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71b -@user_id = 3328552c-5ccd-4172-b84a-d876d56aa71c - -@baseURL = http://localhost:3000 -@badgesURL = {{baseURL}}/internal/services/notifications/v1/badges - -# @name login -GET {{baseURL}}/api/auth/login - -@authToken = {{login.response.body.token}} -@currentUserId = {{login.response.body.user.id}} - -### List badges with all websockets - -GET {{badgesURL}}/?company_id={{company_id}}&websockets=true&limit=5 -Content-Type: application/json -Authorization: Bearer {{authToken}} - diff --git a/tdrive/backend/node/test/http/channel-as-read.http b/tdrive/backend/node/test/http/channel-as-read.http deleted file mode 100644 index 2ee7cdc0..00000000 --- a/tdrive/backend/node/test/http/channel-as-read.http +++ /dev/null @@ -1,61 +0,0 @@ -@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c -@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71b -@user_id = 3328552c-5ccd-4172-b84a-d876d56aa71c - -@baseURL = http://localhost:3000 -@channelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}} -@directChannelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/direct - -# @name login -GET {{baseURL}}/api/auth/login - -@authToken = {{login.response.body.token}} -@currentUserId = {{login.response.body.user.id}} - -### Create a channel - -# @name createChannel -POST {{channelsURL}}/channels -Content-Type: application/json -Authorization: Bearer {{authToken}} - -{ - "resource": { - "name": "My channel", - "icon": "tdrive logo", - "description": "This channel allow tdrive's team to chat easily", - "channel_group": "tdrive", - "visibility": "public", - "is_default": true, - "archived": false - } -} - -### Get a single channel - -@getId = {{createChannel.response.body.resource.id}} - -GET {{channelsURL}}/channels/{{getId}} -Authorization: Bearer {{authToken}} - -### Add current user as member to a channel (join channel) - -POST {{channelsURL}}/channels/{{getId}}/members -Content-Type: application/json -Authorization: Bearer {{authToken}} - -{ - "resource": { - "user_id": "{{currentUserId}}" - } -} - -### Mark the channel as read/unread - -POST {{channelsURL}}/channels/{{getId}}/read -Content-Type: application/json -Authorization: Bearer {{authToken}} - -{ - "value": true -} diff --git a/tdrive/backend/node/test/http/channels.http b/tdrive/backend/node/test/http/channels.http deleted file mode 100644 index c665d84c..00000000 --- a/tdrive/backend/node/test/http/channels.http +++ /dev/null @@ -1,133 +0,0 @@ -@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c -@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71b -@user_id = 3328552c-5ccd-4172-b84a-d876d56aa71c - -@baseURL = http://localhost:3000 -@channelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}} -@directChannelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/direct - -# @name login -GET {{baseURL}}/api/auth/login - -@authToken = {{login.response.body.token}} -@currentUserId = {{login.response.body.user.id}} - -### List workspace channels with all websockets - -GET {{channelsURL}}/channels?websockets=true&limit=5 -Content-Type: application/json -Authorization: Bearer {{authToken}} - -### List user channels with all websockets - -@authToken = {{login.response.body.token}} -GET {{channelsURL}}/channels?websockets=true&mine=true&limit=5 -Content-Type: application/json -Authorization: Bearer {{authToken}} - -### Create a channel - -# @name createChannel -POST {{channelsURL}}/channels -Content-Type: application/json -Authorization: Bearer {{authToken}} - -{ - "resource": { - "name": "My channel", - "icon": "tdrive logo", - "description": "This channel allow tdrive's team to chat easily", - "channel_group": "tdrive", - "visibility": "public", - "is_default": true, - "archived": false - } -} - -### Get a single channel - -@getId = {{createChannel.response.body.resource.id}} - -GET {{channelsURL}}/channels/{{getId}} -Authorization: Bearer {{authToken}} - -### Update a channel -@updateId = {{createChannel.response.body.resource.id}} - -POST {{channelsURL}}/channels/{{updateId}} -Content-Type: application/json -Authorization: Bearer {{authToken}} - -{ - "resource": { - "name": "My channel", - "description": "Hello world", - "is_default": false - } -} - -### Delete a channel - -@deleteId = {{createChannel.response.body.resource.id}} - -DELETE {{channelsURL}}/channels/{{deleteId}} -Authorization: Bearer {{authToken}} - - -### Get all channel members - -GET {{channelsURL}}/channels/{{getId}}/members?websockets=true -Content-Type: application/json -Authorization: Bearer {{authToken}} - - -### Add current user as member to a channel (join channel) - -POST {{channelsURL}}/channels/{{getId}}/members -Content-Type: application/json -Authorization: Bearer {{authToken}} - -{ - "resource": { - "user_id": "{{currentUserId}}" - } -} - -### Get a channel member - -GET {{channelsURL}}/channels/{{getId}}/members/{{currentUserId}} -Content-Type: application/json -Authorization: Bearer {{authToken}} - -### Update current channel member - -POST {{channelsURL}}/channels/{{getId}}/members/{{currentUserId}} -Content-Type: application/json -Authorization: Bearer {{authToken}} - -{ - "resource": { - "favorite": true, - "notification_level": "none", - "hello": 1 - } -} - -### Current user quits the channel - -DELETE {{channelsURL}}/channels/{{getId}}/members/{{currentUserId}} -Content-Type: application/json -Authorization: Bearer {{authToken}} - -### Errors Tests - -### Call without the JWT token should HTTP 401 - -GET {{channelsURL}}/channels -Content-Type: application/json - -### Get a channel which may not exists - -GET {{channelsURL}}/channels/0b0e1492-f596-46b9-a4fb-c12d71b2696e -Authorization: Bearer {{authToken}} - diff --git a/tdrive/backend/node/test/http/direct-channels.http b/tdrive/backend/node/test/http/direct-channels.http deleted file mode 100644 index c3bbe15d..00000000 --- a/tdrive/backend/node/test/http/direct-channels.http +++ /dev/null @@ -1,163 +0,0 @@ -@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c -@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71b - -@baseURL = http://localhost:3000 -@channelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}} -@directChannelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/direct - -### Login as user 1 - -# @name login -GET {{baseURL}}/api/auth/login - -@authToken1 = {{login.response.body.token}} -@userId1 = {{login.response.body.user.id}} - -### Login as user 2 - -# @name login2 - -GET {{baseURL}}/api/auth/login - -@authToken2 = {{login2.response.body.token}} -@userId2 = {{login2.response.body.user.id}} - -### Login as user 3 - -# @name login3 - -GET {{baseURL}}/api/auth/login - -@authToken3 = {{login3.response.body.token}} -@userId3 = {{login3.response.body.user.id}} - -### Create a direct channel - -# @name createDirectChannel -POST {{directChannelsURL}}/channels -Content-Type: application/json -Authorization: Bearer {{authToken1}} - -{ - "options": { - "members": ["{{userId2}}"] - }, - "resource": { - "icon": "hello", - "description": "A direct channel", - "channel_group": "tdrive", - "is_default": false, - "archived": false - } -} - -### Direct channel details as user 1 should work -@directId = {{createDirectChannel.response.body.resource.id}} - -GET {{directChannelsURL}}/channels/{{directId}} -Content-Type: application/json -Authorization: Bearer {{authToken1}} - -### Direct channel details as user 2 (member) should work - -GET {{directChannelsURL}}/channels/{{directId}} -Content-Type: application/json -Authorization: Bearer {{authToken2}} - -### Direct channel details as user 3 (not member) should not work - -GET {{directChannelsURL}}/channels/{{directId}} -Content-Type: application/json -Authorization: Bearer {{authToken3}} - -### Direct channel members - -GET {{directChannelsURL}}/channels/{{directId}}/members?websockets=true -Content-Type: application/json -Authorization: Bearer {{authToken1}} - -### List direct channels for user in company - -@authToken = {{login.response.body.token}} -GET {{directChannelsURL}}/channels?websockets=true&limit=5 -Content-Type: application/json -Authorization: Bearer {{authToken1}} - -### Update the direct channel description - -POST {{directChannelsURL}}/channels/{{directId}} -Content-Type: application/json -Authorization: Bearer {{authToken1}} - -{ - "resource": { - "description": "A new direct channel description" - } -} - -### Update the direct channel name will do nothing since the name is useless -POST {{directChannelsURL}}/channels/{{directId}} -Content-Type: application/json -Authorization: Bearer {{authToken1}} - -{ - "resource": { - "name": "A new direct channel name" - } -} - -### Update the direct channel member settings for current user - -POST {{directChannelsURL}}/channels/{{directId}}/members/{{userId1}} -Content-Type: application/json -Authorization: Bearer {{authToken1}} - -{ - "resource": { - "favorite": true, - "notification_level": "all" - } -} - -### Update another user member settings should fail - -POST {{directChannelsURL}}/channels/{{directId}}/members/{{userId2}} -Content-Type: application/json -Authorization: Bearer {{authToken1}} - -{ - "resource": { - "favorite": true, - "notification_level": "none" - } -} - -### Get member settings as user1 - -GET {{directChannelsURL}}/channels/{{directId}}/members/{{userId1}} -Content-Type: application/json -Authorization: Bearer {{authToken1}} - -### Get member settings as user2 - -GET {{directChannelsURL}}/channels/{{directId}}/members/{{userId2}} -Content-Type: application/json -Authorization: Bearer {{authToken2}} - -### Get other member settings should fail - -GET {{directChannelsURL}}/channels/{{directId}}/members/{{userId2}} -Content-Type: application/json -Authorization: Bearer {{authToken1}} - -### Leave a direct channel of another user should fail - -DELETE {{directChannelsURL}}/channels/{{directId}}/members/{{userId2}} -Content-Type: application/json -Authorization: Bearer {{authToken1}} - -### Leave a direct channel should not fail - -DELETE {{directChannelsURL}}/channels/{{directId}}/members/{{userId1}} -Content-Type: application/json -Authorization: Bearer {{authToken1}} diff --git a/tdrive/backend/node/test/http/elasticsearch.http b/tdrive/backend/node/test/http/elasticsearch.http new file mode 100644 index 00000000..bcc1eae8 --- /dev/null +++ b/tdrive/backend/node/test/http/elasticsearch.http @@ -0,0 +1,251 @@ +# The task is: +# to find all the files that I havee access to, it shoud be a doc files, +# that was modified during the last week and share with my by Diana Potoking + + +### Drop index +DELETE https://localhost:9200/tdrive_files_extended + +### Delete Index +DELETE https://localhost:9200/drive_files + +### Create index +PUT https://localhost:9200/tdrive_files_extended +Content-Type: application/json + +{ + "drive_files": { + "aliases": {}, + "mappings": { + "_source": { + "enabled": true + } + } + } +} + + + +### Check that it is created +GET https://localhost:9200/_cat/indices + +### GET index info +GET https://localhost:9200/drive_files + +### GET data to the index +GET https://localhost:9200/drive_files/_doc/8c03c5a1-0146-11ee-82d9-f503f8a58e9d +Content-Type: application/json + +### Put data to the index +PUT https://localhost:9200/tdrive_files_extended/_doc/0001 +Content-Type: application/json + +{ + "file_name": "file0001.txt", + "access_info": [ + { + "type": "user", + "id": "user01", + "level": "read", + "grantor": "user02" + } + ] +} + +### +PUT https://localhost:9200/tdrive_files_extended/_doc/8c03c5a1-0146-11ee-82d9-f503f8a58e9d +Content-Type: application/json + +{ + "file_name": "file0002.png", + "access_info": [ + { + "type": "user", + "id": "user01", + "level": "read", + "grantor": "user03" + } + ] +} + +### Search all in the index by last_modified +GET https://localhost:9200/drive_files/_search +Content-Type: application/json + +{ + "query": { + + "bool": { + "boost": 1, + "must": [ + { + "range": + { + "last_modified": { + "gte": "0" + } + } + }, + { + "range": + { + "last_modified": { + "lte": "1685958426348" + } + } + }, + { + "bool": { + "should": [ + { + "match": { + "access_entities": { + "query": "edfb30e0-0385-11ee-80e8-41892804174d", + "operator": "AND" + } + } + }, + { + "match": { + "access_entities": { + "query": "edd447f1-0385-11ee-80e8-41892804174d", + "operator": "AND" + } + } + } + ], + "minimum_should_match": 1 + } + }, + { + "bool": { + "should": [ + { + "match": { + "company_id": { + "query": "edd447f1-0385-11ee-80e8-41892804174d", + "operator": "AND" + } + } + } + ], + "minimum_should_match": 1 + } + } + ] + } + } +} + + +### Search all in the index and order by name +GET https://localhost:9200/drive_files/_search +Content-Type: application/json + +{ + "query": { + "bool": { + "boost": 1, + "must": [ + { + "bool": { + "should": [ + { + "match": { + "access_entities": { + "query": "12fb6a40-03b3-11ee-af31-a569970b7f74", + "operator": "AND" + } + } + }, + { + "match": { + "access_entities": { + "query": "12f290a1-03b3-11ee-af31-a569970b7f74", + "operator": "AND" + } + } + } + ], + "minimum_should_match": 1 + } + }, + { + "bool": { + "should": [ + { + "match": { + "company_id": { + "query": "12f290a1-03b3-11ee-af31-a569970b7f74", + "operator": "AND" + } + } + } + ], + "minimum_should_match": 1 + } + } + ] + } + }, + "sort": [ + { + "name": "asc" + } + ] +} + +### Search all in the index and order by name +GET https://localhost:9200/drive_files/_search +Content-Type: application/json + +{ + "query": { + "bool": { + "boost": 1, + "must": [ + { + "bool": { + "should": [ + { + "match": { + "access_entities": { + "query": "4090dce0-03ba-11ee-97d7-fb8fa15d86a1", + "operator": "AND" + } + } + }, + { + "match": { + "access_entities": { + "query": "40808931-03ba-11ee-97d7-fb8fa15d86a1", + "operator": "AND" + } + } + } + ], + "minimum_should_match": 1 + } + }, + { + "bool": { + "should": [ + { + "match": { + "company_id": { + "query": "40808931-03ba-11ee-97d7-fb8fa15d86a1", + "operator": "AND" + } + } + } + ], + "minimum_should_match": 1 + } + } + ] + } + } +} + + + diff --git a/tdrive/backend/node/test/http/private-channels-membership.http b/tdrive/backend/node/test/http/private-channels-membership.http deleted file mode 100644 index 5b1fc3f1..00000000 --- a/tdrive/backend/node/test/http/private-channels-membership.http +++ /dev/null @@ -1,108 +0,0 @@ -@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c -@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71b -@baseURL = http://localhost:3000 -@channelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}} -@directChannelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/direct - -### Login as user 1 - -# @name login -GET {{baseURL}}/api/auth/login - -@authTokenUser1 = {{login.response.body.token}} -@currentUserId1 = {{login.response.body.user.id}} - -### Login as user 2 - -# @name login2 -GET {{baseURL}}/api/auth/login - -@authTokenUser2 = {{login2.response.body.token}} -@currentUserId2 = {{login2.response.body.user.id}} - -### User 1 creates a private channel - -# @name createChannel -POST {{channelsURL}}/channels -Content-Type: application/json -Authorization: Bearer {{authTokenUser1}} - -{ - "resource": { - "name": "My private channel", - "icon": "tdrive logo", - "description": "This channel allow tdrive's team to chat easily", - "channel_group": "tdrive", - "visibility": "private", - "is_default": true, - "archived": false - } -} - -### Get the private channel - -@getId = {{createChannel.response.body.resource.id}} - -GET {{channelsURL}}/channels/{{getId}} -Authorization: Bearer {{authTokenUser1}} - -### Get all channel members - -GET {{channelsURL}}/channels/{{getId}}/members?websockets=true -Content-Type: application/json -Authorization: Bearer {{authTokenUser1}} - - -### The user 2 tries to join the private channel, this should reject - -POST {{channelsURL}}/channels/{{getId}}/members -Content-Type: application/json -Authorization: Bearer {{authTokenUser2}} - -{ - "resource": { - "user_id": "{{currentUserId2}}" - } -} - -### The user 1 adds the user 2 as channel member, this should be OK - -POST {{channelsURL}}/channels/{{getId}}/members -Content-Type: application/json -Authorization: Bearer {{authTokenUser1}} - -{ - "resource": { - "user_id": "{{currentUserId2}}" - } -} - -### User 2 lists his channels - -GET {{channelsURL}}/channels?websockets=true&mine=true&limit=5 -Content-Type: application/json -Authorization: Bearer {{authTokenUser2}} - -### Get all channel members as user 1 - -GET {{channelsURL}}/channels/{{getId}}/members?websockets=true -Content-Type: application/json -Authorization: Bearer {{authTokenUser1}} - -### Get all channel members as user 2 - -GET {{channelsURL}}/channels/{{getId}}/members?websockets=true -Content-Type: application/json -Authorization: Bearer {{authTokenUser2}} - -### User 1 leaves the channel he created - -DELETE {{channelsURL}}/channels/{{getId}}/members/{{currentUserId1}} -Content-Type: application/json -Authorization: Bearer {{authTokenUser1}} - -### User 2 leaves the channel: Error since he is the last member, he can not leave - -DELETE {{channelsURL}}/channels/{{getId}}/members/{{currentUserId2}} -Content-Type: application/json -Authorization: Bearer {{authTokenUser2}} diff --git a/tdrive/backend/node/test/http/private-channels.http b/tdrive/backend/node/test/http/private-channels.http deleted file mode 100644 index cbfecc4a..00000000 --- a/tdrive/backend/node/test/http/private-channels.http +++ /dev/null @@ -1,70 +0,0 @@ -@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c -@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71b -@user_id = 3328552c-5ccd-4172-b84a-d876d56aa71c - -@baseURL = http://localhost:3000 -@channelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}} -@directChannelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/direct - -# @name login -GET {{baseURL}}/api/auth/login - -@authToken = {{login.response.body.token}} -@currentUserId = {{login.response.body.user.id}} - -### List workspace channels with all websockets - -GET {{channelsURL}}/channels?websockets=true&limit=5 -Content-Type: application/json -Authorization: Bearer {{authToken}} - -### List user channels with all websockets - -@authToken = {{login.response.body.token}} -GET {{channelsURL}}/channels?websockets=true&mine=true&limit=5 -Content-Type: application/json -Authorization: Bearer {{authToken}} - -### Create a private channel - -# @name createChannel -POST {{channelsURL}}/channels -Content-Type: application/json -Authorization: Bearer {{authToken}} - -{ - "resource": { - "name": "My private channel", - "icon": "tdrive logo", - "description": "A private channel", - "channel_group": "tdrive", - "visibility": "private", - "is_default": true, - "archived": false - } -} - -### Get the private channel - -@getId = {{createChannel.response.body.resource.id}} - -GET {{channelsURL}}/channels/{{getId}} -Authorization: Bearer {{authToken}} - -### Get members of the private channel - -GET {{channelsURL}}/channels/{{getId}}/members?websockets=true -Content-Type: application/json -Authorization: Bearer {{authToken}} - -### Get current user as member - -GET {{channelsURL}}/channels/{{getId}}/members/{{currentUserId}} -Content-Type: application/json -Authorization: Bearer {{authToken}} - -### Current user quits the private channel will fail because he is the only one in it - -DELETE {{channelsURL}}/channels/{{getId}}/members/{{currentUserId}} -Content-Type: application/json -Authorization: Bearer {{authToken}} diff --git a/tdrive/backend/node/test/http/public-channels-membership.http b/tdrive/backend/node/test/http/public-channels-membership.http deleted file mode 100644 index 04123337..00000000 --- a/tdrive/backend/node/test/http/public-channels-membership.http +++ /dev/null @@ -1,108 +0,0 @@ -@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c -@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71b -@baseURL = http://localhost:3000 -@channelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}} -@directChannelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/direct - -### Login as user 1 - -# @name login -GET {{baseURL}}/api/auth/login - -@authTokenUser1 = {{login.response.body.token}} -@currentUserId1 = {{login.response.body.user.id}} - -### Login as user 2 - -# @name login2 -GET {{baseURL}}/api/auth/login - -@authTokenUser2 = {{login2.response.body.token}} -@currentUserId2 = {{login2.response.body.user.id}} - -### User 1 creates a public channel - -# @name createChannel -POST {{channelsURL}}/channels -Content-Type: application/json -Authorization: Bearer {{authTokenUser1}} - -{ - "resource": { - "name": "A public channel", - "icon": "tdrive logo", - "description": "This channel allow tdrive's team to chat easily", - "channel_group": "tdrive", - "visibility": "public", - "is_default": true, - "archived": false - } -} - -### Get the public channel - -@getId = {{createChannel.response.body.resource.id}} - -GET {{channelsURL}}/channels/{{getId}} -Authorization: Bearer {{authTokenUser1}} - -### Get all channel members - -GET {{channelsURL}}/channels/{{getId}}/members?websockets=true -Content-Type: application/json -Authorization: Bearer {{authTokenUser1}} - - -### The user 2 joins the public channel, this should be OK - -POST {{channelsURL}}/channels/{{getId}}/members -Content-Type: application/json -Authorization: Bearer {{authTokenUser2}} - -{ - "resource": { - "user_id": "{{currentUserId2}}" - } -} - -### Get all channel members, there are now 2 members - -GET {{channelsURL}}/channels/{{getId}}/members?websockets=true -Content-Type: application/json -Authorization: Bearer {{authTokenUser1}} - -### User 1 lists his channels - -GET {{channelsURL}}/channels?mine=true -Content-Type: application/json -Authorization: Bearer {{authTokenUser1}} - -### User 2 lists his channels - -GET {{channelsURL}}/channels?mine=true -Content-Type: application/json -Authorization: Bearer {{authTokenUser2}} - -### User 2 leaves the channel - -DELETE {{channelsURL}}/channels/{{getId}}/members/{{currentUserId2}} -Content-Type: application/json -Authorization: Bearer {{authTokenUser2}} - -### Get all channel members, there are now 1 member, the initial one - -GET {{channelsURL}}/channels/{{getId}}/members?websockets=true -Content-Type: application/json -Authorization: Bearer {{authTokenUser1}} - -### List channels as user 2, current channel appears since it is public - -GET {{channelsURL}}/channels -Content-Type: application/json -Authorization: Bearer {{authTokenUser2}} - -### User 2 list his public channels, current channel does not appear since he left it - -GET {{channelsURL}}/channels?mine=true -Content-Type: application/json -Authorization: Bearer {{authTokenUser2}} \ No newline at end of file diff --git a/tdrive/backend/node/test/http/tabs.http b/tdrive/backend/node/test/http/tabs.http deleted file mode 100644 index fcd432b4..00000000 --- a/tdrive/backend/node/test/http/tabs.http +++ /dev/null @@ -1,85 +0,0 @@ -@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c -@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71a - - -@baseURL = http://localhost:3000 -@tabsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}}/channels -@channelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}} - - -# @name login -GET {{baseURL}}/api/auth/login - -@authToken = {{login.response.body.token}} -@currentUserId = {{login.response.body.user.id}} - - -### List channel's tab with all websockets -GET {{tabsURL}}/{{channelId}}/tabs?websockets=true&limit=5 -Content-Type: application/json -Authorization: Bearer {{authToken}} - - -### Create a channel -# @name createChannel -POST {{channelsURL}}/channels -Content-Type: application/json -Authorization: Bearer {{authToken}} - -{ - "resource": { - "name": "My channel", - "icon": "tdrive logo", - "description": "This channel allow tdrive's team to chat easily", - "channel_group": "tdrive", - "visibility": "public", - "is_default": true, - "archived": false - } -} - - -### Create a tab -# @name createTab -@channelId = {{createChannel.response.body.resource.id}} - -POST {{tabsURL}}/{{channelId}}/tabs -Content-Type: application/json -Authorization: Bearer {{authToken}} - -{ - "resource": { - // WARNING : special caracter in tab's name does not work - "name": "My tabs name", - "configuration": "JSON" - } -} - - -### Get a single tab -@getId = {{createTab.response.body.resource.id}} - -GET {{tabsURL}}/{{channelId}}/tabs/{{getId}} -Authorization: Bearer {{authToken}} - - -### Update a tab -@updateId = {{createTab.response.body.resource.id}} - -POST {{tabsURL}}/{{channelId}}/tabs/{{updateId}} -Content-Type: application/json -Authorization: Bearer {{authToken}} - -{ - "resource": { - "name": "My tab updated", - "configuration": "JSON" - } -} - - -### Delete a tab -@deleteId = {{createTab.response.body.resource.id}} - -DELETE {{tabsURL}}/{{channelId}}/tabs/{{deleteId}} -Authorization: Bearer {{authToken}} \ No newline at end of file diff --git a/tdrive/backend/node/test/unit/core/services/search/adapters/elasticsearch/search.test.ts b/tdrive/backend/node/test/unit/core/services/search/adapters/elasticsearch/search.test.ts new file mode 100644 index 00000000..b025e2c3 --- /dev/null +++ b/tdrive/backend/node/test/unit/core/services/search/adapters/elasticsearch/search.test.ts @@ -0,0 +1,158 @@ +import "reflect-metadata"; +import {describe, it} from "@jest/globals"; +import {buildSearchQuery} from "../../../../../../../src/core/platform/services/search/adapters/elasticsearch/search"; +import {DriveFile} from "../../../../../../../src/services/documents/entities/drive-file"; +import {FindFilter, FindOptions} from "../../../../../../../src/core/platform/services/search/api"; +import { + comparisonType +} from "../../../../../../../src/core/platform/services/database/services/orm/repository/repository"; + +describe("ES Query Builder", () => { + + describe("The intervals section of the search query", () => { + + it("'lte' section become a proper 'range' array in ES query", async () => { + //given:: + const filters: FindFilter = {}; + const options: FindOptions = { + $lte: [["last_modified", 10] as comparisonType] + }; + + //when + let esReq = buildSearchQuery(DriveFile, filters, options); + + //then + const expected: any = { + bool: { + boost: 1, + must: [ + { + range: { + last_modified: { + lte: 10 + } + } + } + ] + } + } + const query = esReq.esParams.body["query"]; + expect(query).not.toBeNull(); + expect(query).toEqual(expected); + }); + + it("'lte' section shouldn't exist if some params are missing", async () => { + //given:: + const filters: FindFilter = {}; + const options: FindOptions = { + $lte: [] + }; + + //when + let esReq = buildSearchQuery(DriveFile, filters, options); + + //then + const expected: any = { + bool: { + boost: 1 + } + } + const query = esReq.esParams.body["query"]; + expect(query).not.toBeNull(); + expect(query).toEqual(expected); + }); + + it("'gte' section become a proper 'range' array in ES query", async () => { + //given:: + const filters: FindFilter = {}; + const options: FindOptions = { + $gte: [["last_modified", 10] as comparisonType] + }; + + //when + let esReq = buildSearchQuery(DriveFile, filters, options); + + //then + const expected: any = { + bool: { + boost: 1, + must: [ + { + range: { + last_modified: { + gte: 10 + } + } + } + ] + } + } + const query = esReq.esParams.body["query"]; + expect(query).not.toBeNull(); + expect(query).toEqual(expected); + }); + + it("'gt' section become a proper 'range' array in ES query", async () => { + //given:: + const filters: FindFilter = {}; + const options: FindOptions = { + $gt: [["last_modified", 10] as comparisonType] + }; + + //when + let esReq = buildSearchQuery(DriveFile, filters, options); + + //then + const expected: any = { + bool: { + boost: 1, + must: [ + { + range: { + last_modified: { + gt: 10 + } + } + } + ] + } + } + const query = esReq.esParams.body["query"]; + expect(query).not.toBeNull(); + expect(query).toEqual(expected); + }); + + it("'lt' section become a proper 'range' array in ES query", async () => { + //given:: + const filters: FindFilter = {}; + const options: FindOptions = { + $lt: [["last_modified", 10] as comparisonType] + }; + + //when + let esReq = buildSearchQuery(DriveFile, filters, options); + + //then + const expected: any = { + bool: { + boost: 1, + must: [ + { + range: { + last_modified: { + lt: 10 + } + } + } + ] + } + } + const query = esReq.esParams.body["query"]; + expect(query).not.toBeNull(); + expect(query).toEqual(expected); + }); + + }); + + +}); \ No newline at end of file