diff --git a/backend/package.json b/backend/package.json index b3486017..42934eae 100644 --- a/backend/package.json +++ b/backend/package.json @@ -26,10 +26,10 @@ "test-all": "ava --timeout=5m --serial", "test-saas": "ava test/ava-tests/saas-tests/* ", "typeorm": "ts-node -r tsconfig-paths/register ../node_modules/.bin/typeorm", - "migration:generate": "yarn run typeorm migration:generate -d dist/shared/config/datasource.config.js", - "migration:create": "yarn run typeorm migration:create -d dist/shared/config/datasource.config.js", - "migration:run": "yarn run typeorm migration:run -d dist/shared/config/datasource.config.js", - "migration:revert": "npm run typeorm -- migration:revert -d dist/shared/config/datasource.config.js", + "migration:generate": "yarn run typeorm migration:generate -d dist/src/shared/config/datasource.config.js", + "migration:create": "yarn run typeorm migration:create -d dist/src/shared/config/datasource.config.js", + "migration:run": "yarn run typeorm migration:run -d dist/src/shared/config/datasource.config.js", + "migration:revert": "npm run typeorm -- migration:revert -d dist/src/shared/config/datasource.config.js", "knip": "knip" }, "dependencies": { diff --git a/backend/src/app.controller.ts b/backend/src/app.controller.ts index 001bdacf..2c169e22 100644 --- a/backend/src/app.controller.ts +++ b/backend/src/app.controller.ts @@ -16,7 +16,7 @@ export class AppController { @Get('/hello') async getHello(): Promise { - return this.getHelloUseCase.execute(undefined, InTransactionEnum.OFF); } } + diff --git a/backend/src/authorization/auth-with-api.middleware.ts b/backend/src/authorization/auth-with-api.middleware.ts index 20fb77a5..38307be3 100644 --- a/backend/src/authorization/auth-with-api.middleware.ts +++ b/backend/src/authorization/auth-with-api.middleware.ts @@ -28,7 +28,6 @@ export class AuthWithApiMiddleware implements NestMiddleware { ) {} async use(req: Request, res: Response, next: (err?: any, res?: any) => void): Promise { - console.info(`Auth with api middleware triggered ->: ${new Date().toISOString()}`); try { await this.authenticateRequest(req); next(); diff --git a/backend/src/authorization/auth.middleware.ts b/backend/src/authorization/auth.middleware.ts index a01189a2..6ef58f50 100644 --- a/backend/src/authorization/auth.middleware.ts +++ b/backend/src/authorization/auth.middleware.ts @@ -27,7 +27,6 @@ export class AuthMiddleware implements NestMiddleware { private readonly logOutRepository: Repository, ) {} async use(req: Request, res: Response, next: (err?: any, res?: any) => void): Promise { - console.log(`auth middleware triggered ->: ${new Date().toISOString()}`); let token: string; try { token = req.cookies[Constants.JWT_COOKIE_KEY_NAME]; diff --git a/backend/src/entities/agent/repository/custom-agent-repository-extension.ts b/backend/src/entities/agent/repository/custom-agent-repository-extension.ts index ef832a3f..971da925 100644 --- a/backend/src/entities/agent/repository/custom-agent-repository-extension.ts +++ b/backend/src/entities/agent/repository/custom-agent-repository-extension.ts @@ -65,6 +65,8 @@ export const customAgentRepositoryExtension = { return 'CASSANDRA-TEST-AGENT-TOKEN'; case ConnectionTypeTestEnum.agent_redis: return 'REDIS-TEST-AGENT-TOKEN'; + case ConnectionTypeTestEnum.agent_clickhouse: + return 'CLICKHOUSE-TEST-AGENT-TOKEN'; } }, }; diff --git a/backend/src/entities/connection/ssl-certificate/read-certificate.ts b/backend/src/entities/connection/ssl-certificate/read-certificate.ts index 09e94a0b..919f73e6 100644 --- a/backend/src/entities/connection/ssl-certificate/read-certificate.ts +++ b/backend/src/entities/connection/ssl-certificate/read-certificate.ts @@ -9,7 +9,7 @@ export async function readSslCertificate(): Promise { const fileName = 'global-bundle.pem'; return new Promise((resolve, reject) => { fs.readFile( - join(__dirname, '..', '..', '..', '..', 'files', 'certificates', fileName), + join(__dirname, '..', '..', '..', '..', '..', 'files', 'certificates', fileName), 'utf8', function (err, data) { if (err) { diff --git a/backend/src/entities/email/template-engine/nunjucks.module.ts b/backend/src/entities/email/template-engine/nunjucks.module.ts index 3a407066..f1179e75 100644 --- a/backend/src/entities/email/template-engine/nunjucks.module.ts +++ b/backend/src/entities/email/template-engine/nunjucks.module.ts @@ -3,19 +3,20 @@ import * as nunjucks from 'nunjucks'; import { BaseType } from '../../../common/data-injection.tokens.js'; import path from 'path'; import { fileURLToPath } from 'url'; -import { isTest } from '../../../helpers/app/is-test.js'; +import { existsSync } from 'node:fs'; +import assert from 'assert'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +const pathToTemplates = path.join(__dirname, '..', '..', '..', '..', '..', 'public', 'email-templates'); +assert(existsSync(pathToTemplates), `Email templates directory does not exist: ${__dirname} ${pathToTemplates}`); + @Global() @Module({ providers: [ { provide: BaseType.NUNJUCKS, useFactory: () => { - const pathToTemplates = isTest() - ? process.cwd() + '/public/email-templates' - : path.join(__dirname, '..', '..', '..', '..', 'public', 'email-templates'); const env = new nunjucks.Environment(new nunjucks.FileSystemLoader(pathToTemplates)); return env; }, diff --git a/backend/src/entities/logging/winston-logger.ts b/backend/src/entities/logging/winston-logger.ts index 526da133..4c3c914b 100644 --- a/backend/src/entities/logging/winston-logger.ts +++ b/backend/src/entities/logging/winston-logger.ts @@ -18,6 +18,10 @@ export class WinstonLogger implements LoggerService { this.logger.info(message, ...optionalParams); } + public info(message: any, ...optionalParams: any[]) { + this.logger.info(message, ...optionalParams); + } + public error(message: any, ...optionalParams: any[]) { this.logger.error(message, ...optionalParams); } diff --git a/backend/src/enums/connection-type.enum.ts b/backend/src/enums/connection-type.enum.ts index 10fa84e6..72462891 100644 --- a/backend/src/enums/connection-type.enum.ts +++ b/backend/src/enums/connection-type.enum.ts @@ -10,6 +10,7 @@ export enum ConnectionTypeTestEnum { elasticsearch = 'elasticsearch', cassandra = 'cassandra', redis = 'redis', + clickhouse = 'clickhouse', agent_postgres = 'agent_postgres', agent_mysql = 'agent_mysql', agent_oracledb = 'agent_oracledb', @@ -19,4 +20,5 @@ export enum ConnectionTypeTestEnum { agent_elasticsearch = 'agent_elasticsearch', agent_cassandra = 'agent_cassandra', agent_redis = 'agent_redis', + agent_clickhouse = 'agent_clickhouse', } diff --git a/backend/src/helpers/is-connection-entity-agent.ts b/backend/src/helpers/is-connection-entity-agent.ts index d629102d..ffc45e9f 100644 --- a/backend/src/helpers/is-connection-entity-agent.ts +++ b/backend/src/helpers/is-connection-entity-agent.ts @@ -13,6 +13,7 @@ export function isConnectionEntityAgent(connection: ConnectionEntity | CreateCon ConnectionTypesEnum.agent_mongodb, ConnectionTypesEnum.agent_cassandra, ConnectionTypesEnum.agent_redis, + ConnectionTypesEnum.agent_clickhouse, ]; return agentTypes.includes(connection.type as ConnectionTypesEnum); @@ -28,6 +29,7 @@ export function isConnectionTypeAgent(type: ConnectionTypesEnum | string): boole ConnectionTypeTestEnum.agent_mongodb, ConnectionTypeTestEnum.agent_cassandra, ConnectionTypesEnum.agent_redis, + ConnectionTypesEnum.agent_clickhouse, ]; return connectionTypes.includes(type as ConnectionTypeTestEnum); diff --git a/backend/src/middlewares/logging-middleware/app-logger-middlewate.ts b/backend/src/middlewares/logging-middleware/app-logger-middlewate.ts index 52d1a022..08fa2219 100644 --- a/backend/src/middlewares/logging-middleware/app-logger-middlewate.ts +++ b/backend/src/middlewares/logging-middleware/app-logger-middlewate.ts @@ -9,11 +9,11 @@ export class AppLoggerMiddleware implements NestMiddleware { use(request: Request, response: Response, next: NextFunction): void { const { ip, method, path: url, baseUrl } = request; const userAgent = request.get('user-agent') || ''; - this.logger.log(`START ${method} ${url}${baseUrl} - ${userAgent} ${ip}`, { context: 'HTTP' }); + this.logger.info(`START ${method} ${url}${baseUrl} - ${userAgent} ${ip}`, { context: 'HTTP' }); response.on('close', () => { const { statusCode } = response; const contentLength = response.get('content-length'); - this.logger.log( + this.logger.info( `method: ${method}, url: ${url}, statusCode: ${statusCode}, contentLength: ${contentLength}, userAgent: ${userAgent}, ip: ${ip}`, { context: 'HTTP' }, ); diff --git a/backend/test/ava-tests/saas-tests/company-info-e2e.test.ts b/backend/test/ava-tests/saas-tests/company-info-e2e.test.ts index 28da164b..f9cc02d4 100644 --- a/backend/test/ava-tests/saas-tests/company-info-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/company-info-e2e.test.ts @@ -7,6 +7,7 @@ import test from 'ava'; import { ValidationError } from 'class-validator'; import cookieParser from 'cookie-parser'; import fs from 'fs'; +import os from 'os'; import { nanoid } from 'nanoid'; import path, { join } from 'path'; import request from 'supertest'; @@ -1194,13 +1195,8 @@ test.serial(`${currentTest} should create and return found company logo after cr const foundCompanyInfoRO = JSON.parse(foundCompanyInfo.text); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - const testLogoPatch = join(process.cwd(), 'test', 'ava-tests', 'test-files', 'test_logo.png'); - const downloadedLogoPatch = join(__dirname, 'response-files', `${foundCompanyInfoRO.id}_test_logo.png`); + const downloadedLogoPatch = join(os.tmpdir(), `${foundCompanyInfoRO.id}_test_logo.png`); const createLogoResponse = await request(app.getHttpServer()) .post(`/company/logo/${foundCompanyInfoRO.id}`) @@ -1240,11 +1236,8 @@ test.serial(`${currentTest} should create and return found company logo after cr t.is(foundCompanyLogoForSimpleUserRO.logo.mimeType, 'image/png'); t.is(foundCompanyLogoForSimpleUserRO.logo.image.length > 0, true); - const downloadedLogoPatchForSimpleUser = join( - __dirname, - 'response-files', - `${foundCompanyInfoRO.id}_simple_user_logo.png`, - ); + const downloadedLogoPatchForSimpleUser = join(os.tmpdir(), `${foundCompanyInfoRO.id}_simple_user_logo.png`); + fs.writeFileSync(downloadedLogoPatchForSimpleUser, foundCompanyLogoForSimpleUserRO.logo.image); const isFileExistsForSimpleUser = fs.existsSync(downloadedLogoPatchForSimpleUser); t.is(isFileExistsForSimpleUser, true); @@ -1262,11 +1255,8 @@ test.serial(`${currentTest} should create and return found company logo after cr t.is(foundCompanyInfoWithLogoRO.logo.mimeType, 'image/png'); t.is(foundCompanyInfoWithLogoRO.logo.image.length > 0, true); - const downloadedLogoPatchWithLogo = join( - __dirname, - 'response-files', - `${foundCompanyInfoWithLogoRO.id}_admin_user_logo.png`, - ); + const downloadedLogoPatchWithLogo = join(os.tmpdir(), `${foundCompanyInfoWithLogoRO.id}_admin_user_logo.png`); + fs.writeFileSync(downloadedLogoPatchWithLogo, foundCompanyInfoWithLogoRO.logo.image); const isFileExistsWithLogo = fs.existsSync(downloadedLogoPatchWithLogo); t.is(isFileExistsWithLogo, true); @@ -1285,10 +1275,10 @@ test.serial(`${currentTest} should create and return found company logo after cr t.is(foundCompanyInfoWithLogoForSimpleUserRO.logo.image.length > 0, true); const downloadedLogoPatchForSimpleUserWithLogo = join( - __dirname, - 'response-files', + os.tmpdir(), `${foundCompanyInfoWithLogoForSimpleUserRO.id}_simple_user_logo.png`, ); + fs.writeFileSync(downloadedLogoPatchForSimpleUserWithLogo, foundCompanyInfoWithLogoForSimpleUserRO.logo.image); const isFileExistsForSimpleUserWithLogo = fs.existsSync(downloadedLogoPatchForSimpleUserWithLogo); t.is(isFileExistsForSimpleUserWithLogo, true); @@ -1316,13 +1306,8 @@ test.serial(`${currentTest} should create and return found company favicon after const foundCompanyInfoRO = JSON.parse(foundCompanyInfo.text); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - const testFaviconPatch = join(process.cwd(), 'test', 'ava-tests', 'test-files', 'test_logo.png'); - const downloadedFaviconPatch = join(__dirname, 'response-files', `${foundCompanyInfoRO.id}_test_favicon.png`); + const downloadedFaviconPatch = join(os.tmpdir(), `${foundCompanyInfoRO.id}_test_favicon.png`); const createFaviconResponse = await request(app.getHttpServer()) .post(`/company/favicon/${foundCompanyInfoRO.id}`) @@ -1362,11 +1347,8 @@ test.serial(`${currentTest} should create and return found company favicon after t.is(foundCompanyFaviconForSimpleUserRO.favicon.mimeType, 'image/png'); t.is(foundCompanyFaviconForSimpleUserRO.favicon.image.length > 0, true); - const downloadedFaviconPatchForSimpleUser = join( - __dirname, - 'response-files', - `${foundCompanyInfoRO.id}_simple_user_favicon.png`, - ); + const downloadedFaviconPatchForSimpleUser = join(os.tmpdir(), `${foundCompanyInfoRO.id}_simple_user_favicon.png`); + fs.writeFileSync(downloadedFaviconPatchForSimpleUser, foundCompanyFaviconForSimpleUserRO.favicon.image); const isFileExistsForSimpleUser = fs.existsSync(downloadedFaviconPatchForSimpleUser); t.is(isFileExistsForSimpleUser, true); @@ -1385,10 +1367,10 @@ test.serial(`${currentTest} should create and return found company favicon after t.is(foundCompanyInfoWithFaviconRO.favicon.image.length > 0, true); const downloadedFaviconPatchWithFavicon = join( - __dirname, - 'response-files', + os.tmpdir(), `${foundCompanyInfoWithFaviconRO.id}_admin_user_favicon.png`, ); + fs.writeFileSync(downloadedFaviconPatchWithFavicon, foundCompanyInfoWithFaviconRO.favicon.image); const isFileExistsWithFavicon = fs.existsSync(downloadedFaviconPatchWithFavicon); t.is(isFileExistsWithFavicon, true); @@ -1407,10 +1389,10 @@ test.serial(`${currentTest} should create and return found company favicon after t.is(foundCompanyInfoWithFaviconForSimpleUserRO.favicon.image.length > 0, true); const downloadedFaviconPatchForSimpleUserWithFavicon = join( - __dirname, - 'response-files', + os.tmpdir(), `${foundCompanyInfoWithFaviconForSimpleUserRO.id}_simple_user_favicon.png`, ); + fs.writeFileSync( downloadedFaviconPatchForSimpleUserWithFavicon, foundCompanyInfoWithFaviconForSimpleUserRO.favicon.image, @@ -1512,13 +1494,8 @@ test.serial(`${currentTest} should return found company white label properties f const foundCompanyInfoRO = JSON.parse(foundCompanyInfo.text); // crete company logo - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - const testLogoPatch = join(process.cwd(), 'test', 'ava-tests', 'test-files', 'test_logo.png'); - const downloadedLogoPatch = join(__dirname, 'response-files', `${foundCompanyInfoRO.id}_test_logo.png`); + const downloadedLogoPatch = join(os.tmpdir(), `${foundCompanyInfoRO.id}_test_logo.png`); const createLogoResponse = await request(app.getHttpServer()) .post(`/company/logo/${foundCompanyInfoRO.id}`) @@ -1532,7 +1509,7 @@ test.serial(`${currentTest} should return found company white label properties f // crete company favicon const testFaviconPatch = join(process.cwd(), 'test', 'ava-tests', 'test-files', 'test_logo.png'); - const downloadedFaviconPatch = join(__dirname, 'response-files', `${foundCompanyInfoRO.id}_test_favicon.png`); + const downloadedFaviconPatch = join(os.tmpdir(), `${foundCompanyInfoRO.id}_test_favicon.png`); const createFaviconResponse = await request(app.getHttpServer()) .post(`/company/favicon/${foundCompanyInfoRO.id}`) diff --git a/backend/test/ava-tests/saas-tests/table-cassandra-agent.e2e.test.ts b/backend/test/ava-tests/saas-tests/table-cassandra-agent.e2e.test.ts index 4c48d80a..4eef5672 100644 --- a/backend/test/ava-tests/saas-tests/table-cassandra-agent.e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-cassandra-agent.e2e.test.ts @@ -8,6 +8,7 @@ import test from 'ava'; import { ValidationError } from 'class-validator'; import cookieParser from 'cookie-parser'; import fs from 'fs'; +import os from 'os'; import path, { join } from 'path'; import request from 'supertest'; import { fileURLToPath } from 'url'; @@ -3178,12 +3179,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3293,12 +3290,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3358,12 +3351,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename @@ -3463,12 +3452,8 @@ test.serial(`${currentTest} should throw exception whe csv import is disabled`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-cassandra.e2e.test.ts b/backend/test/ava-tests/saas-tests/table-cassandra.e2e.test.ts index df7951ac..6d93cf12 100644 --- a/backend/test/ava-tests/saas-tests/table-cassandra.e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-cassandra.e2e.test.ts @@ -8,6 +8,7 @@ import test from 'ava'; import { ValidationError } from 'class-validator'; import cookieParser from 'cookie-parser'; import fs from 'fs'; +import os from 'os'; import path, { join } from 'path'; import request from 'supertest'; import { fileURLToPath } from 'url'; @@ -3461,12 +3462,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3584,12 +3581,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3653,12 +3646,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename @@ -3762,12 +3751,8 @@ test.serial(`${currentTest} should throw exception whe csv import is disabled`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-clickhouse-agent-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-clickhouse-agent-e2e.test.ts new file mode 100644 index 00000000..b15cb087 --- /dev/null +++ b/backend/test/ava-tests/saas-tests/table-clickhouse-agent-e2e.test.ts @@ -0,0 +1,3855 @@ +/* eslint-disable security/detect-non-literal-fs-filename */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable security/detect-object-injection */ +import { faker } from '@faker-js/faker'; +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import test from 'ava'; +import { ValidationError } from 'class-validator'; +import cookieParser from 'cookie-parser'; +import fs from 'fs'; +import os from 'os'; +import path, { join } from 'path'; +import request from 'supertest'; +import { fileURLToPath } from 'url'; +import { ApplicationModule } from '../../../src/app.module.js'; +import { LogOperationTypeEnum, QueryOrderingEnum } from '../../../src/enums/index.js'; +import { AllExceptionsFilter } from '../../../src/exceptions/all-exceptions.filter.js'; +import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; +import { Messages } from '../../../src/exceptions/text/messages.js'; +import { Cacher } from '../../../src/helpers/cache/cacher.js'; +import { Constants } from '../../../src/helpers/constants/constants.js'; +import { DatabaseModule } from '../../../src/shared/database/database.module.js'; +import { DatabaseService } from '../../../src/shared/database/database.service.js'; +import { MockFactory } from '../../mock.factory.js'; +import { createTestTable } from '../../utils/create-test-table.js'; +import { getTestData } from '../../utils/get-test-data.js'; +import { registerUserAndReturnUserInfo } from '../../utils/register-user-and-return-user-info.js'; +import { TestUtils } from '../../utils/test.utils.js'; +import { WinstonLogger } from '../../../src/entities/logging/winston-logger.js'; +import { getRandomTestTableName } from '../../utils/get-random-test-table-name.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const mockFactory = new MockFactory(); +let app: INestApplication; +let testUtils: TestUtils; +const testSearchedUserName = 'Vasia'; +const testTables: Array = []; +let currentTest: string; +let connectionToTestDB: any; +let firstUserToken: string; +let testTableName: string; +let testTableColumnName: string; +let testTableSecondColumnName: string; +let testEntitiesSeedsCount: number; +let insertedSearchedIds: Array<{ _id?: string; number: number }>; + +test.before(async () => { + const moduleFixture = await Test.createTestingModule({ + imports: [ApplicationModule, DatabaseModule], + providers: [DatabaseService, TestUtils], + }).compile(); + app = moduleFixture.createNestApplication() as any; + testUtils = moduleFixture.get(TestUtils); + + app.use(cookieParser()); + app.useGlobalFilters(new AllExceptionsFilter(app.get(WinstonLogger))); + app.useGlobalPipes( + new ValidationPipe({ + exceptionFactory(validationErrors: ValidationError[] = []) { + return new ValidationException(validationErrors); + }, + }), + ); + await app.init(); + app.getHttpServer().listen(0); +}); + +test.after(async () => { + try { + await Cacher.clearAllCache(); + await app.close(); + } catch (e) { + console.error('After tests error ' + e); + } +}); + +test.beforeEach(async () => { + const connectionToClickhouseDBInDocker = getTestData(mockFactory).clickhouseTestConnection; + const testTableCreationResult = await createTestTable(connectionToClickhouseDBInDocker); + testTableName = testTableCreationResult.testTableName; + testTableColumnName = testTableCreationResult.testTableColumnName; + testTableSecondColumnName = testTableCreationResult.testTableSecondColumnName; + testEntitiesSeedsCount = testTableCreationResult.testEntitiesSeedsCount; +}); + +currentTest = 'GET /connection/tables/:slug'; + +test.serial(`${currentTest} should return list of tables in connection`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const getTablesResponse = await request(app.getHttpServer()) + .get(`/connection/tables/${createConnectionRO.id}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getTablesRO = JSON.parse(getTablesResponse.text); + t.is(typeof getTablesRO, 'object'); + t.is(getTablesRO.length > 0, true); + + const testTableIndex = getTablesRO.findIndex((t) => t.table === testTableName); + + t.is(getTablesRO[testTableIndex].hasOwnProperty('table'), true); + t.is(getTablesRO[testTableIndex].hasOwnProperty('permissions'), true); + t.is(typeof getTablesRO[testTableIndex].permissions, 'object'); + t.is(Object.keys(getTablesRO[testTableIndex].permissions).length, 5); + t.is(getTablesRO[testTableIndex].table, testTableName); + t.is(getTablesRO[testTableIndex].permissions.visibility, true); + t.is(getTablesRO[testTableIndex].permissions.readonly, false); + t.is(getTablesRO[testTableIndex].permissions.add, true); + t.is(getTablesRO[testTableIndex].permissions.delete, true); + t.is(getTablesRO[testTableIndex].permissions.edit, true); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial(`${currentTest} should throw an error when connectionId not passed in request`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + createConnectionRO.id = ''; + const getTablesResponse = await request(app.getHttpServer()) + .get(`/connection/tables/${createConnectionRO.id}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTablesResponse.status, 404); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial(`${currentTest} should throw an error when connection id is incorrect`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + + t.is(createConnectionResponse.status, 201); + createConnectionRO.id = faker.string.uuid(); + const getTablesResponse = await request(app.getHttpServer()) + .get(`/connection/tables/${createConnectionRO.id}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTablesResponse.status, 400); + const { message } = JSON.parse(getTablesResponse.text); + t.is(message, Messages.CONNECTION_NOT_FOUND); + } catch (e) { + console.error(e); + throw e; + } +}); + +currentTest = 'GET /table/rows/:slug'; + +test.serial(`${currentTest} should return rows of selected table without search and without pagination`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.hasOwnProperty('large_dataset'), true); + t.is(getTableRowsRO.rows.length, Constants.DEFAULT_PAGINATION.perPage); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].hasOwnProperty('id'), true); + t.is(getTableRowsRO.rows[1].hasOwnProperty(testTableColumnName), true); + t.is(getTableRowsRO.rows[10].hasOwnProperty(testTableSecondColumnName), true); + t.is(getTableRowsRO.rows[15].hasOwnProperty('created_at'), true); + t.is(getTableRowsRO.rows[19].hasOwnProperty('updated_at'), true); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial(`${currentTest} should return rows of selected table with search and without pagination`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + ['id'], + undefined, + undefined, + 3, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(createTableSettingsResponse.status, 201); + + const searchedDescription = '5'; + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${searchedDescription}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 3); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + t.is(getTableRowsRO.rows[0].id, parseInt(searchedDescription)); + t.is(getTableRowsRO.rows[0].hasOwnProperty(testTableColumnName), true); + t.is(getTableRowsRO.rows[0].hasOwnProperty(testTableSecondColumnName), true); + t.is(getTableRowsRO.rows[0].hasOwnProperty('created_at'), true); + t.is(getTableRowsRO.rows[0].hasOwnProperty('updated_at'), true); + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial(`${currentTest} should return page of all rows with pagination page=1, perPage=2`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + ['id'], + undefined, + undefined, + 3, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=2`) + .set('Content-Type', 'application/json') + .set('Cookie', firstUserToken) + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].hasOwnProperty('id'), true); + t.is(getTableRowsRO.rows[1].hasOwnProperty(testTableColumnName), true); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + t.is(getTableRowsRO.primaryColumns[0].column_name, 'id'); + t.is(getTableRowsRO.primaryColumns[0].data_type, 'integer'); + + t.is(getTableRowsRO.pagination.total, 42); + t.is(getTableRowsRO.pagination.lastPage, 21); + t.is(getTableRowsRO.pagination.perPage, 2); + t.is(getTableRowsRO.pagination.currentPage, 1); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial(`${currentTest} should return page of all rows with pagination page=3, perPage=2`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + ['id'], + undefined, + undefined, + 3, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=3&perPage=2`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + console.log('🚀 ~ getTableRowsRO:', getTableRowsRO); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].hasOwnProperty('id'), true); + t.is(getTableRowsRO.rows[1].hasOwnProperty(testTableColumnName), true); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + t.is(getTableRowsRO.primaryColumns[0].column_name, 'id'); + t.is(getTableRowsRO.primaryColumns[0].data_type, 'integer'); + + t.is(getTableRowsRO.pagination.total, 42); + t.is(getTableRowsRO.pagination.lastPage, 21); + t.is(getTableRowsRO.pagination.perPage, 2); + t.is(getTableRowsRO.pagination.currentPage, 3); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial( + `${currentTest} with search, with pagination, without sorting +should return all found rows with pagination page=1 perPage=2`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + t.is(getTableRowsRO.primaryColumns[0].column_name, 'id'); + t.is(getTableRowsRO.primaryColumns[0].data_type, 'integer'); + + t.is(getTableRowsRO.pagination.total, 3); + t.is(getTableRowsRO.pagination.lastPage, 2); + t.is(getTableRowsRO.pagination.perPage, 2); + t.is(getTableRowsRO.pagination.currentPage, 1); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination, without sorting +should return all found rows with pagination page=1 perPage=3`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + t.is(getTableRowsRO.primaryColumns[0].column_name, 'id'); + t.is(getTableRowsRO.primaryColumns[0].data_type, 'integer'); + + t.is(getTableRowsRO.pagination.total, 3); + t.is(getTableRowsRO.pagination.lastPage, 2); + t.is(getTableRowsRO.pagination.perPage, 2); + t.is(getTableRowsRO.pagination.currentPage, 1); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} without search and without pagination and with sorting +should return all found rows with sorting ids by DESC`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 42, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 42); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].id, 42); + t.is(getTableRowsRO.rows[1].id, 41); + t.is(getTableRowsRO.rows[41].id, 1); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} without search and without pagination and with sorting +should return all found rows with sorting ids by ASC`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 42, + QueryOrderingEnum.ASC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 42); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].id, 1); + t.is(getTableRowsRO.rows[1].id, 2); + t.is(getTableRowsRO.rows[41].id, 42); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} without search and with pagination and with sorting +should return all found rows with sorting ports by DESC and with pagination page=1, perPage=2`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=2`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].id, 42); + t.is(getTableRowsRO.rows[1].id, 41); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} should return all found rows with sorting ports by ASC and with pagination page=1, perPage=2`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.ASC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=2`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].id, 1); + t.is(getTableRowsRO.rows[1].id, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} should return all found rows with sorting ports by DESC and with pagination page=2, perPage=3`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=2&perPage=3`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 3); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].id, 39); + t.is(getTableRowsRO.rows[1].id, 38); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination and with sorting +should return all found rows with search, pagination: page=1, perPage=2 and DESC sorting`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=2&search=${testSearchedUserName}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].id, 38); + t.is(getTableRowsRO.rows[1].id, 22); + t.is(getTableRowsRO.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination and with sorting +should return all found rows with search, pagination: page=2, perPage=2 and DESC sorting`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=2&perPage=2&search=${testSearchedUserName}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 1); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + t.is(getTableRowsRO.rows[0].id, 1); + t.is(getTableRowsRO.pagination.currentPage, 2); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination and with sorting +should return all found rows with search, pagination: page=1, perPage=2 and ASC sorting`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.ASC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=2&search=${testSearchedUserName}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + t.is(getTableRowsRO.rows[0].id, 1); + t.is(getTableRowsRO.rows[1].id, 22); + t.is(getTableRowsRO.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination and with sorting +should return all found rows with search, pagination: page=2, perPage=2 and ASC sorting`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.ASC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=2&perPage=2&search=${testSearchedUserName}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 1); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + t.is(getTableRowsRO.rows[0].id, 38); + t.is(getTableRowsRO.pagination.currentPage, 2); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination, with sorting and with filtering +should return all found rows with search, pagination: page=1, perPage=2 and DESC sorting and filtering`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldvalue = '45'; + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[0].id, 38); + t.is(getTableRowsRO.rows[1][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[1].id, 22); + + t.is(getTableRowsRO.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +// todo: rework for other tables after removing old endpoint +test.serial( + `${currentTest} with search, with pagination, with sorting and with filtering +should return all found rows with search, pagination: page=1, perPage=2 and DESC sorting and filtering in body`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldvalue = '45'; + + const filters = { + [fieldname]: { lt: fieldvalue }, + }; + + const getTableRowsResponse = await request(app.getHttpServer()) + .post( + `/table/rows/find/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2`, + ) + .send({ filters }) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[0].id, 38); + t.is(getTableRowsRO.rows[1][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[1].id, 22); + + t.is(getTableRowsRO.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination, with sorting and with filtering +should return all found rows with search, pagination: page=1, perPage=10 and DESC sorting and filtering'`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldvalue = '41'; + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=10&f_${fieldname}__lt=${fieldvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 3); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[0].id, 38); + t.is(getTableRowsRO.rows[1][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[1].id, 22); + t.is(getTableRowsRO.rows[2][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[2].id, 1); + + t.is(getTableRowsRO.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 10); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination, with sorting and with filtering +should return all found rows with search, pagination: page=2, perPage=2 and DESC sorting and filtering`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldvalue = '41'; + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=2&perPage=2&f_${fieldname}__lt=${fieldvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 1); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[0].id, 1); + + t.is(getTableRowsRO.pagination.currentPage, 2); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination, with sorting and with filtering +should return all found rows with search, pagination: page=1, perPage=2 and DESC sorting and with multi filtering`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldGtvalue = '25'; + const fieldLtvalue = '40'; + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}&f_${fieldname}__gt=${fieldGtvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 1); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + + t.is(getTableRowsRO.rows[0].id, 38); + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + + t.is(getTableRowsRO.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial(`${currentTest} should throw an exception when connection id is not passed in request`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldGtvalue = '25'; + const fieldLtvalue = '40'; + createConnectionRO.id = ''; + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}&f_${fieldname}__gt=${fieldGtvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 404); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial(`${currentTest} should throw an exception when connection id passed in request is incorrect`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldGtvalue = '25'; + const fieldLtvalue = '40'; + + createConnectionRO.id = faker.string.uuid(); + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}&f_${fieldname}__gt=${fieldGtvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 403); + + const { message } = JSON.parse(getTableRowsResponse.text); + + t.is(message, Messages.DONT_HAVE_PERMISSIONS); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial(`${currentTest} should throw an exception when table name passed in request is incorrect`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldGtvalue = '25'; + const fieldLtvalue = '40'; + + const fakeTableName = `${faker.lorem.words(1)}_${faker.string.uuid()}`; + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${fakeTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}&f_${fieldname}__gt=${fieldGtvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const { message } = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 400); + t.is(message, Messages.TABLE_NOT_FOUND); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial( + `${currentTest} should return an array with searched fields when filtered name passed in request is incorrect`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = faker.lorem.words(1); + const fieldGtvalue = '25'; + const fieldLtvalue = '40'; + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}&f_${fieldname}__gt=${fieldGtvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTablesRO = JSON.parse(getTableRowsResponse.text); + t.is(getTablesRO.rows.length, 2); + t.is(getTablesRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTablesRO.rows[1][testTableColumnName], testSearchedUserName); + t.is(getTablesRO.hasOwnProperty('primaryColumns'), true); + t.is(getTablesRO.hasOwnProperty('pagination'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +currentTest = 'GET /table/structure/:slug'; +test.serial(`${currentTest} should return table structure`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const getTableStructure = await request(app.getHttpServer()) + .get(`/table/structure/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableStructure.status, 200); + const getTableStructureRO = JSON.parse(getTableStructure.text); + + t.is(typeof getTableStructureRO, 'object'); + t.is(typeof getTableStructureRO.structure, 'object'); + t.is(getTableStructureRO.structure.length, 6); + + for (const element of getTableStructureRO.structure) { + t.is(element.hasOwnProperty('column_name'), true); + t.is(element.hasOwnProperty('column_default'), true); + t.is(element.hasOwnProperty('data_type'), true); + t.is(element.hasOwnProperty('isExcluded'), true); + t.is(element.hasOwnProperty('isSearched'), true); + } + + t.is(getTableStructureRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableStructureRO.hasOwnProperty('foreignKeys'), true); + + for (const element of getTableStructureRO.primaryColumns) { + t.is(element.hasOwnProperty('column_name'), true); + t.is(element.hasOwnProperty('data_type'), true); + } + + for (const element of getTableStructureRO.foreignKeys) { + t.is(element.hasOwnProperty('referenced_column_name'), true); + t.is(element.hasOwnProperty('referenced_table_name'), true); + t.is(element.hasOwnProperty('constraint_name'), true); + t.is(element.hasOwnProperty('column_name'), true); + } +}); + +test.serial(`${currentTest} should throw an exception whe connection id not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + createConnectionRO.id = ''; + const getTableStructure = await request(app.getHttpServer()) + .get(`/table/structure/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableStructure.status, 404); +}); + +test.serial(`${currentTest} should throw an exception whe connection id passed in request id incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + createConnectionRO.id = faker.string.uuid(); + const getTableStructure = await request(app.getHttpServer()) + .get(`/table/structure/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableStructure.status, 403); + const { message } = JSON.parse(getTableStructure.text); + t.is(message, Messages.DONT_HAVE_PERMISSIONS); +}); + +test.serial(`${currentTest}should throw an exception when tableName not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const tableName = ''; + const getTableStructure = await request(app.getHttpServer()) + .get(`/table/structure/${createConnectionRO.id}?tableName=${tableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableStructure.status, 400); + const { message } = JSON.parse(getTableStructure.text); + t.is(message, Messages.TABLE_NAME_MISSING); +}); + +test.serial(`${currentTest} should throw an exception when tableName passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const tableName = faker.lorem.words(1); + const getTableStructure = await request(app.getHttpServer()) + .get(`/table/structure/${createConnectionRO.id}?tableName=${tableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableStructure.status, 400); + + const responseObject = JSON.parse(getTableStructure.text); + t.is(responseObject.message, Messages.TABLE_NOT_FOUND); +}); + +currentTest = 'POST /table/row/:slug'; + +test.serial(`${currentTest} should add row in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + id: 43, + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const addRowInTableResponse = await request(app.getHttpServer()) + .post(`/table/row/${createConnectionRO.id}?tableName=${testTableName}`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const addRowInTableRO = JSON.parse(addRowInTableResponse.text); + t.is(addRowInTableResponse.status, 201); + + t.is(addRowInTableRO.hasOwnProperty('row'), true); + t.is(addRowInTableRO.hasOwnProperty('structure'), true); + t.is(addRowInTableRO.hasOwnProperty('foreignKeys'), true); + t.is(addRowInTableRO.hasOwnProperty('primaryColumns'), true); + t.is(addRowInTableRO.hasOwnProperty('readonly_fields'), true); + t.is(addRowInTableRO.row[testTableColumnName], row[testTableColumnName]); + t.is(addRowInTableRO.row[testTableSecondColumnName], row[testTableSecondColumnName]); + + //checking that the line was added + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 43); + t.is(rows[42][testTableColumnName], row[testTableColumnName]); + t.is(rows[42][testTableSecondColumnName], row[testTableSecondColumnName]); + t.is(rows[42].id, rows[41].id + 1); + + // check that rows adding was logged + + const getLogsResponse = await request(app.getHttpServer()) + .get(`/logs/${createConnectionRO.id}?page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(getLogsResponse.status, 200); + const getLogsRO = JSON.parse(getLogsResponse.text); + console.log('🚀 ~ test.serial ~ getLogsRO:', getLogsRO.logs[1].affected_primary_key); + t.is(getLogsRO.hasOwnProperty('logs'), true); + t.is(getLogsRO.hasOwnProperty('pagination'), true); + t.is(getLogsRO.logs.length > 0, true); + const addRowLogIndex = getLogsRO.logs.findIndex((log) => log.operationType === 'addRow'); + t.is(getLogsRO.logs[addRowLogIndex].hasOwnProperty('affected_primary_key'), true); + t.is(typeof getLogsRO.logs[addRowLogIndex].affected_primary_key, 'object'); + t.is(getLogsRO.logs[addRowLogIndex].affected_primary_key.hasOwnProperty('id'), true); + t.is(getLogsRO.logs[addRowLogIndex].affected_primary_key.id, 43); +}); + +test.serial(`${currentTest} should throw an exception when connection id is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + id: 999, + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + const fakeConnectionId = ''; + const addRowInTableResponse = await request(app.getHttpServer()) + .post(`/table/row/${fakeConnectionId}?tableName=${testTableName}`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(addRowInTableResponse.status, 404); + + //checking that the line wasn't added + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); +}); + +test.serial(`${currentTest} should throw an exception when table name is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + id: 999, + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const addRowInTableResponse = await request(app.getHttpServer()) + .post(`/table/row/${createConnectionRO.id}?tableName=`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(addRowInTableResponse.status, 400); + const { message } = JSON.parse(addRowInTableResponse.text); + + t.is(message, Messages.TABLE_NAME_MISSING); + + //checking that the line wasn't added + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); +}); + +test.serial(`${currentTest} should throw an exception when row is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const addRowInTableResponse = await request(app.getHttpServer()) + .post(`/table/row/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(addRowInTableResponse.status, 400); + const { message } = JSON.parse(addRowInTableResponse.text); + + t.is(message, Messages.PARAMETER_MISSING); + + //checking that the line wasn't added + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); +}); + +test.serial(`${currentTest} should throw an exception when table name passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + id: 999, + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const fakeTableName = `${faker.lorem.words(1)}_${faker.number.int({ min: 1, max: 10000 })}`; + const addRowInTableResponse = await request(app.getHttpServer()) + .post(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const { message } = JSON.parse(addRowInTableResponse.text); + console.log('🚀 ~ test.serial ~ message:', message); + t.is(addRowInTableResponse.status, 400); + t.is(message, Messages.TABLE_NOT_FOUND); + + //checking that the line wasn't added + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); +}); + +currentTest = 'PUT /table/row/:slug'; + +test.serial(`${currentTest} should update row in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 200); + const updateRowInTableRO = JSON.parse(updateRowInTableResponse.text); + + t.is(updateRowInTableRO.hasOwnProperty('row'), true); + t.is(updateRowInTableRO.hasOwnProperty('structure'), true); + t.is(updateRowInTableRO.hasOwnProperty('foreignKeys'), true); + t.is(updateRowInTableRO.hasOwnProperty('primaryColumns'), true); + t.is(updateRowInTableRO.hasOwnProperty('readonly_fields'), true); + // t.is(updateRowInTableRO.row[testTableColumnName], row[testTableColumnName]); + // t.is(updateRowInTableRO.row[testTableSecondColumnName], row[testTableSecondColumnName]); + + //checking that the line was updated + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + const updateRowIndex = rows.map((row) => row.id).indexOf(1); + t.is(rows.length, 42); + t.is(rows[updateRowIndex][testTableColumnName], row[testTableColumnName]); + t.is(rows[updateRowIndex][testTableSecondColumnName], row[testTableSecondColumnName]); +}); + +test.serial(`${currentTest} should throw an exception when connection id not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + createConnectionRO.id = ''; + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 404); +}); + +test.serial(`${currentTest} should throw an exception when connection id passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + createConnectionRO.id = faker.string.uuid(); + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 403); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.DONT_HAVE_PERMISSIONS); +}); + +test.serial(`${currentTest} should throw an exception when tableName not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + createConnectionRO.id = faker.string.uuid(); + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=&id=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 400); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.TABLE_NAME_MISSING); +}); + +test.serial(`${currentTest} should throw an exception when tableName passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const fakeTableName = faker.string.uuid(); + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}&id=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 400); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.TABLE_NOT_FOUND); +}); + +test.serial(`${currentTest} should throw an exception when primary key not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 400); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.PRIMARY_KEY_INVALID); +}); + +test.serial( + `${currentTest} should throw an exception when primary key passed in request has incorrect field name`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&IncorrectField=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 400); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.PRIMARY_KEY_INVALID); + }, +); + +test.serial( + `${currentTest} should throw an exception when primary key passed in request has incorrect field value`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=100000000`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 500); + const { message } = JSON.parse(updateRowInTableResponse.text); + console.log('🚀 ~ message:', message); + t.is(message, 'Failed to update row in table. No data returned from agent'); + }, +); + +currentTest = 'PUT /table/rows/update/:connectionId'; + +test.serial(`${currentTest} should update multiple rows and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const requestData = { + primaryKeys: [{ id: 1 }, { id: 2 }], + newValues: { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }, + }; + + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/rows/update/${createConnectionRO.id}?tableName=${testTableName}`) + .send(JSON.stringify(requestData)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const updateRowInTableRO = JSON.parse(updateRowInTableResponse.text); + + t.is(updateRowInTableResponse.status, 200); + t.is(updateRowInTableRO.success, true); + + // check that the rows were updated + const firstRowResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=1`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const firstRow = JSON.parse(firstRowResponse.text); + t.is(firstRowResponse.status, 200); + t.is(firstRow.row[testTableColumnName], fakeName); + t.is(firstRow.row[testTableSecondColumnName], fakeMail); + + const secondRowResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=2`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const secondRow = JSON.parse(secondRowResponse.text); + t.is(secondRowResponse.status, 200); + t.is(secondRow.row[testTableColumnName], fakeName); + t.is(secondRow.row[testTableSecondColumnName], fakeMail); +}); + +currentTest = 'DELETE /table/row/:slug'; + +test.serial(`${currentTest} should delete row in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForDeletion = 1; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForDeletion}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 200); + const deleteRowInTableRO = JSON.parse(deleteRowInTableResponse.text); + + t.is(deleteRowInTableRO.hasOwnProperty('row'), true); + + //checking that the line was deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 41); + const deletedRowIndex = rows.map((row: Record) => row.id).indexOf(idForDeletion); + t.is(deletedRowIndex < 0, true); +}); + +test.serial(`${currentTest} should throw an exception when connection id not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForDeletion = 1; + const connectionId = ''; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${connectionId}?tableName=${testTableName}&id=${idForDeletion}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 404); + + //checking that the line wasn't deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); + const deletedRowIndex = rows.map((row: Record) => row.id).indexOf(idForDeletion); + t.is(deletedRowIndex < 0, false); +}); + +test.serial(`${currentTest} should throw an exception when connection id passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForDeletion = 1; + const connectionId = faker.string.uuid(); + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${connectionId}?tableName=${testTableName}&id=${idForDeletion}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 403); + const { message } = JSON.parse(deleteRowInTableResponse.text); + t.is(message, Messages.DONT_HAVE_PERMISSIONS); + + //checking that the line wasn't deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); + const deletedRowIndex = rows.map((row: Record) => row.id).indexOf(idForDeletion); + t.is(deletedRowIndex < 0, false); +}); + +test.serial(`${currentTest} should throw an exception when tableName not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForDeletion = 1; + const fakeTableName = ''; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}&id=${idForDeletion}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 400); + const { message } = JSON.parse(deleteRowInTableResponse.text); + t.is(message, Messages.TABLE_NAME_MISSING); + + //checking that the line wasn't deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); + const deletedRowIndex = rows.map((row: Record) => row.id).indexOf(idForDeletion); + t.is(deletedRowIndex < 0, false); +}); + +test.serial(`${currentTest} should throw an exception when tableName passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForDeletion = 1; + const fakeTableName = `${faker.lorem.words(1)}_${faker.string.uuid()}`; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}&id=${idForDeletion}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 400); + const { message } = JSON.parse(deleteRowInTableResponse.text); + t.is(message, Messages.TABLE_NOT_FOUND); + + //checking that the line wasn't deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); + const deletedRowIndex = rows.map((row: Record) => row.id).indexOf(idForDeletion); + t.is(deletedRowIndex < 0, false); +}); + +test.serial(`${currentTest} should throw an exception when primary key not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForDeletion = 1; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 400); + const { message } = JSON.parse(deleteRowInTableResponse.text); + t.is(message, Messages.PRIMARY_KEY_INVALID); + + //checking that the line wasn't deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); + const deletedRowIndex = rows.map((row: Record) => row.id).indexOf(idForDeletion); + t.is(deletedRowIndex < 0, false); +}); + +test.serial( + `${currentTest} should throw an exception when primary key passed in request has incorrect field name`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForDeletion = 1; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&fakePKey=1`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 400); + const { message } = JSON.parse(deleteRowInTableResponse.text); + t.is(message, Messages.PRIMARY_KEY_INVALID); + + //checking that the line wasn't deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); + const deletedRowIndex = rows.map((row: Record) => row.id).indexOf(idForDeletion); + t.is(deletedRowIndex < 0, false); + }, +); + +test.serial( + `${currentTest} should throw an exception when primary key passed in request has incorrect field value`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=100000`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const deleteRowInTableRO = JSON.parse(deleteRowInTableResponse.text); + t.is(deleteRowInTableResponse.status, 500); + }, +); + +currentTest = 'GET /table/row/:slug'; + +test.serial(`${currentTest} found row`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 200); + const foundRowInTableRO = JSON.parse(foundRowInTableResponse.text); + t.is(foundRowInTableRO.hasOwnProperty('row'), true); + t.is(foundRowInTableRO.hasOwnProperty('structure'), true); + t.is(foundRowInTableRO.hasOwnProperty('foreignKeys'), true); + t.is(foundRowInTableRO.hasOwnProperty('primaryColumns'), true); + t.is(foundRowInTableRO.hasOwnProperty('readonly_fields'), true); + t.is(typeof foundRowInTableRO.row, 'object'); + t.is(typeof foundRowInTableRO.structure, 'object'); + t.is(typeof foundRowInTableRO.primaryColumns, 'object'); + t.is(typeof foundRowInTableRO.readonly_fields, 'object'); + t.is(typeof foundRowInTableRO.foreignKeys, 'object'); + t.is(foundRowInTableRO.row.id, 1); + t.is(foundRowInTableRO.row[testTableColumnName], testSearchedUserName); + t.is(Object.keys(foundRowInTableRO.row).length, 6); +}); + +test.serial(`${currentTest} should throw an exception, when connection id is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const idForSearch = 1; + createConnectionRO.id = ''; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 404); +}); + +test.serial( + `${currentTest} should throw an exception, when connection id passed in request is incorrect`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1; + createConnectionRO.id = faker.string.uuid(); + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 403); + const { message } = JSON.parse(foundRowInTableResponse.text); + t.is(message, Messages.DONT_HAVE_PERMISSIONS); + }, +); + +test.serial(`${currentTest} should throw an exception, when tableName in not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1; + const fakeTableName = ''; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}&id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 400); + const { message } = JSON.parse(foundRowInTableResponse.text); + t.is(message, Messages.TABLE_NAME_MISSING); +}); + +test.serial(`${currentTest} should throw an exception, when tableName passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1; + const fakeTableName = `${faker.lorem.words(1)}_${faker.string.uuid()}`; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}&id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const FoundRowRO = JSON.parse(foundRowInTableResponse.text); + console.log('🚀 ~ test.serial ~ FoundRowRO:', FoundRowRO); + t.is(foundRowInTableResponse.status, 400); + t.is(FoundRowRO.message, Messages.TABLE_NOT_FOUND); +}); + +test.serial(`${currentTest} should throw an exception, when primary key is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 400); + const { message } = JSON.parse(foundRowInTableResponse.text); + t.is(message, Messages.PRIMARY_KEY_INVALID); +}); + +test.serial( + `${currentTest} should throw an exception, when primary key passed in request has incorrect name`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&fakeKeyName=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 400); + const { message } = JSON.parse(foundRowInTableResponse.text); + t.is(message, Messages.PRIMARY_KEY_INVALID); + }, +); + +test.serial( + `${currentTest} should throw an exception, when primary key passed in request has incorrect value`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1000000; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 500); + const { message } = JSON.parse(foundRowInTableResponse.text); + t.is(message, 'Failed to get row by primary key. No data returned from agent'); + }, +); + +currentTest = 'PUT /table/rows/delete/:slug'; + +test.serial(`${currentTest} should delete row in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const primaryKeysForDeletion: Array> = [ + { + id: 1, + }, + { + id: 10, + }, + { + id: 32, + }, + ]; + const deleteRowsInTableResponse = await request(app.getHttpServer()) + .put(`/table/rows/delete/${createConnectionRO.id}?tableName=${testTableName}`) + .send(primaryKeysForDeletion) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowsInTableResponse.status, 200); + const deleteRowInTableRO = JSON.parse(deleteRowsInTableResponse.text); + + //checking that the line was deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + // check that lines was deleted + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, testEntitiesSeedsCount - primaryKeysForDeletion.length); + + for (const key of primaryKeysForDeletion) { + t.is( + rows.findIndex((row) => row.id === key.id), + -1, + ); + } + + // check that table deletion was logged + const tableLogsResponse = await request(app.getHttpServer()) + .get(`/logs/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(tableLogsResponse.status, 200); + + const tableLogsRO = JSON.parse(tableLogsResponse.text); + t.is(tableLogsRO.logs.length, primaryKeysForDeletion.length + 1); + const onlyDeleteLogs = tableLogsRO.logs.filter((log) => log.operationType === LogOperationTypeEnum.deleteRow); + for (const key of primaryKeysForDeletion) { + t.is(onlyDeleteLogs.findIndex((log) => log.received_data.id === key.id) >= 0, true); + } +}); + +currentTest = 'DELETE /table/rows/:slug'; + +test.serial(`${currentTest} should delete rows in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const primaryKeysForDeletion = [ + { + id: 1, + }, + { + id: 10, + }, + { + id: 32, + }, + ]; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/rows/delete/${createConnectionRO.id}?tableName=${testTableName}`) + .send(primaryKeysForDeletion) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 200); + const deleteRowInTableRO = JSON.parse(deleteRowInTableResponse.text); + + //checking that the line was deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 200); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 39); + for (const primaryKey of primaryKeysForDeletion) { + t.is( + rows.findIndex((row) => row.id === primaryKey.id), + -1, + ); + } + + // check that deletion of rows was logged + + const getTableLogs = await request(app.getHttpServer()) + .get(`/logs/${createConnectionRO.id}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const getRowInTableRO = JSON.parse(getTableLogs.text); + const deleteRowsLogs = getRowInTableRO.logs.filter((log) => log.operationType === LogOperationTypeEnum.deleteRow); + t.is(deleteRowsLogs.length, primaryKeysForDeletion.length); +}); + +currentTest = 'GET table/csv/:slug'; + +test.serial(`${currentTest} should return csv file with table data`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const getTableCsvResponse = await request(app.getHttpServer()) + .post(`/table/csv/export/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'text/csv') + .set('Accept', 'text/csv'); + + if (getTableCsvResponse.status !== 201) { + const getTableCsvResponseRO = JSON.parse(getTableCsvResponse.text); + console.log(getTableCsvResponseRO); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(os.tmpdir(), fileName); + + // eslint-disable-next-line security/detect-non-literal-fs-filename + fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); + + // eslint-disable-next-line security/detect-non-literal-fs-filename + const isFileExists = fs.existsSync(downloadedFilePatch); + t.is(isFileExists, true); +}); + +test.serial(`${currentTest} should throw exception when csv export is disabled in table settings`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + false, + false, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableCsvResponse = await request(app.getHttpServer()) + .post(`/table/csv/export/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'text/csv') + .set('Accept', 'text/csv'); + + t.is(getTableCsvResponse.status, 400); + const { message } = JSON.parse(getTableCsvResponse.text); + t.is(message, Messages.CSV_EXPORT_DISABLED); +}); + +test.serial( + `${currentTest} should return csv file with table data with search, with pagination, with sorting, +with search and pagination: page=1, perPage=2 and DESC sorting`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableCsvResponse = await request(app.getHttpServer()) + .post( + `/table/csv/export/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'text/csv') + .set('Accept', 'text/csv'); + + if (getTableCsvResponse.status !== 201) { + console.log(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(os.tmpdir(), fileName); + + // eslint-disable-next-line security/detect-non-literal-fs-filename + fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); + + // eslint-disable-next-line security/detect-non-literal-fs-filename + const isFileExists = fs.existsSync(downloadedFilePatch); + t.is(isFileExists, true); + }, +); + +currentTest = 'POST /table/csv/import/:slug'; +test.serial(`${currentTest} should import csv file with table data`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableCsvResponse = await request(app.getHttpServer()) + .post( + `/table/csv/export/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'text/csv') + .set('Accept', 'text/csv'); + + if (getTableCsvResponse.status !== 201) { + console.log(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(os.tmpdir(), fileName); + + // eslint-disable-next-line security/detect-non-literal-fs-filename + fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); + // eslint-disable-next-line security/detect-non-literal-fs-filename + const isFileExists = fs.existsSync(downloadedFilePatch); + t.is(isFileExists, true); + + function changeIdFieldsValuesInCsvFile(filePatch: string) { + const fileContent = fs.readFileSync(filePatch).toString(); + const rows = fileContent.split('\n'); + const newRows = rows.map((row, index) => { + if (index === 0) { + return row; + } + const columns = row.split(','); + if (columns.length === 1) { + return row; + } + columns[0] = `5${index}`; + return columns.join(','); + }); + return newRows.join('\n'); + } + + const newFileContent = changeIdFieldsValuesInCsvFile(downloadedFilePatch); + fs.writeFileSync(downloadedFilePatch, newFileContent); + + const importCsvResponse = await request(app.getHttpServer()) + .post(`/table/csv/import/${createConnectionRO.id}?tableName=${testTableName}`) + .attach('file', downloadedFilePatch) + .set('Cookie', firstUserToken) + .set('Accept', 'application/json'); + + t.is(importCsvResponse.status, 201); + + //checking that the lines was added + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 200); + const addedRows = getTableRowsRO.rows.filter((row: Record) => row.id > testEntitiesSeedsCount); + t.is(addedRows.length, 2); +}); + +test.serial(`${currentTest} should throw exception whe csv import is disabled`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseAgentTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + false, + true, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableCsvResponse = await request(app.getHttpServer()) + .post( + `/table/csv/export/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'text/csv') + .set('Accept', 'text/csv'); + + if (getTableCsvResponse.status !== 201) { + console.log(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(os.tmpdir(), fileName); + + // eslint-disable-next-line security/detect-non-literal-fs-filename + fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); + // eslint-disable-next-line security/detect-non-literal-fs-filename + const isFileExists = fs.existsSync(downloadedFilePatch); + t.is(isFileExists, true); + + function changeIdFieldsValuesInCsvFile(filePatch: string) { + const fileContent = fs.readFileSync(filePatch).toString(); + const rows = fileContent.split('\n'); + const newRows = rows.map((row, index) => { + if (index === 0) { + return row; + } + const columns = row.split(','); + if (columns.length === 1) { + return row; + } + columns[0] = `5${index}`; + return columns.join(','); + }); + return newRows.join('\n'); + } + + const newFileContent = changeIdFieldsValuesInCsvFile(downloadedFilePatch); + fs.writeFileSync(downloadedFilePatch, newFileContent); + + const importCsvResponse = await request(app.getHttpServer()) + .post(`/table/csv/import/${createConnectionRO.id}?tableName=${testTableName}`) + .attach('file', downloadedFilePatch) + .set('Cookie', firstUserToken) + .set('Accept', 'application/json'); + + t.is(importCsvResponse.status, 400); + + const { message } = JSON.parse(importCsvResponse.text); + t.is(message, Messages.CSV_IMPORT_DISABLED); +}); diff --git a/backend/test/ava-tests/saas-tests/table-clickhouse-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-clickhouse-e2e.test.ts new file mode 100644 index 00000000..6d79a818 --- /dev/null +++ b/backend/test/ava-tests/saas-tests/table-clickhouse-e2e.test.ts @@ -0,0 +1,3991 @@ +/* eslint-disable security/detect-non-literal-fs-filename */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable security/detect-object-injection */ +import { faker } from '@faker-js/faker'; +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import test from 'ava'; +import { ValidationError } from 'class-validator'; +import cookieParser from 'cookie-parser'; +import fs from 'fs'; +import os from 'os'; +import path, { join } from 'path'; +import request from 'supertest'; +import { fileURLToPath } from 'url'; +import { ApplicationModule } from '../../../src/app.module.js'; +import { LogOperationTypeEnum, QueryOrderingEnum } from '../../../src/enums/index.js'; +import { AllExceptionsFilter } from '../../../src/exceptions/all-exceptions.filter.js'; +import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; +import { Messages } from '../../../src/exceptions/text/messages.js'; +import { Cacher } from '../../../src/helpers/cache/cacher.js'; +import { Constants } from '../../../src/helpers/constants/constants.js'; +import { DatabaseModule } from '../../../src/shared/database/database.module.js'; +import { DatabaseService } from '../../../src/shared/database/database.service.js'; +import { MockFactory } from '../../mock.factory.js'; +import { createTestTable } from '../../utils/create-test-table.js'; +import { getTestData } from '../../utils/get-test-data.js'; +import { registerUserAndReturnUserInfo } from '../../utils/register-user-and-return-user-info.js'; +import { TestUtils } from '../../utils/test.utils.js'; +import { WinstonLogger } from '../../../src/entities/logging/winston-logger.js'; +import { getRandomTestTableName } from '../../utils/get-random-test-table-name.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const mockFactory = new MockFactory(); +let app: INestApplication; +let testUtils: TestUtils; +const testSearchedUserName = 'Vasia'; +const testTables: Array = []; +let currentTest; + +test.before(async () => { + const moduleFixture = await Test.createTestingModule({ + imports: [ApplicationModule, DatabaseModule], + providers: [DatabaseService, TestUtils], + }).compile(); + app = moduleFixture.createNestApplication() as any; + testUtils = moduleFixture.get(TestUtils); + + app.use(cookieParser()); + app.useGlobalFilters(new AllExceptionsFilter(app.get(WinstonLogger))); + app.useGlobalPipes( + new ValidationPipe({ + exceptionFactory(validationErrors: ValidationError[] = []) { + return new ValidationException(validationErrors); + }, + }), + ); + await app.init(); + app.getHttpServer().listen(0); +}); + +test.after(async () => { + try { + await Cacher.clearAllCache(); + await app.close(); + } catch (e) { + console.error('After tests error ' + e); + } +}); + +currentTest = 'GET /connection/tables/:slug'; + +test.serial(`${currentTest} should return list of tables in connection`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const getTablesResponse = await request(app.getHttpServer()) + .get(`/connection/tables/${createConnectionRO.id}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getTablesRO = JSON.parse(getTablesResponse.text); + t.is(typeof getTablesRO, 'object'); + t.is(getTablesRO.length > 0, true); + + const testTableIndex = getTablesRO.findIndex((t) => t.table === testTableName); + + t.is(getTablesRO[testTableIndex].hasOwnProperty('table'), true); + t.is(getTablesRO[testTableIndex].hasOwnProperty('permissions'), true); + t.is(typeof getTablesRO[testTableIndex].permissions, 'object'); + t.is(Object.keys(getTablesRO[testTableIndex].permissions).length, 5); + t.is(getTablesRO[testTableIndex].table, testTableName); + t.is(getTablesRO[testTableIndex].permissions.visibility, true); + t.is(getTablesRO[testTableIndex].permissions.readonly, false); + t.is(getTablesRO[testTableIndex].permissions.add, true); + t.is(getTablesRO[testTableIndex].permissions.delete, true); + t.is(getTablesRO[testTableIndex].permissions.edit, true); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial(`${currentTest} should throw an error when connectionId not passed in request`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + createConnectionRO.id = ''; + const getTablesResponse = await request(app.getHttpServer()) + .get(`/connection/tables/${createConnectionRO.id}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTablesResponse.status, 404); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial(`${currentTest} should throw an error when connection id is incorrect`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + + t.is(createConnectionResponse.status, 201); + createConnectionRO.id = faker.string.uuid(); + const getTablesResponse = await request(app.getHttpServer()) + .get(`/connection/tables/${createConnectionRO.id}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTablesResponse.status, 400); + const { message } = JSON.parse(getTablesResponse.text); + t.is(message, Messages.CONNECTION_NOT_FOUND); + } catch (e) { + console.error(e); + throw e; + } +}); + +currentTest = 'GET /table/rows/:slug'; + +test.serial(`${currentTest} should return rows of selected table without search and without pagination`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testTableSecondColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.hasOwnProperty('large_dataset'), true); + t.is(getTableRowsRO.rows.length, Constants.DEFAULT_PAGINATION.perPage); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].hasOwnProperty('id'), true); + t.is(getTableRowsRO.rows[1].hasOwnProperty(testTableColumnName), true); + t.is(getTableRowsRO.rows[10].hasOwnProperty(testTableSecondColumnName), true); + t.is(getTableRowsRO.rows[15].hasOwnProperty('created_at'), true); + t.is(getTableRowsRO.rows[19].hasOwnProperty('updated_at'), true); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial(`${currentTest} should return rows of selected table with search and without pagination`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testTableSecondColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + ['id'], + undefined, + undefined, + 3, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(createTableSettingsResponse.status, 201); + + const searchedDescription = '5'; + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${searchedDescription}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 3); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + t.is(getTableRowsRO.rows[0].id, parseInt(searchedDescription)); + t.is(getTableRowsRO.rows[0].hasOwnProperty(testTableColumnName), true); + t.is(getTableRowsRO.rows[0].hasOwnProperty(testTableSecondColumnName), true); + t.is(getTableRowsRO.rows[0].hasOwnProperty('created_at'), true); + t.is(getTableRowsRO.rows[0].hasOwnProperty('updated_at'), true); + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial(`${currentTest} should return page of all rows with pagination page=1, perPage=2`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + ['id'], + undefined, + undefined, + 3, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=2`) + .set('Content-Type', 'application/json') + .set('Cookie', firstUserToken) + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].hasOwnProperty('id'), true); + t.is(getTableRowsRO.rows[1].hasOwnProperty(testTableColumnName), true); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + t.is(getTableRowsRO.primaryColumns[0].column_name, 'id'); + t.is(getTableRowsRO.primaryColumns[0].data_type, 'integer'); + + t.is(getTableRowsRO.pagination.total, 42); + t.is(getTableRowsRO.pagination.lastPage, 21); + t.is(getTableRowsRO.pagination.perPage, 2); + t.is(getTableRowsRO.pagination.currentPage, 1); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial(`${currentTest} should return page of all rows with pagination page=3, perPage=2`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + ['id'], + undefined, + undefined, + 3, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=3&perPage=2`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + console.log('🚀 ~ getTableRowsRO:', getTableRowsRO); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].hasOwnProperty('id'), true); + t.is(getTableRowsRO.rows[1].hasOwnProperty(testTableColumnName), true); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + t.is(getTableRowsRO.primaryColumns[0].column_name, 'id'); + t.is(getTableRowsRO.primaryColumns[0].data_type, 'integer'); + + t.is(getTableRowsRO.pagination.total, 42); + t.is(getTableRowsRO.pagination.lastPage, 21); + t.is(getTableRowsRO.pagination.perPage, 2); + t.is(getTableRowsRO.pagination.currentPage, 3); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial( + `${currentTest} with search, with pagination, without sorting +should return all found rows with pagination page=1 perPage=2`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + t.is(getTableRowsRO.primaryColumns[0].column_name, 'id'); + t.is(getTableRowsRO.primaryColumns[0].data_type, 'integer'); + + t.is(getTableRowsRO.pagination.total, 3); + t.is(getTableRowsRO.pagination.lastPage, 2); + t.is(getTableRowsRO.pagination.perPage, 2); + t.is(getTableRowsRO.pagination.currentPage, 1); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination, without sorting +should return all found rows with pagination page=1 perPage=3`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + t.is(getTableRowsRO.primaryColumns[0].column_name, 'id'); + t.is(getTableRowsRO.primaryColumns[0].data_type, 'integer'); + + t.is(getTableRowsRO.pagination.total, 3); + t.is(getTableRowsRO.pagination.lastPage, 2); + t.is(getTableRowsRO.pagination.perPage, 2); + t.is(getTableRowsRO.pagination.currentPage, 1); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} without search and without pagination and with sorting +should return all found rows with sorting ids by DESC`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 42, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 42); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].id, 42); + t.is(getTableRowsRO.rows[1].id, 41); + t.is(getTableRowsRO.rows[41].id, 1); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} without search and without pagination and with sorting +should return all found rows with sorting ids by ASC`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 42, + QueryOrderingEnum.ASC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 42); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].id, 1); + t.is(getTableRowsRO.rows[1].id, 2); + t.is(getTableRowsRO.rows[41].id, 42); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} without search and with pagination and with sorting +should return all found rows with sorting ports by DESC and with pagination page=1, perPage=2`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=2`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].id, 42); + t.is(getTableRowsRO.rows[1].id, 41); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} should return all found rows with sorting ports by ASC and with pagination page=1, perPage=2`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.ASC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=2`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].id, 1); + t.is(getTableRowsRO.rows[1].id, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} should return all found rows with sorting ports by DESC and with pagination page=2, perPage=3`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=2&perPage=3`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 3); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].id, 39); + t.is(getTableRowsRO.rows[1].id, 38); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination and with sorting +should return all found rows with search, pagination: page=1, perPage=2 and DESC sorting`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=2&search=${testSearchedUserName}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].id, 38); + t.is(getTableRowsRO.rows[1].id, 22); + t.is(getTableRowsRO.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination and with sorting +should return all found rows with search, pagination: page=2, perPage=2 and DESC sorting`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=2&perPage=2&search=${testSearchedUserName}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 1); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + t.is(getTableRowsRO.rows[0].id, 1); + t.is(getTableRowsRO.pagination.currentPage, 2); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination and with sorting +should return all found rows with search, pagination: page=1, perPage=2 and ASC sorting`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.ASC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=2&search=${testSearchedUserName}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + t.is(getTableRowsRO.rows[0].id, 1); + t.is(getTableRowsRO.rows[1].id, 22); + t.is(getTableRowsRO.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination and with sorting +should return all found rows with search, pagination: page=2, perPage=2 and ASC sorting`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.ASC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=2&perPage=2&search=${testSearchedUserName}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 1); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + t.is(getTableRowsRO.rows[0].id, 38); + t.is(getTableRowsRO.pagination.currentPage, 2); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination, with sorting and with filtering +should return all found rows with search, pagination: page=1, perPage=2 and DESC sorting and filtering`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldvalue = '45'; + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[0].id, 38); + t.is(getTableRowsRO.rows[1][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[1].id, 22); + + t.is(getTableRowsRO.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +// todo: rework for other tables after removing old endpoint +test.serial( + `${currentTest} with search, with pagination, with sorting and with filtering +should return all found rows with search, pagination: page=1, perPage=2 and DESC sorting and filtering in body`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldvalue = '45'; + + const filters = { + [fieldname]: { lt: fieldvalue }, + }; + + const getTableRowsResponse = await request(app.getHttpServer()) + .post( + `/table/rows/find/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2`, + ) + .send({ filters }) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[0].id, 38); + t.is(getTableRowsRO.rows[1][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[1].id, 22); + + t.is(getTableRowsRO.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination, with sorting and with filtering +should return all found rows with search, pagination: page=1, perPage=10 and DESC sorting and filtering'`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldvalue = '41'; + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=10&f_${fieldname}__lt=${fieldvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 3); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[0].id, 38); + t.is(getTableRowsRO.rows[1][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[1].id, 22); + t.is(getTableRowsRO.rows[2][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[2].id, 1); + + t.is(getTableRowsRO.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 10); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination, with sorting and with filtering +should return all found rows with search, pagination: page=2, perPage=2 and DESC sorting and filtering`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldvalue = '41'; + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=2&perPage=2&f_${fieldname}__lt=${fieldvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 1); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[0].id, 1); + + t.is(getTableRowsRO.pagination.currentPage, 2); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `${currentTest} with search, with pagination, with sorting and with filtering +should return all found rows with search, pagination: page=1, perPage=2 and DESC sorting and with multi filtering`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldGtvalue = '25'; + const fieldLtvalue = '40'; + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}&f_${fieldname}__gt=${fieldGtvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 1); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + + t.is(getTableRowsRO.rows[0].id, 38); + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + + t.is(getTableRowsRO.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 2); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial(`${currentTest} should throw an exception when connection id is not passed in request`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldGtvalue = '25'; + const fieldLtvalue = '40'; + createConnectionRO.id = ''; + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}&f_${fieldname}__gt=${fieldGtvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 404); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial(`${currentTest} should throw an exception when connection id passed in request is incorrect`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldGtvalue = '25'; + const fieldLtvalue = '40'; + + createConnectionRO.id = faker.string.uuid(); + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}&f_${fieldname}__gt=${fieldGtvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 403); + + const { message } = JSON.parse(getTableRowsResponse.text); + + t.is(message, Messages.DONT_HAVE_PERMISSIONS); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial(`${currentTest} should throw an exception when table name passed in request is incorrect`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'id'; + const fieldGtvalue = '25'; + const fieldLtvalue = '40'; + + const fakeTableName = `${faker.lorem.words(1)}_${faker.string.uuid()}`; + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${fakeTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}&f_${fieldname}__gt=${fieldGtvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const { message } = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 400); + t.is(message, Messages.TABLE_NOT_FOUND); + } catch (e) { + console.error(e); + throw e; + } +}); + +test.serial( + `${currentTest} should return an array with searched fields when filtered name passed in request is incorrect`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = faker.lorem.words(1); + const fieldGtvalue = '25'; + const fieldLtvalue = '40'; + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}&f_${fieldname}__gt=${fieldGtvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTablesRO = JSON.parse(getTableRowsResponse.text); + t.is(getTablesRO.rows.length, 2); + t.is(getTablesRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTablesRO.rows[1][testTableColumnName], testSearchedUserName); + t.is(getTablesRO.hasOwnProperty('primaryColumns'), true); + t.is(getTablesRO.hasOwnProperty('pagination'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); + +currentTest = 'GET /table/structure/:slug'; +test.serial(`${currentTest} should return table structure`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const getTableStructure = await request(app.getHttpServer()) + .get(`/table/structure/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableStructure.status, 200); + const getTableStructureRO = JSON.parse(getTableStructure.text); + + t.is(typeof getTableStructureRO, 'object'); + t.is(typeof getTableStructureRO.structure, 'object'); + t.is(getTableStructureRO.structure.length, 6); + + for (const element of getTableStructureRO.structure) { + t.is(element.hasOwnProperty('column_name'), true); + t.is(element.hasOwnProperty('column_default'), true); + t.is(element.hasOwnProperty('data_type'), true); + t.is(element.hasOwnProperty('isExcluded'), true); + t.is(element.hasOwnProperty('isSearched'), true); + } + + t.is(getTableStructureRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableStructureRO.hasOwnProperty('foreignKeys'), true); + + for (const element of getTableStructureRO.primaryColumns) { + t.is(element.hasOwnProperty('column_name'), true); + t.is(element.hasOwnProperty('data_type'), true); + } + + for (const element of getTableStructureRO.foreignKeys) { + t.is(element.hasOwnProperty('referenced_column_name'), true); + t.is(element.hasOwnProperty('referenced_table_name'), true); + t.is(element.hasOwnProperty('constraint_name'), true); + t.is(element.hasOwnProperty('column_name'), true); + } +}); + +test.serial(`${currentTest} should throw an exception whe connection id not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + createConnectionRO.id = ''; + const getTableStructure = await request(app.getHttpServer()) + .get(`/table/structure/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableStructure.status, 404); +}); + +test.serial(`${currentTest} should throw an exception whe connection id passed in request id incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + createConnectionRO.id = faker.string.uuid(); + const getTableStructure = await request(app.getHttpServer()) + .get(`/table/structure/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableStructure.status, 403); + const { message } = JSON.parse(getTableStructure.text); + t.is(message, Messages.DONT_HAVE_PERMISSIONS); +}); + +test.serial(`${currentTest}should throw an exception when tableName not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const tableName = ''; + const getTableStructure = await request(app.getHttpServer()) + .get(`/table/structure/${createConnectionRO.id}?tableName=${tableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableStructure.status, 400); + const { message } = JSON.parse(getTableStructure.text); + t.is(message, Messages.TABLE_NAME_MISSING); +}); + +test.serial(`${currentTest} should throw an exception when tableName passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const tableName = faker.lorem.words(1); + const getTableStructure = await request(app.getHttpServer()) + .get(`/table/structure/${createConnectionRO.id}?tableName=${tableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableStructure.status, 400); + + const responseObject = JSON.parse(getTableStructure.text); + t.is(responseObject.message, Messages.TABLE_NOT_FOUND); +}); + +currentTest = 'POST /table/row/:slug'; + +test.serial(`${currentTest} should add row in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + id: 43, + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const addRowInTableResponse = await request(app.getHttpServer()) + .post(`/table/row/${createConnectionRO.id}?tableName=${testTableName}`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const addRowInTableRO = JSON.parse(addRowInTableResponse.text); + t.is(addRowInTableResponse.status, 201); + + t.is(addRowInTableRO.hasOwnProperty('row'), true); + t.is(addRowInTableRO.hasOwnProperty('structure'), true); + t.is(addRowInTableRO.hasOwnProperty('foreignKeys'), true); + t.is(addRowInTableRO.hasOwnProperty('primaryColumns'), true); + t.is(addRowInTableRO.hasOwnProperty('readonly_fields'), true); + t.is(addRowInTableRO.row[testTableColumnName], row[testTableColumnName]); + t.is(addRowInTableRO.row[testTableSecondColumnName], row[testTableSecondColumnName]); + + //checking that the line was added + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 43); + t.is(rows[42][testTableColumnName], row[testTableColumnName]); + t.is(rows[42][testTableSecondColumnName], row[testTableSecondColumnName]); + t.is(rows[42].id, rows[41].id + 1); + + // check that rows adding was logged + + const getLogsResponse = await request(app.getHttpServer()) + .get(`/logs/${createConnectionRO.id}?page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(getLogsResponse.status, 200); + const getLogsRO = JSON.parse(getLogsResponse.text); + console.log('🚀 ~ test.serial ~ getLogsRO:', getLogsRO.logs[1].affected_primary_key); + t.is(getLogsRO.hasOwnProperty('logs'), true); + t.is(getLogsRO.hasOwnProperty('pagination'), true); + t.is(getLogsRO.logs.length > 0, true); + const addRowLogIndex = getLogsRO.logs.findIndex((log) => log.operationType === 'addRow'); + t.is(getLogsRO.logs[addRowLogIndex].hasOwnProperty('affected_primary_key'), true); + t.is(typeof getLogsRO.logs[addRowLogIndex].affected_primary_key, 'object'); + t.is(getLogsRO.logs[addRowLogIndex].affected_primary_key.hasOwnProperty('id'), true); + t.is(getLogsRO.logs[addRowLogIndex].affected_primary_key.id, 43); +}); + +test.serial(`${currentTest} should throw an exception when connection id is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + id: 999, + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + const fakeConnectionId = ''; + const addRowInTableResponse = await request(app.getHttpServer()) + .post(`/table/row/${fakeConnectionId}?tableName=${testTableName}`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(addRowInTableResponse.status, 404); + + //checking that the line wasn't added + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); +}); + +test.serial(`${currentTest} should throw an exception when table name is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + id: 999, + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const addRowInTableResponse = await request(app.getHttpServer()) + .post(`/table/row/${createConnectionRO.id}?tableName=`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(addRowInTableResponse.status, 400); + const { message } = JSON.parse(addRowInTableResponse.text); + + t.is(message, Messages.TABLE_NAME_MISSING); + + //checking that the line wasn't added + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); +}); + +test.serial(`${currentTest} should throw an exception when row is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const addRowInTableResponse = await request(app.getHttpServer()) + .post(`/table/row/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(addRowInTableResponse.status, 400); + const { message } = JSON.parse(addRowInTableResponse.text); + + t.is(message, Messages.PARAMETER_MISSING); + + //checking that the line wasn't added + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); +}); + +test.serial(`${currentTest} should throw an exception when table name passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + id: 999, + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const fakeTableName = `${faker.lorem.words(1)}_${faker.number.int({ min: 1, max: 10000 })}`; + const addRowInTableResponse = await request(app.getHttpServer()) + .post(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const { message } = JSON.parse(addRowInTableResponse.text); + console.log('🚀 ~ test.serial ~ message:', message); + t.is(addRowInTableResponse.status, 400); + t.is(message, Messages.TABLE_NOT_FOUND); + + //checking that the line wasn't added + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); +}); + +currentTest = 'PUT /table/row/:slug'; + +test.serial(`${currentTest} should update row in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 200); + const updateRowInTableRO = JSON.parse(updateRowInTableResponse.text); + + t.is(updateRowInTableRO.hasOwnProperty('row'), true); + t.is(updateRowInTableRO.hasOwnProperty('structure'), true); + t.is(updateRowInTableRO.hasOwnProperty('foreignKeys'), true); + t.is(updateRowInTableRO.hasOwnProperty('primaryColumns'), true); + t.is(updateRowInTableRO.hasOwnProperty('readonly_fields'), true); + // t.is(updateRowInTableRO.row[testTableColumnName], row[testTableColumnName]); + // t.is(updateRowInTableRO.row[testTableSecondColumnName], row[testTableSecondColumnName]); + + //checking that the line was updated + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + const updateRowIndex = rows.map((row) => row.id).indexOf(1); + t.is(rows.length, 42); + t.is(rows[updateRowIndex][testTableColumnName], row[testTableColumnName]); + t.is(rows[updateRowIndex][testTableSecondColumnName], row[testTableSecondColumnName]); +}); + +test.serial(`${currentTest} should throw an exception when connection id not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + createConnectionRO.id = ''; + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 404); +}); + +test.serial(`${currentTest} should throw an exception when connection id passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + createConnectionRO.id = faker.string.uuid(); + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 403); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.DONT_HAVE_PERMISSIONS); +}); + +test.serial(`${currentTest} should throw an exception when tableName not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + createConnectionRO.id = faker.string.uuid(); + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=&id=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 400); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.TABLE_NAME_MISSING); +}); + +test.serial(`${currentTest} should throw an exception when tableName passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const fakeTableName = faker.string.uuid(); + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}&id=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 400); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.TABLE_NOT_FOUND); +}); + +test.serial(`${currentTest} should throw an exception when primary key not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 400); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.PRIMARY_KEY_INVALID); +}); + +test.serial( + `${currentTest} should throw an exception when primary key passed in request has incorrect field name`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&IncorrectField=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 400); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.PRIMARY_KEY_INVALID); + }, +); + +test.serial( + `${currentTest} should throw an exception when primary key passed in request has incorrect field value`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=100000000`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 400); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.ROW_PRIMARY_KEY_NOT_FOUND); + }, +); + +currentTest = 'PUT /table/rows/update/:connectionId'; + +test.serial(`${currentTest} should update multiple rows and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const requestData = { + primaryKeys: [{ id: 1 }, { id: 2 }], + newValues: { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }, + }; + + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/rows/update/${createConnectionRO.id}?tableName=${testTableName}`) + .send(JSON.stringify(requestData)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const updateRowInTableRO = JSON.parse(updateRowInTableResponse.text); + + t.is(updateRowInTableResponse.status, 200); + t.is(updateRowInTableRO.success, true); + + // check that the rows were updated + const firstRowResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=1`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const firstRow = JSON.parse(firstRowResponse.text); + t.is(firstRowResponse.status, 200); + t.is(firstRow.row[testTableColumnName], fakeName); + t.is(firstRow.row[testTableSecondColumnName], fakeMail); + + const secondRowResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=2`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const secondRow = JSON.parse(secondRowResponse.text); + t.is(secondRowResponse.status, 200); + t.is(secondRow.row[testTableColumnName], fakeName); + t.is(secondRow.row[testTableSecondColumnName], fakeMail); +}); + +currentTest = 'DELETE /table/row/:slug'; + +test.serial(`${currentTest} should delete row in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForDeletion = 1; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForDeletion}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 200); + const deleteRowInTableRO = JSON.parse(deleteRowInTableResponse.text); + + t.is(deleteRowInTableRO.hasOwnProperty('row'), true); + + //checking that the line was deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 41); + const deletedRowIndex = rows.map((row: Record) => row.id).indexOf(idForDeletion); + t.is(deletedRowIndex < 0, true); +}); + +test.serial(`${currentTest} should throw an exception when connection id not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForDeletion = 1; + const connectionId = ''; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${connectionId}?tableName=${testTableName}&id=${idForDeletion}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 404); + + //checking that the line wasn't deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); + const deletedRowIndex = rows.map((row: Record) => row.id).indexOf(idForDeletion); + t.is(deletedRowIndex < 0, false); +}); + +test.serial(`${currentTest} should throw an exception when connection id passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForDeletion = 1; + const connectionId = faker.string.uuid(); + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${connectionId}?tableName=${testTableName}&id=${idForDeletion}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 403); + const { message } = JSON.parse(deleteRowInTableResponse.text); + t.is(message, Messages.DONT_HAVE_PERMISSIONS); + + //checking that the line wasn't deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); + const deletedRowIndex = rows.map((row: Record) => row.id).indexOf(idForDeletion); + t.is(deletedRowIndex < 0, false); +}); + +test.serial(`${currentTest} should throw an exception when tableName not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForDeletion = 1; + const fakeTableName = ''; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}&id=${idForDeletion}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 400); + const { message } = JSON.parse(deleteRowInTableResponse.text); + t.is(message, Messages.TABLE_NAME_MISSING); + + //checking that the line wasn't deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); + const deletedRowIndex = rows.map((row: Record) => row.id).indexOf(idForDeletion); + t.is(deletedRowIndex < 0, false); +}); + +test.serial(`${currentTest} should throw an exception when tableName passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForDeletion = 1; + const fakeTableName = `${faker.lorem.words(1)}_${faker.string.uuid()}`; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}&id=${idForDeletion}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 400); + const { message } = JSON.parse(deleteRowInTableResponse.text); + t.is(message, Messages.TABLE_NOT_FOUND); + + //checking that the line wasn't deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); + const deletedRowIndex = rows.map((row: Record) => row.id).indexOf(idForDeletion); + t.is(deletedRowIndex < 0, false); +}); + +test.serial(`${currentTest} should throw an exception when primary key not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForDeletion = 1; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 400); + const { message } = JSON.parse(deleteRowInTableResponse.text); + t.is(message, Messages.PRIMARY_KEY_INVALID); + + //checking that the line wasn't deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); + const deletedRowIndex = rows.map((row: Record) => row.id).indexOf(idForDeletion); + t.is(deletedRowIndex < 0, false); +}); + +test.serial( + `${currentTest} should throw an exception when primary key passed in request has incorrect field name`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForDeletion = 1; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&fakePKey=1`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 400); + const { message } = JSON.parse(deleteRowInTableResponse.text); + t.is(message, Messages.PRIMARY_KEY_INVALID); + + //checking that the line wasn't deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); + const deletedRowIndex = rows.map((row: Record) => row.id).indexOf(idForDeletion); + t.is(deletedRowIndex < 0, false); + }, +); + +test.serial( + `${currentTest} should throw an exception when primary key passed in request has incorrect field value`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=100000`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const deleteRowInTableRO = JSON.parse(deleteRowInTableResponse.text); + t.is(deleteRowInTableResponse.status, 400); + t.is(deleteRowInTableRO.message, Messages.ROW_PRIMARY_KEY_NOT_FOUND); + }, +); + +currentTest = 'GET /table/row/:slug'; + +test.serial(`${currentTest} found row`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 200); + const foundRowInTableRO = JSON.parse(foundRowInTableResponse.text); + t.is(foundRowInTableRO.hasOwnProperty('row'), true); + t.is(foundRowInTableRO.hasOwnProperty('structure'), true); + t.is(foundRowInTableRO.hasOwnProperty('foreignKeys'), true); + t.is(foundRowInTableRO.hasOwnProperty('primaryColumns'), true); + t.is(foundRowInTableRO.hasOwnProperty('readonly_fields'), true); + t.is(typeof foundRowInTableRO.row, 'object'); + t.is(typeof foundRowInTableRO.structure, 'object'); + t.is(typeof foundRowInTableRO.primaryColumns, 'object'); + t.is(typeof foundRowInTableRO.readonly_fields, 'object'); + t.is(typeof foundRowInTableRO.foreignKeys, 'object'); + t.is(foundRowInTableRO.row.id, 1); + t.is(foundRowInTableRO.row[testTableColumnName], testSearchedUserName); + t.is(Object.keys(foundRowInTableRO.row).length, 6); +}); + +test.serial(`${currentTest} should throw an exception, when connection id is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const idForSearch = 1; + createConnectionRO.id = ''; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 404); +}); + +test.serial( + `${currentTest} should throw an exception, when connection id passed in request is incorrect`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1; + createConnectionRO.id = faker.string.uuid(); + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 403); + const { message } = JSON.parse(foundRowInTableResponse.text); + t.is(message, Messages.DONT_HAVE_PERMISSIONS); + }, +); + +test.serial(`${currentTest} should throw an exception, when tableName in not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1; + const fakeTableName = ''; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}&id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 400); + const { message } = JSON.parse(foundRowInTableResponse.text); + t.is(message, Messages.TABLE_NAME_MISSING); +}); + +test.serial(`${currentTest} should throw an exception, when tableName passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1; + const fakeTableName = `${faker.lorem.words(1)}_${faker.string.uuid()}`; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}&id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const FoundRowRO = JSON.parse(foundRowInTableResponse.text); + console.log('🚀 ~ test.serial ~ FoundRowRO:', FoundRowRO); + t.is(foundRowInTableResponse.status, 400); + t.is(FoundRowRO.message, Messages.TABLE_NOT_FOUND); +}); + +test.serial(`${currentTest} should throw an exception, when primary key is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 400); + const { message } = JSON.parse(foundRowInTableResponse.text); + t.is(message, Messages.PRIMARY_KEY_INVALID); +}); + +test.serial( + `${currentTest} should throw an exception, when primary key passed in request has incorrect name`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&fakeKeyName=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 400); + const { message } = JSON.parse(foundRowInTableResponse.text); + t.is(message, Messages.PRIMARY_KEY_INVALID); + }, +); + +test.serial( + `${currentTest} should throw an exception, when primary key passed in request has incorrect value`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1000000; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 400); + const { message } = JSON.parse(foundRowInTableResponse.text); + t.is(message, Messages.ROW_PRIMARY_KEY_NOT_FOUND); + }, +); + +currentTest = 'PUT /table/rows/delete/:slug'; + +test.serial(`${currentTest} should delete row in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const primaryKeysForDeletion: Array> = [ + { + id: 1, + }, + { + id: 10, + }, + { + id: 32, + }, + ]; + const deleteRowsInTableResponse = await request(app.getHttpServer()) + .put(`/table/rows/delete/${createConnectionRO.id}?tableName=${testTableName}`) + .send(primaryKeysForDeletion) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowsInTableResponse.status, 200); + const deleteRowInTableRO = JSON.parse(deleteRowsInTableResponse.text); + + //checking that the line was deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + // check that lines was deleted + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, testEntitiesSeedsCount - primaryKeysForDeletion.length); + + for (const key of primaryKeysForDeletion) { + t.is( + rows.findIndex((row) => row.id === key.id), + -1, + ); + } + + // check that table deletion was logged + const tableLogsResponse = await request(app.getHttpServer()) + .get(`/logs/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(tableLogsResponse.status, 200); + + const tableLogsRO = JSON.parse(tableLogsResponse.text); + t.is(tableLogsRO.logs.length, primaryKeysForDeletion.length + 1); + const onlyDeleteLogs = tableLogsRO.logs.filter((log) => log.operationType === LogOperationTypeEnum.deleteRow); + for (const key of primaryKeysForDeletion) { + t.is(onlyDeleteLogs.findIndex((log) => log.received_data.id === key.id) >= 0, true); + } +}); + +currentTest = 'DELETE /table/rows/:slug'; + +test.serial(`${currentTest} should delete rows in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const primaryKeysForDeletion = [ + { + id: 1, + }, + { + id: 10, + }, + { + id: 32, + }, + ]; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/rows/delete/${createConnectionRO.id}?tableName=${testTableName}`) + .send(primaryKeysForDeletion) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 200); + const deleteRowInTableRO = JSON.parse(deleteRowInTableResponse.text); + + //checking that the line was deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 200); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 39); + for (const primaryKey of primaryKeysForDeletion) { + t.is( + rows.findIndex((row) => row.id === primaryKey.id), + -1, + ); + } + + // check that deletion of rows was logged + + const getTableLogs = await request(app.getHttpServer()) + .get(`/logs/${createConnectionRO.id}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const getRowInTableRO = JSON.parse(getTableLogs.text); + const deleteRowsLogs = getRowInTableRO.logs.filter((log) => log.operationType === LogOperationTypeEnum.deleteRow); + t.is(deleteRowsLogs.length, primaryKeysForDeletion.length); +}); + +test.serial(`${currentTest} should test connection and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + const testConnectionResponse = await request(app.getHttpServer()) + .post('/connection/test/') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(testConnectionResponse.status, 201); + const { message } = JSON.parse(testConnectionResponse.text); + t.is(message, 'Successfully connected'); +}); + +test.serial( + `${currentTest} should test connection and return negative result when connection password is incorrect result`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + connectionToTestDB.password = '8764323452888'; + const testConnectionResponse = await request(app.getHttpServer()) + .post('/connection/test/') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(testConnectionResponse.status, 201); + const { result } = JSON.parse(testConnectionResponse.text); + t.is(result, false); + }, +); + +currentTest = 'GET table/csv/:slug'; + +test.serial(`${currentTest} should return csv file with table data`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const getTableCsvResponse = await request(app.getHttpServer()) + .post(`/table/csv/export/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'text/csv') + .set('Accept', 'text/csv'); + + if (getTableCsvResponse.status !== 201) { + const getTableCsvResponseRO = JSON.parse(getTableCsvResponse.text); + console.log(getTableCsvResponseRO); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(os.tmpdir(), fileName); + + // eslint-disable-next-line security/detect-non-literal-fs-filename + fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); + + // eslint-disable-next-line security/detect-non-literal-fs-filename + const isFileExists = fs.existsSync(downloadedFilePatch); + t.is(isFileExists, true); +}); + +test.serial(`${currentTest} should throw exception when csv export is disabled in table settings`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + false, + false, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableCsvResponse = await request(app.getHttpServer()) + .post(`/table/csv/export/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'text/csv') + .set('Accept', 'text/csv'); + + t.is(getTableCsvResponse.status, 400); + const { message } = JSON.parse(getTableCsvResponse.text); + t.is(message, Messages.CSV_EXPORT_DISABLED); +}); + +test.serial( + `${currentTest} should return csv file with table data with search, with pagination, with sorting, +with search and pagination: page=1, perPage=2 and DESC sorting`, + async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableCsvResponse = await request(app.getHttpServer()) + .post( + `/table/csv/export/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'text/csv') + .set('Accept', 'text/csv'); + + if (getTableCsvResponse.status !== 201) { + console.log(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(os.tmpdir(), fileName); + + // eslint-disable-next-line security/detect-non-literal-fs-filename + fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); + + // eslint-disable-next-line security/detect-non-literal-fs-filename + const isFileExists = fs.existsSync(downloadedFilePatch); + t.is(isFileExists, true); + }, +); + +currentTest = 'POST /table/csv/import/:slug'; +test.serial(`${currentTest} should import csv file with table data`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableCsvResponse = await request(app.getHttpServer()) + .post( + `/table/csv/export/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'text/csv') + .set('Accept', 'text/csv'); + + if (getTableCsvResponse.status !== 201) { + console.log(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(os.tmpdir(), fileName); + + // eslint-disable-next-line security/detect-non-literal-fs-filename + fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); + // eslint-disable-next-line security/detect-non-literal-fs-filename + const isFileExists = fs.existsSync(downloadedFilePatch); + t.is(isFileExists, true); + + function changeIdFieldsValuesInCsvFile(filePatch: string) { + const fileContent = fs.readFileSync(filePatch).toString(); + const rows = fileContent.split('\n'); + const newRows = rows.map((row, index) => { + if (index === 0) { + return row; + } + const columns = row.split(','); + if (columns.length === 1) { + return row; + } + columns[0] = `5${index}`; + return columns.join(','); + }); + return newRows.join('\n'); + } + + const newFileContent = changeIdFieldsValuesInCsvFile(downloadedFilePatch); + fs.writeFileSync(downloadedFilePatch, newFileContent); + + const importCsvResponse = await request(app.getHttpServer()) + .post(`/table/csv/import/${createConnectionRO.id}?tableName=${testTableName}`) + .attach('file', downloadedFilePatch) + .set('Cookie', firstUserToken) + .set('Accept', 'application/json'); + + t.is(importCsvResponse.status, 201); + + //checking that the lines was added + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 200); + const addedRows = getTableRowsRO.rows.filter((row: Record) => row.id > testEntitiesSeedsCount); + t.is(addedRows.length, 2); +}); + +test.serial(`${currentTest} should throw exception whe csv import is disabled`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).clickhouseTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + false, + true, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableCsvResponse = await request(app.getHttpServer()) + .post( + `/table/csv/export/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'text/csv') + .set('Accept', 'text/csv'); + + if (getTableCsvResponse.status !== 201) { + console.log(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(os.tmpdir(), fileName); + + // eslint-disable-next-line security/detect-non-literal-fs-filename + fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); + // eslint-disable-next-line security/detect-non-literal-fs-filename + const isFileExists = fs.existsSync(downloadedFilePatch); + t.is(isFileExists, true); + + function changeIdFieldsValuesInCsvFile(filePatch: string) { + const fileContent = fs.readFileSync(filePatch).toString(); + const rows = fileContent.split('\n'); + const newRows = rows.map((row, index) => { + if (index === 0) { + return row; + } + const columns = row.split(','); + if (columns.length === 1) { + return row; + } + columns[0] = `5${index}`; + return columns.join(','); + }); + return newRows.join('\n'); + } + + const newFileContent = changeIdFieldsValuesInCsvFile(downloadedFilePatch); + fs.writeFileSync(downloadedFilePatch, newFileContent); + + const importCsvResponse = await request(app.getHttpServer()) + .post(`/table/csv/import/${createConnectionRO.id}?tableName=${testTableName}`) + .attach('file', downloadedFilePatch) + .set('Cookie', firstUserToken) + .set('Accept', 'application/json'); + + t.is(importCsvResponse.status, 400); + + const { message } = JSON.parse(importCsvResponse.text); + t.is(message, Messages.CSV_IMPORT_DISABLED); +}); diff --git a/backend/test/ava-tests/saas-tests/table-dynamodb-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-dynamodb-e2e.test.ts index 60cd5300..ff6bd465 100644 --- a/backend/test/ava-tests/saas-tests/table-dynamodb-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-dynamodb-e2e.test.ts @@ -21,6 +21,7 @@ import { TestUtils } from '../../utils/test.utils.js'; import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; import { ValidationError } from 'class-validator'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; import { join } from 'path'; @@ -1526,95 +1527,96 @@ should return all found rows with search, pagination: page=1, perPage=2 and DESC }, ); -test.serial(`${currentTest} with search, with pagination, with sorting and with filtering by id -should return all found rows with search, pagination: page=1, perPage=2 and DESC sorting and with multi filtering`, async (t) => { - try { - const connectionToTestDB = getTestData(mockFactory).dynamoDBConnection; - const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; - const { testTableName, testTableColumnName, insertedSearchedIds } = await createTestTable(connectionToTestDB); +test.serial( + `${currentTest} with search, with pagination, with sorting and with filtering by id +should return all found rows with search, pagination: page=1, perPage=2 and DESC sorting and with multi filtering`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).dynamoDBConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, insertedSearchedIds } = await createTestTable(connectionToTestDB); - testTables.push(testTableName); + testTables.push(testTableName); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(connectionToTestDB) - .set('Cookie', firstUserToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); - const createTableSettingsDTO = mockFactory.generateTableSettings( - createConnectionRO.id, - testTableName, - [], - undefined, - undefined, - 3, - QueryOrderingEnum.DESC, - 'id', - undefined, - undefined, - undefined, - undefined, - undefined, - ); + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + 'id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); - const createTableSettingsResponse = await request(app.getHttpServer()) - .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) - .send(createTableSettingsDTO) - .set('Cookie', firstUserToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - t.is(createTableSettingsResponse.status, 201); + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); - const fieldname = 'age'; - const idFieldName = 'id'; - const idFieldValue = 21; - const fieldGtvalue = 14; - // const fieldLtvalue = 95; + const fieldname = 'age'; + const idFieldName = 'id'; + const idFieldValue = 21; + const fieldGtvalue = 14; + // const fieldLtvalue = 95; - const filters = { - // [fieldname]: { gt: fieldGtvalue }, - [idFieldName]: { eq: idFieldValue }, - }; + const filters = { + // [fieldname]: { gt: fieldGtvalue }, + [idFieldName]: { eq: idFieldValue }, + }; - const getTableRowsResponse = await request(app.getHttpServer()) - .post( - `/table/rows/find/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=3`, - ) - .send({ filters }) - .set('Cookie', firstUserToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - t.is(getTableRowsResponse.status, 201); + const getTableRowsResponse = await request(app.getHttpServer()) + .post(`/table/rows/find/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=3`) + .send({ filters }) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 201); - const getTableRowsRO = JSON.parse(getTableRowsResponse.text); - console.log('🚀 ~ getTableRowsRO:', getTableRowsRO.rows); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + console.log('🚀 ~ getTableRowsRO:', getTableRowsRO.rows); - t.is(typeof getTableRowsRO, 'object'); - t.is(getTableRowsRO.hasOwnProperty('rows'), true); - t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); - t.is(getTableRowsRO.hasOwnProperty('pagination'), true); - t.is(getTableRowsRO.rows.length, 1); - t.is(Object.keys(getTableRowsRO.rows[0]).length, 11); + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 1); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 11); - const findRowId = 21; + const findRowId = 21; - t.is(getTableRowsRO.rows[0].id, findRowId); - t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[0].id, findRowId); + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); - t.is(getTableRowsRO.pagination.currentPage, 1); - t.is(getTableRowsRO.pagination.perPage, 3); + t.is(getTableRowsRO.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 3); - t.is(typeof getTableRowsRO.primaryColumns, 'object'); - t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); - t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); - } catch (e) { - console.error(e); - throw e; - } -}); + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } + }, +); test.serial(`${currentTest} should throw an exception when connection id is not passed in request`, async (t) => { try { @@ -3522,12 +3524,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3597,12 +3595,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3668,12 +3662,8 @@ test.skip(`${currentTest} should import csv file with table data`, async (t) => } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-elasticsearch-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-elasticsearch-e2e.test.ts index 1c508eba..d7a331cc 100644 --- a/backend/test/ava-tests/saas-tests/table-elasticsearch-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-elasticsearch-e2e.test.ts @@ -21,6 +21,7 @@ import { TestUtils } from '../../utils/test.utils.js'; import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; import { ValidationError } from 'class-validator'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; import { join } from 'path'; @@ -3493,12 +3494,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3568,12 +3565,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3637,12 +3630,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-ibmdb2-agent-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-ibmdb2-agent-e2e.test.ts index f04a67b9..c3780b91 100644 --- a/backend/test/ava-tests/saas-tests/table-ibmdb2-agent-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-ibmdb2-agent-e2e.test.ts @@ -23,6 +23,7 @@ import { TestUtils } from '../../utils/test.utils.js'; import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; import { ValidationError } from 'class-validator'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; import { join } from 'path'; @@ -3035,12 +3036,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3099,12 +3096,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3162,12 +3155,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-ibmdb2-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-ibmdb2-e2e.test.ts index 889b708b..11f686d9 100644 --- a/backend/test/ava-tests/saas-tests/table-ibmdb2-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-ibmdb2-e2e.test.ts @@ -23,6 +23,7 @@ import { TestUtils } from '../../utils/test.utils.js'; import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; import { ValidationError } from 'class-validator'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; import { join } from 'path'; @@ -3472,12 +3473,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3542,12 +3539,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3611,12 +3604,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-logs-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-logs-e2e.test.ts index 66937fda..87cb6490 100644 --- a/backend/test/ava-tests/saas-tests/table-logs-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-logs-e2e.test.ts @@ -14,6 +14,7 @@ import { createConnectionsAndInviteNewUserInAdminGroupOfFirstConnection } from ' import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; import { ValidationError } from 'class-validator'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; import { join } from 'path'; @@ -324,12 +325,7 @@ test.serial(`${currentTest} should return all found logs in connection as csv`, } t.is(getTableLogsCSV.status, 200); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); - const dir = join(__dirname, 'response-files'); - - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } + const downloadedFilePatch = join(os.tmpdir(), fileName); // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableLogsCSV.body); diff --git a/backend/test/ava-tests/saas-tests/table-mongodb-agent-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-mongodb-agent-e2e.test.ts index c8141c19..d4b2a409 100644 --- a/backend/test/ava-tests/saas-tests/table-mongodb-agent-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-mongodb-agent-e2e.test.ts @@ -21,6 +21,7 @@ import { TestUtils } from '../../utils/test.utils.js'; import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; import { ValidationError } from 'class-validator'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; import { join } from 'path'; @@ -3107,12 +3108,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3171,12 +3168,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3234,12 +3227,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-mongodb-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-mongodb-e2e.test.ts index 3fda77b3..4d753f98 100644 --- a/backend/test/ava-tests/saas-tests/table-mongodb-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-mongodb-e2e.test.ts @@ -21,6 +21,7 @@ import { TestUtils } from '../../utils/test.utils.js'; import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; import { ValidationError } from 'class-validator'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; import { join } from 'path'; @@ -3514,12 +3515,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3589,12 +3586,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3658,12 +3651,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-mssql-agent-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-mssql-agent-e2e.test.ts index 93cc854d..aa9c87b4 100644 --- a/backend/test/ava-tests/saas-tests/table-mssql-agent-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-mssql-agent-e2e.test.ts @@ -25,6 +25,7 @@ import { ErrorsMessages } from '../../../src/exceptions/custom-exceptions/messag import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; import { ValidationError } from 'class-validator'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; import { join } from 'path'; @@ -3193,12 +3194,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3257,12 +3254,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-mssql-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-mssql-e2e.test.ts index 894cca69..5b863f31 100644 --- a/backend/test/ava-tests/saas-tests/table-mssql-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-mssql-e2e.test.ts @@ -23,6 +23,7 @@ import { TestUtils } from '../../utils/test.utils.js'; import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; import { ValidationError } from 'class-validator'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; import { join } from 'path'; @@ -3475,12 +3476,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3545,12 +3542,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3614,12 +3607,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-mysql-agent-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-mysql-agent-e2e.test.ts index 6399dcda..570adf30 100644 --- a/backend/test/ava-tests/saas-tests/table-mysql-agent-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-mysql-agent-e2e.test.ts @@ -25,6 +25,7 @@ import { ErrorsMessages } from '../../../src/exceptions/custom-exceptions/messag import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; import { ValidationError } from 'class-validator'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; import { join } from 'path'; @@ -3195,12 +3196,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3259,12 +3256,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-mysql-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-mysql-e2e.test.ts index e05e15bb..224748cc 100644 --- a/backend/test/ava-tests/saas-tests/table-mysql-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-mysql-e2e.test.ts @@ -23,6 +23,7 @@ import { TestUtils } from '../../utils/test.utils.js'; import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; import { ValidationError } from 'class-validator'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; import { join } from 'path'; @@ -3474,12 +3475,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3544,12 +3541,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3613,12 +3606,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-oracle-agent-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-oracle-agent-e2e.test.ts index c1cca7aa..bf8f1dcb 100644 --- a/backend/test/ava-tests/saas-tests/table-oracle-agent-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-oracle-agent-e2e.test.ts @@ -25,6 +25,7 @@ import { ErrorsMessages } from '../../../src/exceptions/custom-exceptions/messag import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; import { ValidationError } from 'class-validator'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; import { join } from 'path'; @@ -3195,12 +3196,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3259,12 +3256,8 @@ test.skip(`${currentTest} should import csv file with table data`, async (t) => } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-oracledb-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-oracledb-e2e.test.ts index c270ad71..eef53c5c 100644 --- a/backend/test/ava-tests/saas-tests/table-oracledb-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-oracledb-e2e.test.ts @@ -7,6 +7,7 @@ import test from 'ava'; import { ValidationError } from 'class-validator'; import cookieParser from 'cookie-parser'; import fs from 'fs'; +import os from 'os'; import path, { join } from 'path'; import request from 'supertest'; import { fileURLToPath } from 'url'; @@ -3693,12 +3694,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3763,12 +3760,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3832,12 +3825,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-postgres-agent-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-postgres-agent-e2e.test.ts index c3d86750..d564d1ab 100644 --- a/backend/test/ava-tests/saas-tests/table-postgres-agent-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-postgres-agent-e2e.test.ts @@ -25,6 +25,7 @@ import { ErrorsMessages } from '../../../src/exceptions/custom-exceptions/messag import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; import { ValidationError } from 'class-validator'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; import { join } from 'path'; @@ -3194,12 +3195,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3258,12 +3255,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-postgres-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-postgres-e2e.test.ts index 0e9bbeb4..0b5aae61 100644 --- a/backend/test/ava-tests/saas-tests/table-postgres-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-postgres-e2e.test.ts @@ -8,6 +8,7 @@ import test from 'ava'; import { ValidationError } from 'class-validator'; import cookieParser from 'cookie-parser'; import fs from 'fs'; +import os from 'os'; import path, { join } from 'path'; import request from 'supertest'; import { fileURLToPath } from 'url'; @@ -3866,12 +3867,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3989,12 +3986,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -4058,12 +4051,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename @@ -4167,12 +4156,8 @@ test.serial(`${currentTest} should throw exception whe csv import is disabled`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-redis-agent-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-redis-agent-e2e.test.ts index 4ec45543..2b5d1637 100644 --- a/backend/test/ava-tests/saas-tests/table-redis-agent-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-redis-agent-e2e.test.ts @@ -7,6 +7,7 @@ import test from 'ava'; import { ValidationError } from 'class-validator'; import cookieParser from 'cookie-parser'; import fs from 'fs'; +import os from 'os'; import path, { join } from 'path'; import request from 'supertest'; import { fileURLToPath } from 'url'; @@ -3201,12 +3202,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3267,12 +3264,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3333,12 +3326,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/ava-tests/saas-tests/table-redis-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-redis-e2e.test.ts index 3ce589eb..f5b3877c 100644 --- a/backend/test/ava-tests/saas-tests/table-redis-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/table-redis-e2e.test.ts @@ -7,6 +7,7 @@ import test from 'ava'; import { ValidationError } from 'class-validator'; import cookieParser from 'cookie-parser'; import fs from 'fs'; +import os from 'os'; import path, { join } from 'path'; import request from 'supertest'; import { fileURLToPath } from 'url'; @@ -3515,12 +3516,8 @@ test.serial(`${currentTest} should return csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3590,12 +3587,8 @@ with search and pagination: page=1, perPage=2 and DESC sorting`, } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); @@ -3660,12 +3653,8 @@ test.serial(`${currentTest} should import csv file with table data`, async (t) = } t.is(getTableCsvResponse.status, 201); const fileName = `${testTableName}.csv`; - const downloadedFilePatch = join(__dirname, 'response-files', fileName); + const downloadedFilePatch = join(os.tmpdir(), fileName); - const dir = join(__dirname, 'response-files'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } // eslint-disable-next-line security/detect-non-literal-fs-filename fs.writeFileSync(downloadedFilePatch, getTableCsvResponse.body); // eslint-disable-next-line security/detect-non-literal-fs-filename diff --git a/backend/test/mock.factory.ts b/backend/test/mock.factory.ts index 42a8f349..3a15b798 100644 --- a/backend/test/mock.factory.ts +++ b/backend/test/mock.factory.ts @@ -103,6 +103,26 @@ export class MockFactory { return dto; } + generateConnectionToTestClickHouseDBInDocker() { + const dto = new CreateConnectionDto() as any; + dto.title = 'Test connection to ClickHouse in Docker'; + dto.type = 'clickhouse'; + dto.host = 'clickhouse-e2e-testing'; + dto.port = 8123; + dto.username = 'default'; + dto.password = 'clickhouse_password'; + dto.database = 'testdb'; + dto.ssh = false; + return dto; + } + + generateConnectionToTestClickHouseAgent() { + const dto = new CreateConnectionDto() as any; + dto.title = 'Test connection to agent db'; + dto.type = ConnectionTypesEnum.agent_clickhouse; + return dto; + } + generateConnectionToTestMySQLDBInDocker() { const dto = new CreateConnectionDto() as any; dto.title = 'Test connection to MySQL in Docker'; diff --git a/backend/test/utils/create-test-table.ts b/backend/test/utils/create-test-table.ts index f38dd040..0c2e65f5 100644 --- a/backend/test/utils/create-test-table.ts +++ b/backend/test/utils/create-test-table.ts @@ -11,6 +11,7 @@ import { Client } from '@elastic/elasticsearch'; import * as cassandra from 'cassandra-driver'; import { v4 as uuidv4 } from 'uuid'; import { createClient } from 'redis'; +import { createClient as createClickHouseClient } from '@clickhouse/client'; export async function createTestTable( connectionParams: any, @@ -43,6 +44,10 @@ export async function createTestTable( return createTestRedisTable(connectionParams, testEntitiesSeedsCount, testSearchedUserName); } + if (connectionParams.type === ConnectionTypesEnum.clickhouse) { + return createTestClickHouseTable(connectionParams, testEntitiesSeedsCount, testSearchedUserName); + } + const testTableName = getRandomTestTableName(); const testTableColumnName = `${faker.lorem.words(1)}_${faker.lorem.words(1)}`; const testTableSecondColumnName = `${faker.lorem.words(1)}_${faker.lorem.words(1)}`; @@ -882,3 +887,92 @@ async function createTestRedisTable( insertedSearchedIds, }; } + +async function createTestClickHouseTable( + connectionParams: any, + testEntitiesSeedsCount = 42, + testSearchedUserName = 'Vasia', +): Promise { + const testTableName = getRandomTestTableName().toLowerCase().replace(/-/g, '_'); + const testTableColumnName = 'name'; + const testTableSecondColumnName = 'email'; + + const client = createClickHouseClient({ + url: `http://${connectionParams.host}:${connectionParams.port}`, + username: connectionParams.username || 'default', + password: connectionParams.password || '', + database: connectionParams.database || 'default', + }); + + try { + await client.command({ + query: ` + CREATE TABLE IF NOT EXISTS ${testTableName} ( + id UInt32, + ${testTableColumnName} String, + ${testTableSecondColumnName} String, + age UInt32, + created_at DateTime, + updated_at DateTime + ) ENGINE = MergeTree() + ORDER BY id + `, + }); + + const insertedSearchedIds: Array<{ number: number; id: string }> = []; + + const rows: Array<{ + id: number; + name: string; + email: string; + age: number; + created_at: string; + updated_at: string; + }> = []; + + for (let i = 0; i < testEntitiesSeedsCount; i++) { + const isSearchedUser = i === 0 || i === testEntitiesSeedsCount - 21 || i === testEntitiesSeedsCount - 5; + const age = isSearchedUser + ? i === 0 + ? 14 + : i === testEntitiesSeedsCount - 21 + ? 21 + : 37 + : faker.number.int({ min: 16, max: 80 }); + + const row = { + id: i + 1, + name: isSearchedUser ? testSearchedUserName : faker.person.firstName(), + email: faker.internet.email(), + age: age, + created_at: new Date().toISOString().replace('T', ' ').substring(0, 19), + updated_at: new Date().toISOString().replace('T', ' ').substring(0, 19), + }; + + rows.push(row); + + if (isSearchedUser) { + insertedSearchedIds.push({ + number: i, + id: String(i + 1), + }); + } + } + + await client.insert({ + table: testTableName, + values: rows, + format: 'JSONEachRow', + }); + + return { + testTableName: testTableName, + testTableColumnName: testTableColumnName, + testTableSecondColumnName: testTableSecondColumnName, + testEntitiesSeedsCount: testEntitiesSeedsCount, + insertedSearchedIds, + }; + } finally { + await client.close(); + } +} diff --git a/backend/test/utils/get-test-data.ts b/backend/test/utils/get-test-data.ts index 1a8abd43..84806494 100644 --- a/backend/test/utils/get-test-data.ts +++ b/backend/test/utils/get-test-data.ts @@ -32,6 +32,8 @@ export function getTestData(mockFactory: MockFactory) { const cassandraAgentTestConnection = mockFactory.generateConnectionToTestCassandraAgent(); const redisConnection = mockFactory.generateConnectionToTestRedisInDocker(); const redisAgentConnection = mockFactory.generateConnectionToTestRedisAgent(); + const clickhouseTestConnection = mockFactory.generateConnectionToTestClickHouseDBInDocker(); + const clickhouseAgentTestConnection = mockFactory.generateConnectionToTestClickHouseAgent(); return { newConnection, newEncryptedConnection, @@ -64,5 +66,7 @@ export function getTestData(mockFactory: MockFactory) { cassandraAgentTestConnection, redisConnection, redisAgentConnection, + clickhouseTestConnection, + clickhouseAgentTestConnection }; } diff --git a/backend/tsconfig.json b/backend/tsconfig.json index c2a55682..9d487891 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -10,6 +10,7 @@ "target": "ESNext", "sourceMap": true, "outDir": "./dist", + "rootDir": "./", "baseUrl": "./", "incremental": true, "moduleResolution": "node", diff --git a/docker-compose.tst.yml b/docker-compose.tst.yml index c2caf07e..661df479 100644 --- a/docker-compose.tst.yml +++ b/docker-compose.tst.yml @@ -19,6 +19,7 @@ services: - test-dynamodb-e2e-testing - test-redis-e2e-testing - test-cassandra-e2e-testing + - test-clickhouse-e2e-testing links: - postgres - testMySQL-e2e-testing @@ -29,6 +30,7 @@ services: - test-dynamodb-e2e-testing - test-redis-e2e-testing - test-cassandra-e2e-testing + - test-clickhouse-e2e-testing command: ["yarn", "start"] backend_test: build: @@ -37,6 +39,7 @@ services: environment: - "DATABASE_URL=postgres://postgres:abc123@postgres:5432/postgres" - "EXTRA_ARGS=$EXTRA_ARGS" + - LOG_LEVEL=warn volumes: - ./backend/src/migrations:/app/src/migrations depends_on: @@ -60,6 +63,8 @@ services: condition: service_healthy test-cassandra-e2e-testing: condition: service_healthy + test-clickhouse-e2e-testing: + condition: service_healthy command: ["/bin/sh", "-c", "yarn test-all $EXTRA_ARGS"] develop: watch: @@ -76,7 +81,8 @@ services: MYSQL_ROOT_PASSWORD: 123 MYSQL_DATABASE: testDB healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p123"] + test: + ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p123"] interval: 10s timeout: 5s retries: 10 @@ -135,7 +141,11 @@ services: ports: - "5434:1433" healthcheck: - test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'yNuXf@6T#BgoQ%U6knMp' -C -Q 'SELECT 1' || /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'yNuXf@6T#BgoQ%U6knMp' -Q 'SELECT 1'"] + test: + [ + "CMD-SHELL", + "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'yNuXf@6T#BgoQ%U6knMp' -C -Q 'SELECT 1' || /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'yNuXf@6T#BgoQ%U6knMp' -Q 'SELECT 1'", + ] interval: 10s timeout: 5s retries: 20 @@ -200,97 +210,6 @@ services: timeout: 10s retries: 3 - # rocketadmin-agent_oracle: - # build: - # context: . - # dockerfile: ./rocketadmin-agent/Dockerfile - # ports: - # - 8088:8088 - # volumes: - # - ./rocketadmin-agent/dist:/app/dist - # - ./rocketadmin-agent/src:/app/src - # links: - # - autoadmin-ws-server - # depends_on: - # - autoadmin-ws-server - # environment: - # - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - # - APPLICATION_CONFIG_FILE_NAME=.oracle_test_agent_config.txt - # command: ["yarn", "start:dev"] - - # rocketadmin-agent_postgres: - # build: - # context: . - # dockerfile: ./rocketadmin-agent/Dockerfile - # ports: - # - 8098:8098 - # volumes: - # - ./rocketadmin-agent/dist:/app/dist - # - ./rocketadmin-agent/src:/app/src - # links: - # - autoadmin-ws-server - # depends_on: - # - autoadmin-ws-server - # environment: - # - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - # - APPLICATION_CONFIG_FILE_NAME=.postgres_test_agent_config.txt - # command: ["yarn", "start:dev"] - - # rocketadmin-agent_mysql: - # build: - # context: . - # dockerfile: ./rocketadmin-agent/Dockerfile - # ports: - # - 8108:8108 - # volumes: - # - ./rocketadmin-agent/dist:/app/dist - # - ./rocketadmin-agent/src:/app/src - # links: - # - autoadmin-ws-server - # depends_on: - # - autoadmin-ws-server - # environment: - # - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - # - APPLICATION_CONFIG_FILE_NAME=.mysql_test_agent_config.txt - # command: ["yarn", "start:dev"] - - # rocketadmin-agent_mssql: - # build: - # context: . - # dockerfile: ./rocketadmin-agent/Dockerfile - # ports: - # - 8118:8118 - # volumes: - # - ./rocketadmin-agent/dist:/app/dist - # - ./rocketadmin-agent/src:/app/src - # links: - # - autoadmin-ws-server - # depends_on: - # - autoadmin-ws-server - # environment: - # - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - # - APPLICATION_CONFIG_FILE_NAME=.mssql_test_agent_config.txt - # command: ["yarn", "start:dev"] - - # rocketadmin-agent_ibmdb2: - # build: - # context: . - # dockerfile: ./rocketadmin-agent/Dockerfile - # ports: - # - 8308:8308 - # volumes: - # - ./rocketadmin-agent/dist:/app/dist - # - ./rocketadmin-agent/src:/app/src - # - ./rocketadmin-agent/wait-for-db2.sh:/app/wait-for-db2.sh - # links: - # - autoadmin-ws-server - # depends_on: - # - autoadmin-ws-server - # environment: - # - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - # - APPLICATION_CONFIG_FILE_NAME=.ibmdb2_test_agent_config.txt - # command: ["/bin/sh", "/app/wait-for-db2.sh"] - test-ibm-db2-e2e-testing: image: icr.io/db2_community/db2 restart: always @@ -331,3 +250,23 @@ services: timeout: 5s retries: 10 start_period: 10s + + test-clickhouse-e2e-testing: + image: clickhouse/clickhouse-server:latest + ports: + - 8123:8123 + - 9000:9000 + environment: + - CLICKHOUSE_DB=testdb + - CLICKHOUSE_USER=default + - CLICKHOUSE_PASSWORD=clickhouse_password + - CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 + ulimits: + nofile: + soft: 262144 + hard: 262144 + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:8123/ping"] + interval: 30s + timeout: 10s + retries: 3 diff --git a/docker-compose.yml b/docker-compose.yml index 90e076bf..4b71cc05 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,7 +22,8 @@ services: - test-elasticsearch-e2e-testing - cassandra-init - test-cassandra-e2e-testing - - redis-e2e-testing + - test-redis-e2e-testing + - clickhouse-e2e-testing links: - postgres - testMySQL-e2e-testing @@ -34,7 +35,8 @@ services: - test-dynamodb-e2e-testing - test-elasticsearch-e2e-testing - test-cassandra-e2e-testing - - redis-e2e-testing + - test-redis-e2e-testing + - clickhouse-e2e-testing command: ["yarn", "start"] testMySQL-e2e-testing: @@ -203,7 +205,7 @@ services: command: ["/init-cassandra.sh"] restart: "no" - redis-e2e-testing: + test-redis-e2e-testing: image: redis:7.0.11 ports: - 6379:6379 @@ -214,6 +216,26 @@ services: timeout: 10s retries: 3 + clickhouse-e2e-testing: + image: clickhouse/clickhouse-server:latest + ports: + - 8123:8123 + - 9000:9000 + environment: + - CLICKHOUSE_DB=testdb + - CLICKHOUSE_USER=default + - CLICKHOUSE_PASSWORD=clickhouse_password + - CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 + ulimits: + nofile: + soft: 262144 + hard: 262144 + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:8123/ping"] + interval: 30s + timeout: 10s + retries: 3 + rocketadmin-agent_mongo: build: context: . @@ -359,11 +381,31 @@ services: - ./rocketadmin-agent/src:/app/src links: - autoadmin-ws-server - - redis-e2e-testing + - test-redis-e2e-testing depends_on: - autoadmin-ws-server - - redis-e2e-testing + - test-redis-e2e-testing environment: - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - APPLICATION_CONFIG_FILE_NAME=.redis_test_agent_config.txt command: ["yarn", "start:dev"] + + rocketadmin-agent_clickhouse: + build: + context: . + dockerfile: ./rocketadmin-agent/Dockerfile + ports: + - 8148:8148 + volumes: + - ./rocketadmin-agent/dist:/app/dist + - ./rocketadmin-agent/src:/app/src + links: + - autoadmin-ws-server + - clickhouse-e2e-testing + depends_on: + - autoadmin-ws-server + - clickhouse-e2e-testing + environment: + - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 + - APPLICATION_CONFIG_FILE_NAME=.clickhouse_test_agent_config.txt + command: ["yarn", "start:dev"] diff --git a/rocketadmin-agent/.clickhouse_test_agent_config.txt b/rocketadmin-agent/.clickhouse_test_agent_config.txt new file mode 100644 index 00000000..5a5e00cf --- /dev/null +++ b/rocketadmin-agent/.clickhouse_test_agent_config.txt @@ -0,0 +1,21 @@ +{ + "encrypted": false, + "hash": null, + "credentials": { + "app_port": 3000, + "azure_encryption": false, + "cert": null, + "database": "testdb", + "host": "clickhouse-e2e-testing", + "password": "clickhouse_password", + "port": 8123, + "token": "CLICKHOUSE-TEST-AGENT-TOKEN", + "type": "clickhouse", + "username": "default", + "ssl": false, + "application_save_option": true, + "config_encryption_option": false, + "encryption_password": null, + "saving_logs_option": false + } +} diff --git a/rocketadmin-agent/.redis_test_agent_config.txt b/rocketadmin-agent/.redis_test_agent_config.txt index ccd4208c..36c131c3 100644 --- a/rocketadmin-agent/.redis_test_agent_config.txt +++ b/rocketadmin-agent/.redis_test_agent_config.txt @@ -5,7 +5,7 @@ "app_port": 3000, "azure_encryption": false, "cert": null, - "host": "redis-e2e-testing", + "host": "test-redis-e2e-testing", "password": "SuperSecretRedisPassword", "port": 6379, "schema": null, diff --git a/rocketadmin-agent/Dockerfile b/rocketadmin-agent/Dockerfile index 1656d046..aa667752 100644 --- a/rocketadmin-agent/Dockerfile +++ b/rocketadmin-agent/Dockerfile @@ -19,6 +19,7 @@ COPY ./rocketadmin-agent/.ibmdb2_test_agent_config.txt /app/rocketadmin-agent/ COPY ./rocketadmin-agent/.mongodb_test_agent_config.txt /app/rocketadmin-agent/ COPY ./rocketadmin-agent/.cassandra_test_agent_config.txt /app/rocketadmin-agent/ COPY ./rocketadmin-agent/.redis_test_agent_config.txt /app/rocketadmin-agent/ +COPY ./rocketadmin-agent/.clickhouse_test_agent_config.txt /app/rocketadmin-agent/ COPY shared-code /app/shared-code COPY ./rocketadmin-agent/ssl-cert.txt /app/rocketadmin-agent/ RUN cd shared-code && ../node_modules/.bin/tsc diff --git a/rocketadmin-agent/src/helpers/cli/cli-questions.ts b/rocketadmin-agent/src/helpers/cli/cli-questions.ts index 38c79c31..41dacf4e 100644 --- a/rocketadmin-agent/src/helpers/cli/cli-questions.ts +++ b/rocketadmin-agent/src/helpers/cli/cli-questions.ts @@ -35,6 +35,7 @@ export class CLIQuestionUtility { 'MongoDB', 'IBM Db2', 'Redis', + 'ClickHouse', ]; const connectionTypeIndex = readlineSync.keyInSelect(connectionTypeList, '-> \n') + 1; switch (connectionTypeIndex) { @@ -59,6 +60,9 @@ export class CLIQuestionUtility { case 7: console.log(`${connectionTypeList[connectionTypeIndex - 1]} selected.`); return ConnectionTypesEnum.redis; + case 8: + console.log(`${connectionTypeList[connectionTypeIndex - 1]} selected.`); + return ConnectionTypesEnum.clickhouse; case 0: console.log(Messages.INTRO_MESSAGES.APPLICATION_CLI_QUIT); process.exit(0); diff --git a/shared-code/package.json b/shared-code/package.json index 90073a16..21a544e0 100644 --- a/shared-code/package.json +++ b/shared-code/package.json @@ -9,6 +9,7 @@ "dependencies": { "@aws-sdk/client-dynamodb": "^3.918.0", "@aws-sdk/lib-dynamodb": "^3.918.0", + "@clickhouse/client": "^1.14.0", "@elastic/elasticsearch": "8.18.1", "@types/multer": "^2.0.0", "axios": "^1.13.0", diff --git a/shared-code/src/data-access-layer/data-access-objects/data-access-object-clickhouse.ts b/shared-code/src/data-access-layer/data-access-objects/data-access-object-clickhouse.ts new file mode 100644 index 00000000..a45e5c78 --- /dev/null +++ b/shared-code/src/data-access-layer/data-access-objects/data-access-object-clickhouse.ts @@ -0,0 +1,834 @@ +/* eslint-disable security/detect-object-injection */ +import { createClient, ClickHouseClient } from '@clickhouse/client'; +import * as csv from 'csv'; +import { Readable, Stream } from 'stream'; +import { LRUStorage } from '../../caching/lru-storage.js'; +import { DAO_CONSTANTS } from '../../helpers/data-access-objects-constants.js'; +import { ERROR_MESSAGES } from '../../helpers/errors/error-messages.js'; +import { tableSettingsFieldValidator } from '../../helpers/validation/table-settings-validator.js'; +import { AutocompleteFieldsDS } from '../shared/data-structures/autocomplete-fields.ds.js'; +import { ConnectionParams } from '../shared/data-structures/connections-params.ds.js'; +import { FilteringFieldsDS } from '../shared/data-structures/filtering-fields.ds.js'; +import { ForeignKeyDS } from '../shared/data-structures/foreign-key.ds.js'; +import { FoundRowsDS } from '../shared/data-structures/found-rows.ds.js'; +import { PrimaryKeyDS } from '../shared/data-structures/primary-key.ds.js'; +import { ReferencedTableNamesAndColumnsDS } from '../shared/data-structures/referenced-table-names-columns.ds.js'; +import { TableSettingsDS } from '../shared/data-structures/table-settings.ds.js'; +import { TableStructureDS } from '../shared/data-structures/table-structure.ds.js'; +import { TableDS } from '../shared/data-structures/table.ds.js'; +import { TestConnectionResultDS } from '../shared/data-structures/test-result-connection.ds.js'; +import { ValidateTableSettingsDS } from '../shared/data-structures/validate-table-settings.ds.js'; +import { FilterCriteriaEnum } from '../shared/enums/filter-criteria.enum.js'; +import { IDataAccessObject } from '../shared/interfaces/data-access-object.interface.js'; +import { BasicDataAccessObject } from './basic-data-access-object.js'; +import { NodeClickHouseClientConfigOptions } from '@clickhouse/client/dist/config.js'; + +export class DataAccessObjectClickHouse extends BasicDataAccessObject implements IDataAccessObject { + constructor(connection: ConnectionParams) { + super(connection); + } + + public async addRowInTable(tableName: string, row: Record): Promise> { + const client = this.getClickHouseClient(); + try { + await client.insert({ + table: tableName, + values: [row], + format: 'JSONEachRow', + }); + return row; + } finally { + await client.close(); + } + } + + public async deleteRowInTable( + tableName: string, + primaryKey: Record, + ): Promise> { + const client = this.getClickHouseClient(); + try { + const whereClause = this.buildWhereClause(primaryKey); + const query = `ALTER TABLE ${this.escapeIdentifier(tableName)} DELETE WHERE ${whereClause}`; + await client.command({ query }); + return primaryKey; + } finally { + await client.close(); + } + } + + public async getIdentityColumns( + tableName: string, + referencedFieldName: string, + identityColumnName: string, + fieldValues: Array, + ): Promise>> { + if (!referencedFieldName || !fieldValues.length) { + return []; + } + const client = this.getClickHouseClient(); + try { + const columnsToSelect = identityColumnName + ? `${this.escapeIdentifier(referencedFieldName)}, ${this.escapeIdentifier(identityColumnName)}` + : this.escapeIdentifier(referencedFieldName); + + const placeholders = fieldValues.map((v) => this.sanitizeValue(v)).join(', '); + + const query = `SELECT ${columnsToSelect} FROM ${this.escapeIdentifier(tableName)} WHERE ${this.escapeIdentifier(referencedFieldName)} IN (${placeholders})`; + + const result = await client.query({ + query, + format: 'JSONEachRow', + }); + + return await result.json(); + } finally { + await client.close(); + } + } + + public async getRowByPrimaryKey( + tableName: string, + primaryKey: Record, + settings: TableSettingsDS, + ): Promise> { + const client = this.getClickHouseClient(); + try { + let availableFields: string[] = []; + if (settings) { + const tableStructure = await this.getTableStructure(tableName); + availableFields = this.findAvailableFields(settings, tableStructure); + } + + const selectFields = + availableFields.length > 0 ? availableFields.map((f) => this.escapeIdentifier(f)).join(', ') : '*'; + const whereClause = this.buildWhereClause(primaryKey); + + const query = `SELECT ${selectFields} FROM ${this.escapeIdentifier(tableName)} WHERE ${whereClause} LIMIT 1`; + + const result = await client.query({ + query, + format: 'JSONEachRow', + }); + + const rows: Record[] = await result.json(); + return rows[0] || null; + } finally { + await client.close(); + } + } + + public async bulkGetRowsFromTableByPrimaryKeys( + tableName: string, + primaryKeys: Array>, + settings: TableSettingsDS, + ): Promise>> { + if (primaryKeys.length === 0) { + return []; + } + + const client = this.getClickHouseClient(); + try { + let availableFields: string[] = []; + if (settings) { + const tableStructure = await this.getTableStructure(tableName); + availableFields = this.findAvailableFields(settings, tableStructure); + } + + const selectFields = + availableFields.length > 0 ? availableFields.map((f) => this.escapeIdentifier(f)).join(', ') : '*'; + + const whereConditions = primaryKeys.map((pk) => `(${this.buildWhereClause(pk)})`).join(' OR '); + + const query = `SELECT ${selectFields} FROM ${this.escapeIdentifier(tableName)} WHERE ${whereConditions}`; + + const result = await client.query({ + query, + format: 'JSONEachRow', + }); + + return await result.json(); + } finally { + await client.close(); + } + } + + public async getRowsFromTable( + tableName: string, + settings: TableSettingsDS, + page: number, + perPage: number, + searchedFieldValue: string, + filteringFields: Array, + autocompleteFields: AutocompleteFieldsDS, + tableStructure: TableStructureDS[] | null, + ): Promise { + page = page > 0 ? page : DAO_CONSTANTS.DEFAULT_PAGINATION.page; + perPage = + perPage > 0 + ? perPage + : settings.list_per_page > 0 + ? settings.list_per_page + : DAO_CONSTANTS.DEFAULT_PAGINATION.perPage; + + const offset = (page - 1) * perPage; + const client = this.getClickHouseClient(); + + try { + if (!tableStructure) { + tableStructure = await this.getTableStructure(tableName); + } + const availableFields = this.findAvailableFields(settings, tableStructure); + + if (autocompleteFields?.value && autocompleteFields.fields?.length > 0) { + const { fields, value } = autocompleteFields; + const selectFields = fields.map((f) => this.escapeIdentifier(f)).join(', '); + + let whereClause = '1=1'; + if (value !== '*') { + const conditions = fields.map( + (field) => `toString(${this.escapeIdentifier(field)}) LIKE '${this.escapeValue(String(value))}%'`, + ); + whereClause = conditions.join(' OR '); + } + + const query = `SELECT ${selectFields} FROM ${this.escapeIdentifier(tableName)} WHERE ${whereClause} LIMIT ${DAO_CONSTANTS.AUTOCOMPLETE_ROW_LIMIT}`; + + const result = await client.query({ + query, + format: 'JSONEachRow', + }); + + const rows: Record[] = await result.json(); + const { large_dataset } = await this.getRowsCount(client, tableName); + + return { + data: rows, + pagination: {} as any, + large_dataset, + }; + } + + const selectFields = + availableFields.length > 0 ? availableFields.map((f) => this.escapeIdentifier(f)).join(', ') : '*'; + const whereClauses: string[] = []; + + let { search_fields } = settings; + if ((!search_fields || search_fields.length === 0) && searchedFieldValue) { + search_fields = availableFields; + } + + if (searchedFieldValue && search_fields?.length > 0) { + const searchConditions = search_fields.map( + (field) => + `lower(toString(${this.escapeIdentifier(field)})) LIKE '%${this.escapeValue(searchedFieldValue.toLowerCase())}%'`, + ); + whereClauses.push(`(${searchConditions.join(' OR ')})`); + } + + if (filteringFields?.length > 0) { + for (const filterObject of filteringFields) { + const { field, criteria, value } = filterObject; + const escapedField = this.escapeIdentifier(field); + const escapedValue = this.sanitizeValue(value); + + const escapedStringValue = this.escapeValue(String(value)); + switch (criteria) { + case FilterCriteriaEnum.eq: + whereClauses.push(`${escapedField} = ${escapedValue}`); + break; + case FilterCriteriaEnum.startswith: + whereClauses.push(`toString(${escapedField}) LIKE '${escapedStringValue}%'`); + break; + case FilterCriteriaEnum.endswith: + whereClauses.push(`toString(${escapedField}) LIKE '%${escapedStringValue}'`); + break; + case FilterCriteriaEnum.gt: + whereClauses.push(`${escapedField} > ${escapedValue}`); + break; + case FilterCriteriaEnum.lt: + whereClauses.push(`${escapedField} < ${escapedValue}`); + break; + case FilterCriteriaEnum.gte: + whereClauses.push(`${escapedField} >= ${escapedValue}`); + break; + case FilterCriteriaEnum.lte: + whereClauses.push(`${escapedField} <= ${escapedValue}`); + break; + case FilterCriteriaEnum.contains: + whereClauses.push(`toString(${escapedField}) LIKE '%${escapedStringValue}%'`); + break; + case FilterCriteriaEnum.icontains: + whereClauses.push(`toString(${escapedField}) NOT LIKE '%${escapedStringValue}%'`); + break; + case FilterCriteriaEnum.empty: + whereClauses.push(`${escapedField} IS NULL`); + break; + } + } + } + + const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(' AND ')}` : ''; + const orderByClause = + settings.ordering_field && settings.ordering + ? `ORDER BY ${this.escapeIdentifier(settings.ordering_field)} ${settings.ordering}` + : ''; + + const query = `SELECT ${selectFields} FROM ${this.escapeIdentifier(tableName)} ${whereClause} ${orderByClause} LIMIT ${perPage} OFFSET ${offset}`; + + const result = await client.query({ + query, + format: 'JSONEachRow', + }); + + const rows: Record[] = await result.json(); + + const countQuery = `SELECT count(*) as count FROM ${this.escapeIdentifier(tableName)} ${whereClause}`; + const countResult = await client.query({ + query: countQuery, + format: 'JSONEachRow', + }); + const countData: Array<{ count: number }> = await countResult.json(); + const rowsCount = Number(countData[0]?.count || 0); + + const pagination = { + total: rowsCount, + lastPage: Math.ceil(rowsCount / perPage), + perPage, + currentPage: page, + }; + + return { + data: rows, + pagination, + large_dataset: rowsCount >= DAO_CONSTANTS.LARGE_DATASET_ROW_LIMIT, + }; + } finally { + await client.close(); + } + } + + public async getTableForeignKeys(_tableName: string): Promise> { + return []; + } + + public async getTablePrimaryColumns(tableName: string): Promise> { + const cachedPrimaryColumns = LRUStorage.getTablePrimaryKeysCache(this.connection, tableName); + if (cachedPrimaryColumns) { + return cachedPrimaryColumns; + } + + const client = this.getClickHouseClient(); + try { + const query = ` + SELECT name, type + FROM system.columns + WHERE database = '${this.escapeValue(this.connection.database || 'default')}' + AND table = '${this.escapeValue(tableName)}' + AND is_in_primary_key = 1 + ORDER BY position + `; + + const result = await client.query({ + query, + format: 'JSONEachRow', + }); + + const columns: Array<{ name: string; type: string }> = await result.json(); + + const resultKeys = columns.map((column) => ({ + column_name: column.name, + data_type: this.mapClickHouseType(column.type), + })); + + LRUStorage.setTablePrimaryKeysCache(this.connection, tableName, resultKeys); + return resultKeys; + } finally { + await client.close(); + } + } + + public async getTablesFromDB(): Promise> { + const client = this.getClickHouseClient(); + try { + const database = this.connection.database || 'default'; + const query = ` + SELECT name, engine + FROM system.tables + WHERE database = '${this.escapeValue(database)}' + AND name NOT LIKE '.%' + ORDER BY name + `; + + const result = await client.query({ + query, + format: 'JSONEachRow', + }); + + const tables: Array<{ name: string; engine: string }> = await result.json(); + + return tables.map((table) => ({ + tableName: table.name, + isView: table.engine === 'View' || table.engine === 'MaterializedView', + })); + } finally { + await client.close(); + } + } + + public async getTableStructure(tableName: string): Promise> { + const cachedTableStructure = LRUStorage.getTableStructureCache(this.connection, tableName); + if (cachedTableStructure) { + return cachedTableStructure; + } + + const client = this.getClickHouseClient(); + try { + const database = this.connection.database || 'default'; + const query = ` + SELECT + name, + type, + default_kind, + default_expression, + is_in_primary_key + FROM system.columns + WHERE database = '${this.escapeValue(database)}' + AND table = '${this.escapeValue(tableName)}' + ORDER BY position + `; + + const result = await client.query({ + query, + format: 'JSONEachRow', + }); + + const columns: Array<{ + name: string; + type: string; + default_kind: string; + default_expression: string; + is_in_primary_key: number; + }> = await result.json(); + + const structure: TableStructureDS[] = columns.map((column) => ({ + column_name: column.name, + data_type: this.mapClickHouseType(column.type), + column_default: column.default_expression || null, + allow_null: this.isNullableType(column.type), + character_maximum_length: this.extractLength(column.type), + data_type_params: this.extractTypeParams(column.type), + udt_name: column.type, + extra: column.is_in_primary_key ? 'primary_key' : undefined, + })); + + LRUStorage.setTableStructureCache(this.connection, tableName, structure); + return structure; + } finally { + await client.close(); + } + } + + public async testConnect(): Promise { + const client = this.getClickHouseClient(); + try { + const result = await client.query({ + query: 'SELECT 1', + format: 'JSONEachRow', + }); + await result.json(); + + return { + result: true, + message: 'Successfully connected', + }; + } catch (e) { + return { + result: false, + message: e.message || 'Connection failed', + }; + } finally { + await client.close(); + } + } + + public async updateRowInTable( + tableName: string, + row: Record, + primaryKey: Record, + ): Promise> { + const client = this.getClickHouseClient(); + try { + const setClause = Object.entries(row) + .map(([key, value]) => { + const sanitizedValue = this.sanitizeValue(value); + return `${this.escapeIdentifier(key)} = ${sanitizedValue}`; + }) + .join(', '); + + const whereClause = this.buildWhereClause(primaryKey); + + const query = `ALTER TABLE ${this.escapeIdentifier(tableName)} UPDATE ${setClause} WHERE ${whereClause}`; + await client.command({ query }); + + return primaryKey; + } finally { + await client.close(); + } + } + + public async bulkUpdateRowsInTable( + tableName: string, + newValues: Record, + primaryKeys: Array>, + ): Promise>> { + if (primaryKeys.length === 0) { + return []; + } + + const client = this.getClickHouseClient(); + try { + const setClause = Object.entries(newValues) + .map(([key, value]) => { + const sanitizedValue = this.sanitizeValue(value); + return `${this.escapeIdentifier(key)} = ${sanitizedValue}`; + }) + .join(', '); + + const whereConditions = primaryKeys.map((pk) => `(${this.buildWhereClause(pk)})`).join(' OR '); + + const query = `ALTER TABLE ${this.escapeIdentifier(tableName)} UPDATE ${setClause} WHERE ${whereConditions}`; + await client.command({ query }); + + return primaryKeys; + } finally { + await client.close(); + } + } + + public async bulkDeleteRowsInTable(tableName: string, primaryKeys: Array>): Promise { + if (primaryKeys.length === 0) { + return 0; + } + + const client = this.getClickHouseClient(); + try { + const whereConditions = primaryKeys.map((pk) => `(${this.buildWhereClause(pk)})`).join(' OR '); + + const query = `ALTER TABLE ${this.escapeIdentifier(tableName)} DELETE WHERE ${whereConditions}`; + await client.command({ query }); + + return primaryKeys.length; + } finally { + await client.close(); + } + } + + public async validateSettings(settings: ValidateTableSettingsDS, tableName: string): Promise> { + const [tableStructure, primaryColumns] = await Promise.all([ + this.getTableStructure(tableName), + this.getTablePrimaryColumns(tableName), + ]); + return tableSettingsFieldValidator(tableStructure, primaryColumns, settings); + } + + public async getReferencedTableNamesAndColumns(_tableName: string): Promise> { + return []; + } + + public async isView(tableName: string): Promise { + const client = this.getClickHouseClient(); + try { + const database = this.connection.database || 'default'; + const query = ` + SELECT engine + FROM system.tables + WHERE database = '${this.escapeValue(database)}' + AND name = '${this.escapeValue(tableName)}' + `; + + const result = await client.query({ + query, + format: 'JSONEachRow', + }); + + const tables: Array<{ engine: string }> = await result.json(); + + if (tables.length === 0) { + throw new Error(ERROR_MESSAGES.TABLE_NOT_FOUND(tableName)); + } + + return tables[0].engine === 'View' || tables[0].engine === 'MaterializedView'; + } finally { + await client.close(); + } + } + + public async getTableRowsStream( + tableName: string, + settings: TableSettingsDS, + page: number, + perPage: number, + searchedFieldValue: string, + filteringFields: Array, + ): Promise> { + const result = await this.getRowsFromTable( + tableName, + settings, + page, + perPage, + searchedFieldValue, + filteringFields, + null, + null, + ); + + const stream = new Readable({ + objectMode: true, + read() { + for (const row of result.data) { + this.push(row); + } + this.push(null); + }, + }); + + return stream as Stream & AsyncIterable; + } + + public async importCSVInTable(file: Express.Multer.File, tableName: string): Promise { + const client = this.getClickHouseClient(); + try { + const stream = new Readable(); + stream.push(file.buffer); + stream.push(null); + + const parser = stream.pipe(csv.parse({ columns: true })); + + const rows: any[] = []; + for await (const record of parser) { + rows.push(record); + } + + if (rows.length > 0) { + await client.insert({ + table: tableName, + values: rows, + format: 'JSONEachRow', + }); + } + } finally { + await client.close(); + } + } + + public async executeRawQuery(query: string, _tableName?: string): Promise>> { + const client = this.getClickHouseClient(); + try { + const result = await client.query({ + query, + format: 'JSONEachRow', + }); + + return await result.json(); + } finally { + await client.close(); + } + } + + private escapeIdentifier(identifier: string): string { + this.validateIdentifier(identifier); + return `\`${identifier.replace(/`/g, '``')}\``; + } + + private validateIdentifier(identifier: string): void { + if (!identifier || typeof identifier !== 'string') { + throw new Error('Invalid identifier: must be a non-empty string'); + } + + if (identifier.length > 256) { + throw new Error('Invalid identifier: exceeds maximum length'); + } + + if (/[\x00-\x1f\x7f]/.test(identifier)) { + throw new Error('Invalid identifier: contains control characters'); + } + } + + private escapeValue(value: string): string { + if (typeof value !== 'string') { + throw new Error('escapeValue expects a string'); + } + return value + .replace(/\\/g, '\\\\') + .replace(/'/g, "''") + .replace(/\0/g, '\\0') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/\x1a/g, '\\Z'); + } + + private sanitizeValue(value: unknown): string { + if (value === null || value === undefined) { + return 'NULL'; + } + if (typeof value === 'number') { + if (!Number.isFinite(value)) { + throw new Error('Invalid numeric value: must be finite'); + } + return String(value); + } + if (typeof value === 'boolean') { + return value ? '1' : '0'; + } + if (typeof value === 'string') { + return `'${this.escapeValue(value)}'`; + } + if (value instanceof Date) { + return `'${this.escapeValue(value.toISOString())}'`; + } + + if (typeof value === 'object') { + return `'${this.escapeValue(JSON.stringify(value))}'`; + } + throw new Error(`Unsupported value type: ${typeof value}`); + } + + private buildWhereClause(conditions: Record): string { + if (!conditions || typeof conditions !== 'object') { + throw new Error('Invalid conditions: must be an object'); + } + const entries = Object.entries(conditions); + if (entries.length === 0) { + throw new Error('Invalid conditions: must have at least one condition'); + } + return entries + .map(([key, value]) => { + const sanitizedValue = this.sanitizeValue(value); + return `${this.escapeIdentifier(key)} = ${sanitizedValue}`; + }) + .join(' AND '); + } + + private mapClickHouseType(clickHouseType: string): string { + const lowerType = clickHouseType.toLowerCase(); + + if (lowerType.startsWith('nullable(')) { + return this.mapClickHouseType(clickHouseType.slice(9, -1)); + } + + if (lowerType.startsWith('array(')) { + return 'array'; + } + + if (lowerType.startsWith('map(')) { + return 'object'; + } + + if (lowerType.startsWith('tuple(')) { + return 'object'; + } + + if (lowerType.startsWith('enum')) { + return 'enum'; + } + + if (lowerType.startsWith('fixedstring')) { + return 'string'; + } + + if (lowerType.startsWith('decimal')) { + return 'decimal'; + } + + const typeMapping: Record = { + string: 'string', + uuid: 'uuid', + bool: 'boolean', + boolean: 'boolean', + int8: 'integer', + int16: 'integer', + int32: 'integer', + int64: 'bigint', + int128: 'bigint', + int256: 'bigint', + uint8: 'integer', + uint16: 'integer', + uint32: 'integer', + uint64: 'bigint', + uint128: 'bigint', + uint256: 'bigint', + float32: 'float', + float64: 'double', + date: 'date', + date32: 'date', + datetime: 'datetime', + datetime64: 'datetime', + ipv4: 'string', + ipv6: 'string', + json: 'json', + object: 'object', + }; + + return typeMapping[lowerType] || 'string'; + } + + private isNullableType(type: string): boolean { + return type.toLowerCase().startsWith('nullable('); + } + + private extractLength(type: string): number | null { + const fixedStringMatch = type.match(/FixedString\((\d+)\)/i); + if (fixedStringMatch) { + return parseInt(fixedStringMatch[1], 10); + } + return null; + } + + private extractTypeParams(type: string): string | null { + const enumMatch = type.match(/Enum\d*\(([^)]+)\)/i); + if (enumMatch) { + return enumMatch[1]; + } + return null; + } + + private async getRowsCount( + client: ClickHouseClient, + tableName: string, + ): Promise<{ rowsCount: number; large_dataset: boolean }> { + const query = `SELECT count(*) as count FROM ${this.escapeIdentifier(tableName)}`; + const result = await client.query({ + query, + format: 'JSONEachRow', + }); + const data: Array<{ count: number }> = await result.json(); + const count = Number(data[0]?.count || 0); + + return { + rowsCount: count, + large_dataset: count >= DAO_CONSTANTS.LARGE_DATASET_ROW_LIMIT, + }; + } + + private getClickHouseClient(): ClickHouseClient { + const { host, port, username, password, database, ssl, cert } = this.connection; + const protocol = ssl ? 'https' : 'http'; + const url = `${protocol}://${host}:${port}`; + + const clientConfig: NodeClickHouseClientConfigOptions = { + url, + username, + password, + database: database || 'default', + }; + + if (ssl && cert) { + clientConfig.tls = { + ca_cert: Buffer.from(cert), + }; + } + + return createClient(clientConfig); + } +} diff --git a/shared-code/src/data-access-layer/data-access-objects/data-access-object-mssql.ts b/shared-code/src/data-access-layer/data-access-objects/data-access-object-mssql.ts index db5b01e7..f3d2c8a4 100644 --- a/shared-code/src/data-access-layer/data-access-objects/data-access-object-mssql.ts +++ b/shared-code/src/data-access-layer/data-access-objects/data-access-object-mssql.ts @@ -385,7 +385,7 @@ WHERE TABLE_TYPE = 'VIEW' } const knex = await this.configureKnex(); try { - await knex().select(1); + await knex.queryBuilder().select(1); return { result: true, message: 'Successfully connected', @@ -472,18 +472,18 @@ WHERE TABLE_TYPE = 'VIEW' knex .raw( ` - SELECT + SELECT OBJECT_NAME(f.parent_object_id) "table_name", COL_NAME(fc.parent_object_id,fc.parent_column_id) "column_name" - FROM + FROM sys.foreign_keys AS f - INNER JOIN - sys.foreign_key_columns AS fc + INNER JOIN + sys.foreign_key_columns AS fc ON f.OBJECT_ID = fc.constraint_object_id - INNER JOIN - sys.tables t + INNER JOIN + sys.tables t ON t.OBJECT_ID = fc.referenced_object_id - WHERE + WHERE OBJECT_NAME (f.referenced_object_id) = ? `, tableName, diff --git a/shared-code/src/data-access-layer/data-access-objects/data-access-object-mysql.ts b/shared-code/src/data-access-layer/data-access-objects/data-access-object-mysql.ts index caa1d880..95594eff 100644 --- a/shared-code/src/data-access-layer/data-access-objects/data-access-object-mysql.ts +++ b/shared-code/src/data-access-layer/data-access-objects/data-access-object-mysql.ts @@ -431,7 +431,7 @@ export class DataAccessObjectMysql extends BasicDataAccessObject implements IDat } const knex = await this.configureKnex(); try { - await knex().select(1); + await knex.queryBuilder().select(1); return { result: true, message: 'Successfully connected', diff --git a/shared-code/src/data-access-layer/data-access-objects/data-access-object-oracle.ts b/shared-code/src/data-access-layer/data-access-objects/data-access-object-oracle.ts index 6efc518d..8324357b 100644 --- a/shared-code/src/data-access-layer/data-access-objects/data-access-object-oracle.ts +++ b/shared-code/src/data-access-layer/data-access-objects/data-access-object-oracle.ts @@ -494,7 +494,7 @@ export class DataAccessObjectOracle extends BasicDataAccessObject implements IDa } const knex = await this.configureKnex(); const schema = this.connection.schema ?? this.connection.username.toUpperCase(); - const structureColumns = await knex() + const structureColumns = await knex.queryBuilder() .select('COLUMN_NAME', 'DATA_DEFAULT', 'DATA_TYPE', 'NULLABLE', 'DATA_LENGTH') .from('ALL_TAB_COLUMNS') .orderBy('COLUMN_ID') diff --git a/shared-code/src/data-access-layer/data-access-objects/data-access-object-postgres.ts b/shared-code/src/data-access-layer/data-access-objects/data-access-object-postgres.ts index 67cc5bee..1787a4e5 100644 --- a/shared-code/src/data-access-layer/data-access-objects/data-access-object-postgres.ts +++ b/shared-code/src/data-access-layer/data-access-objects/data-access-object-postgres.ts @@ -342,7 +342,6 @@ export class DataAccessObjectPostgres extends BasicDataAccessObject implements I const bindings = [schema]; try { const results = await knex.raw(query, bindings); - console.log({ tablesPg: results }); return results.rows.map((row: Record) => ({ tableName: row.table_name, isView: !!row.is_view })); } catch (error) { console.log({ tablesPg: error }); @@ -434,7 +433,7 @@ export class DataAccessObjectPostgres extends BasicDataAccessObject implements I } const knex = await this.configureKnex(); try { - await knex().select(1); + await knex.queryBuilder().select(1); return { result: true, message: 'Successfully connected', diff --git a/shared-code/src/data-access-layer/shared/create-data-access-object.ts b/shared-code/src/data-access-layer/shared/create-data-access-object.ts index f9814576..eccd7409 100644 --- a/shared-code/src/data-access-layer/shared/create-data-access-object.ts +++ b/shared-code/src/data-access-layer/shared/create-data-access-object.ts @@ -1,6 +1,7 @@ import { ERROR_MESSAGES } from '../../helpers/errors/error-messages.js'; import { DataAccessObjectAgent } from '../data-access-objects/data-access-object-agent.js'; import { DataAccessObjectCassandra } from '../data-access-objects/data-access-object-cassandra.js'; +import { DataAccessObjectClickHouse } from '../data-access-objects/data-access-object-clickhouse.js'; import { DataAccessObjectDynamoDB } from '../data-access-objects/data-access-object-dynamodb.js'; import { DataAccessObjectElasticsearch } from '../data-access-objects/data-access-object-elasticsearch.js'; import { DataAccessObjectIbmDb2 } from '../data-access-objects/data-access-object-ibmdb2.js'; @@ -30,6 +31,7 @@ export function getDataAccessObject( ConnectionTypesEnum.agent_mongodb, ConnectionTypesEnum.agent_cassandra, ConnectionTypesEnum.agent_redis, + ConnectionTypesEnum.agent_clickhouse, ]; if (!connectionParams || connectionParams === null) { throw new Error(ERROR_MESSAGES.CONNECTION_PARAMS_SHOULD_BE_DEFINED); @@ -68,6 +70,9 @@ export function getDataAccessObject( case ConnectionTypesEnum.redis: const connectionParamsRedis = buildConnectionParams(connectionParams); return new DataAccessObjectRedis(connectionParamsRedis); + case ConnectionTypesEnum.clickhouse: + const connectionParamsClickHouse = buildConnectionParams(connectionParams); + return new DataAccessObjectClickHouse(connectionParamsClickHouse); default: if (!agentTypes.includes(connectionParams.type)) { throw new Error(ERROR_MESSAGES.CONNECTION_TYPE_INVALID); @@ -107,6 +112,7 @@ function buildConnectionParams(connectionParams: IUnknownConnectionParams): Conn case ConnectionTypesEnum.ibmdb2: case ConnectionTypesEnum.mongodb: case ConnectionTypesEnum.cassandra: + case ConnectionTypesEnum.clickhouse: requiredKeys.push(...sqlAndMongoRequiredKeys); break; case ConnectionTypesEnum.dynamodb: diff --git a/shared-code/src/data-access-layer/shared/data-structures/connections-params.ds.ts b/shared-code/src/data-access-layer/shared/data-structures/connections-params.ds.ts index 3a3c7617..074b21a6 100644 --- a/shared-code/src/data-access-layer/shared/data-structures/connections-params.ds.ts +++ b/shared-code/src/data-access-layer/shared/data-structures/connections-params.ds.ts @@ -13,7 +13,8 @@ export class ConnectionParams { | 'dynamodb' | 'elasticsearch' | 'cassandra' - | 'redis'; + | 'redis' + | 'clickhouse'; host: string; diff --git a/shared-code/src/data-access-layer/shared/enums/connection-types-enum.ts b/shared-code/src/data-access-layer/shared/enums/connection-types-enum.ts index ed9bed04..1ee1bc90 100644 --- a/shared-code/src/data-access-layer/shared/enums/connection-types-enum.ts +++ b/shared-code/src/data-access-layer/shared/enums/connection-types-enum.ts @@ -10,6 +10,7 @@ export enum ConnectionTypesEnum { elasticsearch = 'elasticsearch', cassandra = 'cassandra', redis = 'redis', + clickhouse = 'clickhouse', agent_postgres = 'agent_postgres', agent_mysql = 'agent_mysql', agent_oracledb = 'agent_oracledb', @@ -18,4 +19,5 @@ export enum ConnectionTypesEnum { agent_mongodb = 'agent_mongodb', agent_cassandra = 'agent_cassandra', agent_redis = 'agent_redis', + agent_clickhouse = 'agent_clickhouse', } diff --git a/yarn.lock b/yarn.lock index 27bb7a63..8049da38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1373,6 +1373,22 @@ __metadata: languageName: node linkType: hard +"@clickhouse/client-common@npm:1.14.0": + version: 1.14.0 + resolution: "@clickhouse/client-common@npm:1.14.0" + checksum: c9618b33551033dfd9d3d29fe0dfdac8badbffc921a5cad6c430ba814469b59f0c7c819547967f75bf0779e1af728555572bf2f5053dd503ea2003230703d1fa + languageName: node + linkType: hard + +"@clickhouse/client@npm:^1.14.0": + version: 1.14.0 + resolution: "@clickhouse/client@npm:1.14.0" + dependencies: + "@clickhouse/client-common": 1.14.0 + checksum: 529616a1b4b50f8df7be5f12e0847bee610a125fe2e7faeaa7a3a16016e0b8e44abead4bff0b70ef6f434a43531b70db7e7d5943ee55cce49d8576cbc0cce493 + languageName: node + linkType: hard + "@colors/colors@npm:1.5.0": version: 1.5.0 resolution: "@colors/colors@npm:1.5.0" @@ -3469,6 +3485,7 @@ __metadata: dependencies: "@aws-sdk/client-dynamodb": ^3.918.0 "@aws-sdk/lib-dynamodb": ^3.918.0 + "@clickhouse/client": ^1.14.0 "@elastic/elasticsearch": 8.18.1 "@types/multer": ^2.0.0 "@types/oracledb": ^6.10.0