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 9192819c9..9efd098f0 100644 --- a/backend/src/entities/agent/repository/custom-agent-repository-extension.ts +++ b/backend/src/entities/agent/repository/custom-agent-repository-extension.ts @@ -61,6 +61,8 @@ export const customAgentRepositoryExtension = { return 'MONGODB-TEST-AGENT-TOKEN'; case ConnectionTypeTestEnum.elasticsearch: return 'ELASTICSEARCH-TEST-AGENT-TOKEN'; + case ConnectionTypeTestEnum.agent_cassandra: + return 'CASSANDRA-TEST-AGENT-TOKEN'; } }, }; diff --git a/backend/src/entities/connection/application/data-structures/create-connection.ds.ts b/backend/src/entities/connection/application/data-structures/create-connection.ds.ts index 3c2c7511e..5f3e65c4d 100644 --- a/backend/src/entities/connection/application/data-structures/create-connection.ds.ts +++ b/backend/src/entities/connection/application/data-structures/create-connection.ds.ts @@ -21,6 +21,7 @@ export class CreateConnectionDs { cert: string; azure_encryption: boolean; authSource: string; + dataCenter: string; }; creation_info: { authorId: string; diff --git a/backend/src/entities/connection/application/data-structures/found-connections.ds.ts b/backend/src/entities/connection/application/data-structures/found-connections.ds.ts index c0ab7a302..6bd95ee96 100644 --- a/backend/src/entities/connection/application/data-structures/found-connections.ds.ts +++ b/backend/src/entities/connection/application/data-structures/found-connections.ds.ts @@ -72,6 +72,9 @@ export class FoundDirectConnectionsDs { @ApiProperty({ required: false }) authSource?: string; + @ApiProperty({ required: false }) + dataCenter?: string; + @ApiProperty() isTestConnection: boolean; diff --git a/backend/src/entities/connection/application/data-structures/update-connection.ds.ts b/backend/src/entities/connection/application/data-structures/update-connection.ds.ts index 708dd121e..1c11182ab 100644 --- a/backend/src/entities/connection/application/data-structures/update-connection.ds.ts +++ b/backend/src/entities/connection/application/data-structures/update-connection.ds.ts @@ -21,6 +21,7 @@ export class UpdateConnectionDs { cert: string; azure_encryption: boolean; authSource: string; + dataCenter: string; }; update_info: { authorId: string; diff --git a/backend/src/entities/connection/application/dto/create-connection.dto.ts b/backend/src/entities/connection/application/dto/create-connection.dto.ts index be98dc2ad..dcda5a68d 100644 --- a/backend/src/entities/connection/application/dto/create-connection.dto.ts +++ b/backend/src/entities/connection/application/dto/create-connection.dto.ts @@ -107,4 +107,9 @@ export class CreateConnectionDto { @IsString() @ApiProperty({ required: false }) authSource?: string; + + @IsOptional() + @IsString() + @ApiProperty({ required: false }) + dataCenter?: string; } diff --git a/backend/src/entities/connection/application/dto/created-connection.dto.ts b/backend/src/entities/connection/application/dto/created-connection.dto.ts index bcdebcee0..c0dda9e51 100644 --- a/backend/src/entities/connection/application/dto/created-connection.dto.ts +++ b/backend/src/entities/connection/application/dto/created-connection.dto.ts @@ -89,6 +89,9 @@ export class CreatedConnectionDTO { @ApiProperty() authSource: string; + @ApiProperty() + dataCenter: string | null; + @ApiProperty() master_hash: string; diff --git a/backend/src/entities/connection/connection.controller.ts b/backend/src/entities/connection/connection.controller.ts index 27058e46b..e3f8b40dd 100644 --- a/backend/src/entities/connection/connection.controller.ts +++ b/backend/src/entities/connection/connection.controller.ts @@ -272,6 +272,7 @@ export class ConnectionController { type: createConnectionDto.type, username: createConnectionDto.username, authSource: createConnectionDto.authSource, + dataCenter: createConnectionDto.dataCenter, }, creation_info: { authorId: userId, @@ -324,6 +325,7 @@ export class ConnectionController { type: updateConnectionDto.type, username: updateConnectionDto.username, authSource: updateConnectionDto.authSource, + dataCenter: updateConnectionDto.dataCenter, }, update_info: { authorId: userId, @@ -531,6 +533,7 @@ export class ConnectionController { type: testConnectionData.type, username: testConnectionData.username, authSource: testConnectionData.authSource, + dataCenter: testConnectionData.dataCenter, }, update_info: { authorId: userId, @@ -618,6 +621,7 @@ export class ConnectionController { cert: restoreConnectionData.cert, azure_encryption: restoreConnectionData.azure_encryption, authSource: restoreConnectionData.authSource, + dataCenter: restoreConnectionData.dataCenter, }, update_info: { connectionId: connectionId, diff --git a/backend/src/entities/connection/connection.entity.ts b/backend/src/entities/connection/connection.entity.ts index a235b1f1c..5f289f4b5 100644 --- a/backend/src/entities/connection/connection.entity.ts +++ b/backend/src/entities/connection/connection.entity.ts @@ -108,6 +108,9 @@ export class ConnectionEntity { @Column({ default: null }) authSource?: string | null; + @Column({ default: null }) + dataCenter?: string | null; + @Column({ default: null }) master_hash?: string | null; diff --git a/backend/src/entities/connection/utils/build-connection-entity.ts b/backend/src/entities/connection/utils/build-connection-entity.ts index 2675cb6cd..739b31e7c 100644 --- a/backend/src/entities/connection/utils/build-connection-entity.ts +++ b/backend/src/entities/connection/utils/build-connection-entity.ts @@ -30,6 +30,7 @@ export async function buildConnectionEntity( type, password, authSource, + dataCenter, }, creation_info: { masterPwd }, } = createConnectionData; @@ -54,6 +55,7 @@ export async function buildConnectionEntity( connection.cert = cert; connection.schema = schema; connection.authSource = authSource; + connection.dataCenter = dataCenter; if (connection.masterEncryption && masterPwd && !isConnectionTypeAgent(connection.type)) { connection = Encryptor.encryptConnectionCredentials(connection, masterPwd); diff --git a/backend/src/entities/connection/utils/build-created-connection.ds.ts b/backend/src/entities/connection/utils/build-created-connection.ds.ts index 8bd234ba9..a353702c2 100644 --- a/backend/src/entities/connection/utils/build-created-connection.ds.ts +++ b/backend/src/entities/connection/utils/build-created-connection.ds.ts @@ -34,6 +34,7 @@ export function buildCreatedConnectionDs( updatedAt: connection.updatedAt, username: connection.username, authSource: connection.authSource, + dataCenter: connection.dataCenter, master_hash: connection.master_hash, isFrozen: connection.is_frozen, groups: connection.groups?.map((group) => { diff --git a/backend/src/entities/connection/utils/build-found-connection.ds.ts b/backend/src/entities/connection/utils/build-found-connection.ds.ts index 39196f640..6a0face13 100644 --- a/backend/src/entities/connection/utils/build-found-connection.ds.ts +++ b/backend/src/entities/connection/utils/build-found-connection.ds.ts @@ -35,5 +35,6 @@ export function buildFoundConnectionDs( connection_properties: connection.connection_properties, isFrozen: connection.is_frozen, authSource: connection.authSource ? connection.authSource : undefined, + dataCenter: connection.dataCenter ? connection.dataCenter : undefined, }; } diff --git a/backend/src/entities/table-settings/table-settings.controller.ts b/backend/src/entities/table-settings/table-settings.controller.ts index ba730919a..782cf1295 100644 --- a/backend/src/entities/table-settings/table-settings.controller.ts +++ b/backend/src/entities/table-settings/table-settings.controller.ts @@ -159,7 +159,7 @@ export class TableSettingsController { HttpStatus.BAD_REQUEST, ); } - return await this.createTableSettingsUseCase.execute(inputData, InTransactionEnum.ON); + return await this.createTableSettingsUseCase.execute(inputData, InTransactionEnum.OFF); } @ApiOperation({ summary: 'Update table settings' }) diff --git a/backend/src/entities/table/use-cases/get-table-rows.use.case.ts b/backend/src/entities/table/use-cases/get-table-rows.use.case.ts index a4b24dc18..8d378eee0 100644 --- a/backend/src/entities/table/use-cases/get-table-rows.use.case.ts +++ b/backend/src/entities/table/use-cases/get-table-rows.use.case.ts @@ -1,19 +1,32 @@ import { BadRequestException, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; +import { ForeignKeyWithAutocompleteColumnsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key-with-autocomplete-columns.ds.js'; +import { ForeignKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key.ds.js'; +import { TableSettingsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table-settings.ds.js'; +import { IDataAccessObjectAgent } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/interfaces/data-access-object-agent.interface.js'; +import { IDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/interfaces/data-access-object.interface.js'; +import { FoundRowsDS } from '@rocketadmin/shared-code/src/data-access-layer/shared/data-structures/found-rows.ds.js'; +import Sentry from '@sentry/minimal'; +import JSON5 from 'json5'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import { AmplitudeEventTypeEnum, LogOperationTypeEnum, OperationResultStatusEnum, WidgetTypeEnum, } from '../../../enums/index.js'; +import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; +import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; +import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { hexToBinary, isBinary } from '../../../helpers/binary-to-hex.js'; import { Constants } from '../../../helpers/constants/constants.js'; import { isConnectionTypeAgent, isObjectEmpty } from '../../../helpers/index.js'; import { AmplitudeService } from '../../amplitude/amplitude.service.js'; +import { buildActionEventDto } from '../../table-actions/table-action-rules-module/utils/build-found-action-event-dto.util.js'; +import { buildCreatedTableFilterRO } from '../../table-filters/utils/build-created-table-filters-response-object.util.js'; import { TableLogsService } from '../../table-logs/table-logs.service.js'; import { TableSettingsEntity } from '../../table-settings/table-settings.entity.js'; import { FoundTableRowsDs } from '../application/data-structures/found-table-rows.ds.js'; @@ -24,21 +37,9 @@ import { findFilteringFieldsUtil, parseFilteringFieldsFromBodyData } from '../ut import { findOrderingFieldUtil } from '../utils/find-ordering-field.util.js'; import { formFullTableStructure } from '../utils/form-full-table-structure.js'; import { isHexString } from '../utils/is-hex-string.js'; -import { IGetTableRows } from './table-use-cases.interface.js'; -import { IDataAccessObjectAgent } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/interfaces/data-access-object-agent.interface.js'; -import { IDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/interfaces/data-access-object.interface.js'; -import { ForeignKeyWithAutocompleteColumnsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key-with-autocomplete-columns.ds.js'; -import { ForeignKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key.ds.js'; -import { FoundRowsDS } from '@rocketadmin/shared-code/src/data-access-layer/shared/data-structures/found-rows.ds.js'; -import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; -import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; -import Sentry from '@sentry/minimal'; import { processRowsUtil } from '../utils/process-found-rows-util.js'; -import JSON5 from 'json5'; -import { buildActionEventDto } from '../../table-actions/table-action-rules-module/utils/build-found-action-event-dto.util.js'; -import { TableSettingsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table-settings.ds.js'; -import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; -import { buildCreatedTableFilterRO } from '../../table-filters/utils/build-created-table-filters-response-object.util.js'; +import { IGetTableRows } from './table-use-cases.interface.js'; +import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/enums/connection-types-enum.js'; @Injectable() export class GetTableRowsUseCase extends AbstractUseCase implements IGetTableRows { @@ -111,11 +112,6 @@ export class GetTableRowsUseCase extends AbstractUseCase isBinary(field.data_type)) || + connection.type === ConnectionTypesEnum.mongodb || + connection.type === ConnectionTypesEnum.agent_mongodb) + ) { searchingFieldValue = hexToBinary(searchingFieldValue) as any; tableSettings.search_fields = tableStructure .filter((field) => isBinary(field.data_type)) diff --git a/backend/src/enums/connection-type.enum.ts b/backend/src/enums/connection-type.enum.ts index a297d4352..2beb52d76 100644 --- a/backend/src/enums/connection-type.enum.ts +++ b/backend/src/enums/connection-type.enum.ts @@ -8,6 +8,7 @@ export enum ConnectionTypeTestEnum { mongodb = 'mongodb', dynamodb = 'dynamodb', elasticsearch = 'elasticsearch', + cassandra = 'cassandra', agent_postgres = 'agent_postgres', agent_mysql = 'agent_mysql', agent_oracledb = 'agent_oracledb', @@ -15,4 +16,5 @@ export enum ConnectionTypeTestEnum { agent_ibmdb2 = 'agent_ibmdb2', agent_mongodb = 'agent_mongodb', agent_elasticsearch = 'agent_elasticsearch', + agent_cassandra = 'agent_cassandra', } diff --git a/backend/src/helpers/cache/cacher.ts b/backend/src/helpers/cache/cacher.ts index 853989e59..9392340da 100644 --- a/backend/src/helpers/cache/cacher.ts +++ b/backend/src/helpers/cache/cacher.ts @@ -1,11 +1,11 @@ +import { ForeignKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key.ds.js'; +import { PrimaryKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/primary-key.ds.js'; +import { TableStructureDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table-structure.ds.js'; import { Knex } from 'knex'; import { LRUCache } from 'lru-cache'; import { ConnectionEntity } from '../../entities/connection/connection.entity.js'; -import { Constants } from '../constants/constants.js'; -import { TableStructureDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table-structure.ds.js'; -import { PrimaryKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/primary-key.ds.js'; -import { ForeignKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key.ds.js'; import { isSaaS } from '../app/is-saas.js'; +import { Constants } from '../constants/constants.js'; const knexCache = new LRUCache(Constants.DEFAULT_CONNECTION_CACHE_OPTIONS); const tunnelCache = new LRUCache(Constants.DEFAULT_TUNNEL_CACHE_OPTIONS); diff --git a/backend/src/helpers/is-connection-entity-agent.ts b/backend/src/helpers/is-connection-entity-agent.ts index 350317865..35d7e73c6 100644 --- a/backend/src/helpers/is-connection-entity-agent.ts +++ b/backend/src/helpers/is-connection-entity-agent.ts @@ -10,7 +10,8 @@ export function isConnectionEntityAgent(connection: ConnectionEntity | CreateCon ConnectionTypesEnum.agent_oracledb, ConnectionTypesEnum.agent_mssql, ConnectionTypesEnum.agent_ibmdb2, - ConnectionTypesEnum.agent_mongodb + ConnectionTypesEnum.agent_mongodb, + ConnectionTypeTestEnum.agent_cassandra, ]; return agentTypes.includes(connection.type as ConnectionTypesEnum); @@ -24,6 +25,7 @@ export function isConnectionTypeAgent(type: ConnectionTypesEnum | string): boole ConnectionTypeTestEnum.agent_mssql, ConnectionTypeTestEnum.agent_ibmdb2, ConnectionTypeTestEnum.agent_mongodb, + ConnectionTypeTestEnum.agent_cassandra, ]; return connectionTypes.includes(type as ConnectionTypeTestEnum); diff --git a/backend/src/migrations/1750931986538-AddDataCenterInConnectionEntity.ts b/backend/src/migrations/1750931986538-AddDataCenterInConnectionEntity.ts new file mode 100644 index 000000000..e07d372a2 --- /dev/null +++ b/backend/src/migrations/1750931986538-AddDataCenterInConnectionEntity.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddDataCenterInConnectionEntity1750931986538 implements MigrationInterface { + name = 'AddDataCenterInConnectionEntity1750931986538'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "connection" ADD "dataCenter" character varying`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "connection" DROP COLUMN "dataCenter"`); + } +} diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-cassandra.e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-cassandra.e2e.test.ts new file mode 100644 index 000000000..96d258370 --- /dev/null +++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-cassandra.e2e.test.ts @@ -0,0 +1,3805 @@ +/* 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 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 { setSaasEnvVariable } from '../../utils/set-saas-env-variable.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 () => { + setSaasEnvVariable(); + 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.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).cassandraTestConnection; + 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).cassandraTestConnection; + 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).cassandraTestConnection; + 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).cassandraTestConnection; + 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'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 200); + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + + 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'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 200); + 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, 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).cassandraTestConnection; + 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, 'text'); + + 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).cassandraTestConnection; + 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); + + 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, 'text'); + + 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).cassandraTestConnection; + 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, 'text'); + + 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).cassandraTestConnection; + 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, 'text'); + + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, insertedSearchedIds } = 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, + 'age', + 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'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsResponse.status, 200); + 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); + + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] >= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be >= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + const highestAgeRows = getTableRowsRO.rows.filter((row) => row.age >= 90); + t.true(highestAgeRows.length >= 2, 'Should have at least 2 rows with age >= 90'); + t.true( + highestAgeRows.every((row) => row.name === testSearchedUserName), + 'High age rows should have the test user name', + ); + + 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).cassandraTestConnection; + 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, + 'age', + 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); + + // Check that rows are sorted by age in ascending order + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] <= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be <= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + // Verify the youngest test user is near the beginning + const lowestAgeRow = getTableRowsRO.rows.find((row) => row.name === testSearchedUserName && row.age === 14); + t.truthy(lowestAgeRow, 'Should find the test user with age 14'); + + 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).cassandraTestConnection; + 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, + 'age', + 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'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 200); + 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); + + // Check that rows are sorted by age in descending order + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] >= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be >= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + // Verify the oldest test user is near the beginning + const lowestAgeRow = getTableRowsRO.rows.find((row) => row.name === testSearchedUserName && row.age === 90); + t.truthy(lowestAgeRow, 'Should find the test user with age 90'); + + 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 age by ASC and with pagination page=1, perPage=2`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).cassandraTestConnection; + 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, + 'age', + 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); + + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] <= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be <= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + const lowestAgeRow = getTableRowsRO.rows.find((row) => row.name === testSearchedUserName && row.age === 14); + t.truthy(lowestAgeRow, 'Should find the test user with age 14'); + + 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).cassandraTestConnection; + 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, + 'age', + 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); + + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] >= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be >= age at index ${i + 1} (${ages[i + 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} 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).cassandraTestConnection; + 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, + 'age', + 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'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsResponse.status, 200); + 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.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 2); + + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] >= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be >= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + const highestAgeRow = getTableRowsRO.rows.find((row) => row.name === testSearchedUserName && row.age === 95); + t.truthy(highestAgeRow, 'Should find the test user with age 95'); + + 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).cassandraTestConnection; + 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, + 'age', + 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'); + const createTableSettingsRO = JSON.parse(createTableSettingsResponse.text); + + 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].age, 14); + 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).cassandraTestConnection; + 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, + 'age', + 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].age, 14); + t.is(getTableRowsRO.rows[1].age, 90); + 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).cassandraTestConnection; + 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, + 'age', + 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].age, 95); + 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 in body`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).cassandraTestConnection; + 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, + 'age', + 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 = 'age'; + const fieldvalue = '80'; + + const filters = { + [fieldname]: { gt: 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].age, 95); + t.is(getTableRowsRO.rows[1][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[1].age, 90); + + 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).cassandraTestConnection; + 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, + 'age', + 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 = 'age'; + const fieldvalue = '18'; + 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, 1); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[0].age, 14); + + 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).cassandraTestConnection; + 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, + 'age', + 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 = 'age'; + const fieldvalue = '96'; + 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].age, 14); + + 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} should throw an exception when table name passed in request is incorrect`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).cassandraTestConnection; + 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, + 'age', + 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 = 'age'; + const fieldLtvalue = '96'; + + 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}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 400); + + const { message } = JSON.parse(getTableRowsResponse.text); + + 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).cassandraTestConnection; + 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, + 'age', + 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 fieldLtvalue = '96'; + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}`, + ) + .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).cassandraTestConnection; + 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).cassandraTestConnection; + 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).cassandraTestConnection; + 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).cassandraTestConnection; + 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).cassandraTestConnection; + 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).cassandraTestConnection; + 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 fakeId = faker.string.uuid(); + const row = { + ['id']: fakeId, + ['age']: 99, + [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); + + // 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); + 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, fakeId); +}); + +test.serial(`${currentTest} should throw an exception when connection id is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).cassandraTestConnection; + 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: faker.string.uuid(), + ['age']: 99, + [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).cassandraTestConnection; + 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: faker.string.uuid(), + age: 99, + [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).cassandraTestConnection; + 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).cassandraTestConnection; + 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: faker.string.uuid(), + age: 99, + [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); + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 foundIdForUpdate = insertedSearchedIds[0].id; + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${foundIdForUpdate}&age=14`) + .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.findIndex((element) => element.id === foundIdForUpdate); + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 foundIdForUpdate = insertedSearchedIds[0].id; + createConnectionRO.id = ''; + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${foundIdForUpdate}`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + await createTestTable(connectionToTestDB); + const foundIdForUpdate = insertedSearchedIds[0].id; + 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=${foundIdForUpdate}`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + await createTestTable(connectionToTestDB); + const foundIdForUpdate = insertedSearchedIds[0].id; + 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=${foundIdForUpdate}`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + await createTestTable(connectionToTestDB); + const foundIdForUpdate = insertedSearchedIds[0].id; + 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=${foundIdForUpdate}`) + .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).cassandraTestConnection; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { + testTableName, + testTableColumnName, + testEntitiesSeedsCount, + testTableSecondColumnName, + insertedSearchedIds, + } = await createTestTable(connectionToTestDB); + const foundIdForUpdate = insertedSearchedIds[0].id; + 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=${foundIdForUpdate}`) + .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).cassandraTestConnection; + 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=${faker.string.uuid()}&age=14`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 firstIdToUpdate = insertedSearchedIds[0].id; + const secondIdToUpdate = insertedSearchedIds[1].id; + const requestData = { + primaryKeys: [ + { id: firstIdToUpdate, age: 14 }, + { id: secondIdToUpdate, age: 90 }, + ], + 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=${firstIdToUpdate}&age=14`) + .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=${secondIdToUpdate}&age=90`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForDeletion}&age=14`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { + testTableName, + testTableColumnName, + testEntitiesSeedsCount, + testTableSecondColumnName, + insertedSearchedIds, + } = 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 = insertedSearchedIds[0].id; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&fakePKey=${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.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).cassandraTestConnection; + 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=${faker.string.uuid()}&age=14`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForSearch}&age=14`) + .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, idForSearch); + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { + testTableName, + testTableColumnName, + testEntitiesSeedsCount, + testTableSecondColumnName, + insertedSearchedIds, + } = 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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); + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { + testTableName, + testTableColumnName, + testEntitiesSeedsCount, + testTableSecondColumnName, + insertedSearchedIds, + } = 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + 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 = faker.string.uuid(); + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForSearch}&age=14`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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: insertedSearchedIds[0].id, + age: 14, + }, + { + id: insertedSearchedIds[1].id, + age: 90, + }, + { + id: insertedSearchedIds[2].id, + age: 95, + }, + ]; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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: insertedSearchedIds[0].id, + age: 14, + }, + { + id: insertedSearchedIds[1].id, + age: 90, + }, + { + id: insertedSearchedIds[2].id, + age: 95, + }, + ]; + 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'); + + const deleteRowInTableRO = JSON.parse(deleteRowInTableResponse.text); + t.is(deleteRowInTableResponse.status, 200); + + //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 = 'POST /connection/test'; + +test.serial(`${currentTest} should test connection and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).cassandraTestConnection; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + connectionToTestDB.password = '8764323452888'; + connectionToTestDB.database = 'test_db'; + 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).cassandraTestConnection; + 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.info(getTableCsvResponseRO); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(__dirname, 'response-files', 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 + 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).cassandraTestConnection; + 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).cassandraTestConnection; + 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.info(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(__dirname, 'response-files', 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 + 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).cassandraTestConnection; + 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.info(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(__dirname, 'response-files', 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 + 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'); + + const importCsvRO = JSON.parse(importCsvResponse.text); + 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); + t.is(getTableRowsRO.rows.length, testEntitiesSeedsCount + 2); +}); + +test.serial(`${currentTest} should throw exception whe csv import is disabled`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).cassandraTestConnection; + 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.info(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(__dirname, 'response-files', 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 + 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-cassandra-agent.e2e.test.ts b/backend/test/ava-tests/saas-tests/table-cassandra-agent.e2e.test.ts new file mode 100644 index 000000000..d1272dc17 --- /dev/null +++ b/backend/test/ava-tests/saas-tests/table-cassandra-agent.e2e.test.ts @@ -0,0 +1,3510 @@ +/* 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 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 { setSaasEnvVariable } from '../../utils/set-saas-env-variable.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; +let testTableName: string; +let testTableColumnName: string; +let testTableSecondColumnName: string; +let testEntitiesSeedsCount: number; +let insertedSearchedIds: Array<{ id?: string; number: number }>; +let connectionToTestDB: any; + +test.before(async () => { + setSaasEnvVariable(); + const moduleFixture = await Test.createTestingModule({ + imports: [ApplicationModule, DatabaseModule], + providers: [DatabaseService, TestUtils], + }).compile(); + app = moduleFixture.createNestApplication() as any; + testUtils = moduleFixture.get(TestUtils); + connectionToTestDB = getTestData(mockFactory).cassandraAgentTestConnection; + app.use(cookieParser()); + app.useGlobalFilters(new AllExceptionsFilter()); + app.useGlobalPipes( + new ValidationPipe({ + exceptionFactory(validationErrors: ValidationError[] = []) { + return new ValidationException(validationErrors); + }, + }), + ); + await app.init(); + app.getHttpServer().listen(0); +}); + +test.beforeEach(async () => { + const connectionToCassandraDBInDocker = getTestData(mockFactory).cassandraTestConnection; + const testTableCreationResult = await createTestTable(connectionToCassandraDBInDocker); + testTableName = testTableCreationResult.testTableName; + testTableColumnName = testTableCreationResult.testTableColumnName; + testTableSecondColumnName = testTableCreationResult.testTableSecondColumnName; + testEntitiesSeedsCount = testTableCreationResult.testEntitiesSeedsCount; + insertedSearchedIds = testTableCreationResult.insertedSearchedIds; +}); + +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 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); + console.log('🚀 ~ test.only ~ createConnectionRO:', createConnectionRO); + 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); + console.log('🚀 ~ test.serial ~ getTablesRO:', getTablesRO) + 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 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); + + 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 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); + 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 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 getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}`) + .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(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 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, + ['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 = insertedSearchedIds[0].id; + + 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'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 200); + 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, 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 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, + ['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, 'text'); + + 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 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, + ['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); + + 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, 'text'); + + 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 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, + 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, 'text'); + + 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 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, + 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, 'text'); + + 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 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, + 42, + QueryOrderingEnum.DESC, + 'age', + 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'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsResponse.status, 200); + 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); + + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] >= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be >= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + const highestAgeRows = getTableRowsRO.rows.filter((row) => row.age >= 90); + t.true(highestAgeRows.length >= 2, 'Should have at least 2 rows with age >= 90'); + t.true( + highestAgeRows.every((row) => row.name === testSearchedUserName), + 'High age rows should have the test user name', + ); + + 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 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, + 42, + QueryOrderingEnum.ASC, + 'age', + 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); + + // Check that rows are sorted by age in ascending order + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] <= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be <= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + // Verify the youngest test user is near the beginning + const lowestAgeRow = getTableRowsRO.rows.find((row) => row.name === testSearchedUserName && row.age === 14); + t.truthy(lowestAgeRow, 'Should find the test user with age 14'); + + 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 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, + 'age', + 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'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 200); + 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); + + // Check that rows are sorted by age in descending order + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] >= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be >= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + // Verify the oldest test user is near the beginning + const lowestAgeRow = getTableRowsRO.rows.find((row) => row.name === testSearchedUserName && row.age === 90); + t.truthy(lowestAgeRow, 'Should find the test user with age 90'); + + 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 age by ASC and with pagination page=1, perPage=2`, + async (t) => { + try { + 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.ASC, + 'age', + 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); + + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] <= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be <= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + const lowestAgeRow = getTableRowsRO.rows.find((row) => row.name === testSearchedUserName && row.age === 14); + t.truthy(lowestAgeRow, 'Should find the test user with age 14'); + + 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 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, + 'age', + 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); + + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] >= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be >= age at index ${i + 1} (${ages[i + 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} 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 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, + 'age', + 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'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsResponse.status, 200); + 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.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 2); + + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] >= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be >= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + const highestAgeRow = getTableRowsRO.rows.find((row) => row.name === testSearchedUserName && row.age === 95); + t.truthy(highestAgeRow, 'Should find the test user with age 95'); + + 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 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, + 'age', + 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'); + const createTableSettingsRO = JSON.parse(createTableSettingsResponse.text); + + 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].age, 14); + 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 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.ASC, + 'age', + 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].age, 14); + t.is(getTableRowsRO.rows[1].age, 90); + 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 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.ASC, + 'age', + 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].age, 95); + 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 in body`, + async (t) => { + try { + 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, + 'age', + 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 = 'age'; + const fieldvalue = '80'; + + const filters = { + [fieldname]: { gt: 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].age, 95); + t.is(getTableRowsRO.rows[1][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[1].age, 90); + + 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 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, + 'age', + 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 = 'age'; + const fieldvalue = '18'; + 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, 1); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[0].age, 14); + + 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 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, + 'age', + 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 = 'age'; + const fieldvalue = '96'; + 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].age, 14); + + 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} should throw an exception when table name passed in request is incorrect`, async (t) => { + try { + 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, + 'age', + 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 = 'age'; + const fieldLtvalue = '96'; + + 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}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 400); + + const { message } = JSON.parse(getTableRowsResponse.text); + + 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 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, + 'age', + 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 fieldLtvalue = '96'; + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}`, + ) + .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 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 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 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); + 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 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); + + 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 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 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 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 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 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 fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const fakeId = faker.string.uuid(); + const row = { + ['id']: fakeId, + ['age']: 99, + [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); + + // 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); + 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, fakeId); +}); + +test.serial(`${currentTest} should throw an exception when connection id is not passed in request`, async (t) => { + 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 fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + id: faker.string.uuid(), + ['age']: 99, + [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 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 fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + id: faker.string.uuid(), + age: 99, + [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 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 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 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 fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + id: faker.string.uuid(), + age: 99, + [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); + 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 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 fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + const foundIdForUpdate = insertedSearchedIds[0].id; + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${foundIdForUpdate}&age=14`) + .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.findIndex((element) => element.id === foundIdForUpdate); + 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 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 fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + const foundIdForUpdate = insertedSearchedIds[0].id; + createConnectionRO.id = ''; + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${foundIdForUpdate}`) + .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 firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const foundIdForUpdate = insertedSearchedIds[0].id; + + 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=${foundIdForUpdate}`) + .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 firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + const foundIdForUpdate = insertedSearchedIds[0].id; + + 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=${foundIdForUpdate}`) + .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 firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + const foundIdForUpdate = insertedSearchedIds[0].id; + + 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=${foundIdForUpdate}`) + .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 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 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 firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const foundIdForUpdate = insertedSearchedIds[0].id; + + 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=${foundIdForUpdate}`) + .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 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 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=${faker.string.uuid()}&age=14`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 500); + const { message, originalMessage } = JSON.parse(updateRowInTableResponse.text); + t.is(originalMessage, `No data returned from agent`); + }, +); + +currentTest = 'PUT /table/rows/update/:connectionId'; + +test.serial(`${currentTest} should update multiple rows and return result`, async (t) => { + 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 fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const firstIdToUpdate = insertedSearchedIds[0].id; + const secondIdToUpdate = insertedSearchedIds[1].id; + const requestData = { + primaryKeys: [ + { id: firstIdToUpdate, age: 14 }, + { id: secondIdToUpdate, age: 90 }, + ], + 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=${firstIdToUpdate}&age=14`) + .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=${secondIdToUpdate}&age=90`) + .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 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 idForDeletion = insertedSearchedIds[0].id; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForDeletion}&age=14`) + .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 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 idForDeletion = insertedSearchedIds[0].id; + 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 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 idForDeletion = insertedSearchedIds[0].id; + 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 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 idForDeletion = insertedSearchedIds[0].id; + 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 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 idForDeletion = insertedSearchedIds[0].id; + 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 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 idForDeletion = insertedSearchedIds[0].id; + 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 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 idForDeletion = insertedSearchedIds[0].id; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&fakePKey=${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.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 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 deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${faker.string.uuid()}&age=14`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const deleteRowInTableRO = JSON.parse(deleteRowInTableResponse.text); + t.is(deleteRowInTableResponse.status, 500); + t.is(deleteRowInTableRO.originalMessage, `No data returned from agent`); + }, +); + +currentTest = 'GET /table/row/:slug'; + +test.serial(`${currentTest} found row`, async (t) => { + 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 idForSearch = insertedSearchedIds[0].id; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForSearch}&age=14`) + .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, idForSearch); + 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 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 idForSearch = insertedSearchedIds[0].id; + 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 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 idForSearch = insertedSearchedIds[0].id; + 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 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 idForSearch = insertedSearchedIds[0].id; + 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 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 idForSearch = insertedSearchedIds[0].id; + 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); + 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 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 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 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 idForSearch = insertedSearchedIds[0].id; + 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 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 idForSearch = faker.string.uuid(); + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForSearch}&age=14`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 500); + const { message, originalMessage } = JSON.parse(foundRowInTableResponse.text); + t.is(originalMessage, `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 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 primaryKeysForDeletion: Array> = [ + { + id: insertedSearchedIds[0].id, + age: 14, + }, + { + id: insertedSearchedIds[1].id, + age: 90, + }, + { + id: insertedSearchedIds[2].id, + age: 95, + }, + ]; + 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 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 primaryKeysForDeletion = [ + { + id: insertedSearchedIds[0].id, + age: 14, + }, + { + id: insertedSearchedIds[1].id, + age: 90, + }, + { + id: insertedSearchedIds[2].id, + age: 95, + }, + ]; + 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'); + + const deleteRowInTableRO = JSON.parse(deleteRowInTableResponse.text); + t.is(deleteRowInTableResponse.status, 200); + + //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 = 'POST /connection/test'; + +test.skip(`${currentTest} should test connection and return result`, async (t) => { + 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.skip( + `${currentTest} should test connection and return negative result when connection password is incorrect result`, + async (t) => { + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + connectionToTestDB.password = '8764323452888'; + connectionToTestDB.database = 'test_db'; + 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 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.info(getTableCsvResponseRO); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(__dirname, 'response-files', 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 + 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 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 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.info(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(__dirname, 'response-files', 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 + 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 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.info(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(__dirname, 'response-files', 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 + 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'); + + const importCsvRO = JSON.parse(importCsvResponse.text); + 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); + t.is(getTableRowsRO.rows.length, testEntitiesSeedsCount + 2); +}); + +test.serial(`${currentTest} should throw exception whe csv import is disabled`, async (t) => { + 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.info(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(__dirname, 'response-files', 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 + 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-cassandra.e2e.test.ts b/backend/test/ava-tests/saas-tests/table-cassandra.e2e.test.ts new file mode 100644 index 000000000..a352d0ed9 --- /dev/null +++ b/backend/test/ava-tests/saas-tests/table-cassandra.e2e.test.ts @@ -0,0 +1,3805 @@ +/* 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 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'; +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.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).cassandraTestConnection; + 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).cassandraTestConnection; + 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).cassandraTestConnection; + 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).cassandraTestConnection; + 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'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 200); + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testTableSecondColumnName, insertedSearchedIds } = + 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'); + + const createTableSettingsRO = JSON.parse(createTableSettingsResponse.text); + + t.is(createTableSettingsResponse.status, 201); + + const searchedDescription = insertedSearchedIds[0].id; + + 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'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 200); + 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, 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).cassandraTestConnection; + 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, 'text'); + + 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).cassandraTestConnection; + 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); + + 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, 'text'); + + 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).cassandraTestConnection; + 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, 'text'); + + 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).cassandraTestConnection; + 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, 'text'); + + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, insertedSearchedIds } = 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, + 'age', + 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'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsResponse.status, 200); + 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); + + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] >= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be >= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + const highestAgeRows = getTableRowsRO.rows.filter((row) => row.age >= 90); + t.true(highestAgeRows.length >= 2, 'Should have at least 2 rows with age >= 90'); + t.true( + highestAgeRows.every((row) => row.name === testSearchedUserName), + 'High age rows should have the test user name', + ); + + 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).cassandraTestConnection; + 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, + 'age', + 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); + + // Check that rows are sorted by age in ascending order + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] <= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be <= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + // Verify the youngest test user is near the beginning + const lowestAgeRow = getTableRowsRO.rows.find((row) => row.name === testSearchedUserName && row.age === 14); + t.truthy(lowestAgeRow, 'Should find the test user with age 14'); + + 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).cassandraTestConnection; + 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, + 'age', + 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'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 200); + 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); + + // Check that rows are sorted by age in descending order + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] >= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be >= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + // Verify the oldest test user is near the beginning + const lowestAgeRow = getTableRowsRO.rows.find((row) => row.name === testSearchedUserName && row.age === 90); + t.truthy(lowestAgeRow, 'Should find the test user with age 90'); + + 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 age by ASC and with pagination page=1, perPage=2`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).cassandraTestConnection; + 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, + 'age', + 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); + + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] <= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be <= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + const lowestAgeRow = getTableRowsRO.rows.find((row) => row.name === testSearchedUserName && row.age === 14); + t.truthy(lowestAgeRow, 'Should find the test user with age 14'); + + 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).cassandraTestConnection; + 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, + 'age', + 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); + + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] >= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be >= age at index ${i + 1} (${ages[i + 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} 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).cassandraTestConnection; + 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, + 'age', + 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'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsResponse.status, 200); + 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.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 2); + + const ages = getTableRowsRO.rows.map((row) => row.age); + for (let i = 0; i < ages.length - 1; i++) { + t.true( + ages[i] >= ages[i + 1], + `Age at index ${i} (${ages[i]}) should be >= age at index ${i + 1} (${ages[i + 1]})`, + ); + } + + const highestAgeRow = getTableRowsRO.rows.find((row) => row.name === testSearchedUserName && row.age === 95); + t.truthy(highestAgeRow, 'Should find the test user with age 95'); + + 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).cassandraTestConnection; + 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, + 'age', + 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'); + const createTableSettingsRO = JSON.parse(createTableSettingsResponse.text); + + 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].age, 14); + 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).cassandraTestConnection; + 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, + 'age', + 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].age, 14); + t.is(getTableRowsRO.rows[1].age, 90); + 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).cassandraTestConnection; + 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, + 'age', + 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].age, 95); + 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 in body`, + async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).cassandraTestConnection; + 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, + 'age', + 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 = 'age'; + const fieldvalue = '80'; + + const filters = { + [fieldname]: { gt: 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].age, 95); + t.is(getTableRowsRO.rows[1][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[1].age, 90); + + 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).cassandraTestConnection; + 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, + 'age', + 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 = 'age'; + const fieldvalue = '18'; + 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, 1); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[0].age, 14); + + 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).cassandraTestConnection; + 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, + 'age', + 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 = 'age'; + const fieldvalue = '96'; + 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].age, 14); + + 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} should throw an exception when table name passed in request is incorrect`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).cassandraTestConnection; + 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, + 'age', + 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 = 'age'; + const fieldLtvalue = '96'; + + 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}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 400); + + const { message } = JSON.parse(getTableRowsResponse.text); + + 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).cassandraTestConnection; + 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, + 'age', + 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 fieldLtvalue = '96'; + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}`, + ) + .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).cassandraTestConnection; + 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).cassandraTestConnection; + 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).cassandraTestConnection; + 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).cassandraTestConnection; + 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).cassandraTestConnection; + 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).cassandraTestConnection; + 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 fakeId = faker.string.uuid(); + const row = { + ['id']: fakeId, + ['age']: 99, + [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); + + // 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); + 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, fakeId); +}); + +test.serial(`${currentTest} should throw an exception when connection id is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).cassandraTestConnection; + 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: faker.string.uuid(), + ['age']: 99, + [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).cassandraTestConnection; + 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: faker.string.uuid(), + age: 99, + [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).cassandraTestConnection; + 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).cassandraTestConnection; + 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: faker.string.uuid(), + age: 99, + [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); + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 foundIdForUpdate = insertedSearchedIds[0].id; + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${foundIdForUpdate}&age=14`) + .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.findIndex((element) => element.id === foundIdForUpdate); + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 foundIdForUpdate = insertedSearchedIds[0].id; + createConnectionRO.id = ''; + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${foundIdForUpdate}`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + await createTestTable(connectionToTestDB); + const foundIdForUpdate = insertedSearchedIds[0].id; + 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=${foundIdForUpdate}`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + await createTestTable(connectionToTestDB); + const foundIdForUpdate = insertedSearchedIds[0].id; + 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=${foundIdForUpdate}`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + await createTestTable(connectionToTestDB); + const foundIdForUpdate = insertedSearchedIds[0].id; + 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=${foundIdForUpdate}`) + .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).cassandraTestConnection; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { + testTableName, + testTableColumnName, + testEntitiesSeedsCount, + testTableSecondColumnName, + insertedSearchedIds, + } = await createTestTable(connectionToTestDB); + const foundIdForUpdate = insertedSearchedIds[0].id; + 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=${foundIdForUpdate}`) + .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).cassandraTestConnection; + 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=${faker.string.uuid()}&age=14`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 firstIdToUpdate = insertedSearchedIds[0].id; + const secondIdToUpdate = insertedSearchedIds[1].id; + const requestData = { + primaryKeys: [ + { id: firstIdToUpdate, age: 14 }, + { id: secondIdToUpdate, age: 90 }, + ], + 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=${firstIdToUpdate}&age=14`) + .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=${secondIdToUpdate}&age=90`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForDeletion}&age=14`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { + testTableName, + testTableColumnName, + testEntitiesSeedsCount, + testTableSecondColumnName, + insertedSearchedIds, + } = 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 = insertedSearchedIds[0].id; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&fakePKey=${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.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).cassandraTestConnection; + 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=${faker.string.uuid()}&age=14`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForSearch}&age=14`) + .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, idForSearch); + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { + testTableName, + testTableColumnName, + testEntitiesSeedsCount, + testTableSecondColumnName, + insertedSearchedIds, + } = 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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 = insertedSearchedIds[0].id; + 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); + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { + testTableName, + testTableColumnName, + testEntitiesSeedsCount, + testTableSecondColumnName, + insertedSearchedIds, + } = 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 = insertedSearchedIds[0].id; + 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).cassandraTestConnection; + 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 = faker.string.uuid(); + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForSearch}&age=14`) + .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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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: insertedSearchedIds[0].id, + age: 14, + }, + { + id: insertedSearchedIds[1].id, + age: 90, + }, + { + id: insertedSearchedIds[2].id, + age: 95, + }, + ]; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + 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: insertedSearchedIds[0].id, + age: 14, + }, + { + id: insertedSearchedIds[1].id, + age: 90, + }, + { + id: insertedSearchedIds[2].id, + age: 95, + }, + ]; + 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'); + + const deleteRowInTableRO = JSON.parse(deleteRowInTableResponse.text); + t.is(deleteRowInTableResponse.status, 200); + + //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 = 'POST /connection/test'; + +test.serial(`${currentTest} should test connection and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).cassandraTestConnection; + 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).cassandraTestConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + connectionToTestDB.password = '8764323452888'; + connectionToTestDB.database = 'test_db'; + 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).cassandraTestConnection; + 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.info(getTableCsvResponseRO); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(__dirname, 'response-files', 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 + 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).cassandraTestConnection; + 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).cassandraTestConnection; + 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.info(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(__dirname, 'response-files', 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 + 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).cassandraTestConnection; + 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.info(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(__dirname, 'response-files', 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 + 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'); + + const importCsvRO = JSON.parse(importCsvResponse.text); + 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); + t.is(getTableRowsRO.rows.length, testEntitiesSeedsCount + 2); +}); + +test.serial(`${currentTest} should throw exception whe csv import is disabled`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).cassandraTestConnection; + 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.info(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(__dirname, 'response-files', 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 + 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-mongodb-agent-e2e.test.ts b/backend/test/ava-tests/saas-tests/table-mongodb-agent-e2e.test.ts index 215cfa27c..fa685f0d5 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 @@ -39,7 +39,7 @@ let testTableName: string; let testTableColumnName: string; let testTableSecondColumnName: string; let testEntitiesSeedsCount: number; -let insertedSearchedIds: Array<{ _id: string; number: number }>; +let insertedSearchedIds: Array<{ _id?: string; number: number }>; test.before(async () => { const moduleFixture = await Test.createTestingModule({ diff --git a/backend/test/mock.factory.ts b/backend/test/mock.factory.ts index 79941937c..4f7dcff7a 100644 --- a/backend/test/mock.factory.ts +++ b/backend/test/mock.factory.ts @@ -263,6 +263,27 @@ export class MockFactory { return dto; } + generateConnectionToTestCassandraInDocker() { + const dto = new CreateConnectionDto() as any; + dto.title = 'Test connection to Cassandra in Docker'; + dto.type = 'cassandra'; + dto.host = 'test-cassandra-e2e-testing'; + dto.port = 9042; + dto.username = 'cassandra'; + dto.password = 'cassandra'; + dto.database = 'testdb'; + dto.dataCenter = 'datacenter1'; + dto.ssh = false; + return dto; + } + + generateConnectionToTestCassandraAgent() { + const dto = new CreateConnectionDto() as any; + dto.title = 'Test connection to agent db'; + dto.type = ConnectionTypesEnum.agent_cassandra; + return dto; + } + generateKnexConfigAgentTests(db_type = 'postgres') { switch (db_type) { case 'postgres': diff --git a/backend/test/utils/create-test-table.ts b/backend/test/utils/create-test-table.ts index 1c5de0ef0..ff920a63a 100644 --- a/backend/test/utils/create-test-table.ts +++ b/backend/test/utils/create-test-table.ts @@ -8,6 +8,8 @@ import { MongoClient, Db, ObjectId } from 'mongodb'; import { DynamoDB, PutItemCommand, PutItemCommandInput } from '@aws-sdk/client-dynamodb'; import { BatchWriteCommand, DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'; import { Client } from '@elastic/elasticsearch'; +import * as cassandra from 'cassandra-driver'; +import { v4 as uuidv4 } from 'uuid'; export async function createTestTable( connectionParams: any, @@ -26,6 +28,10 @@ export async function createTestTable( return createTestElasticsearchTable(connectionParams, testEntitiesSeedsCount, testSearchedUserName); } + if (connectionParams.type === ConnectionTypesEnum.cassandra) { + return createTestCassandraTable(connectionParams, testEntitiesSeedsCount, testSearchedUserName); + } + if (connectionParams.type === ConnectionTypesEnum.dynamodb) { return createTestDynamoDBTable(connectionParams, testEntitiesSeedsCount, testSearchedUserName); } @@ -297,7 +303,7 @@ export type CreatedTableInfo = { testTableColumnName: string; testTableSecondColumnName: string; testEntitiesSeedsCount: number; - insertedSearchedIds?: Array<{ number: number; _id: string }>; + insertedSearchedIds?: Array<{ number: number; _id?: string; id?: string }>; }; export async function createTestTableForMSSQLWithChema( @@ -564,3 +570,89 @@ export async function createTestDynamoDBTable( testEntitiesSeedsCount: testEntitiesSeedsCount, }; } + +export async function createTestCassandraTable( + connectionParams: any, + testEntitiesSeedsCount = 42, + testSearchedUserName = 'Vasia', +): Promise { + const testTableName = getRandomTestTableName().toLowerCase(); + const testTableColumnName = 'name'; + const testTableSecondColumnName = 'email'; + const client = new cassandra.Client({ + contactPoints: [connectionParams.host], + localDataCenter: connectionParams.dataCenter, + authProvider: new cassandra.auth.PlainTextAuthProvider(connectionParams.username, connectionParams.password), + pooling: { + coreConnectionsPerHost: { + [cassandra.types.distance.local]: 1, + [cassandra.types.distance.remote]: 1, + }, + maxRequestsPerConnection: 32, + }, + socketOptions: { + readTimeout: 30000, + connectTimeout: 30000, + }, + }); + + try { + await client.connect(); + + try { + await client.execute( + `CREATE KEYSPACE IF NOT EXISTS ${connectionParams.database} WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1}`, + ); + await client.execute(`USE ${connectionParams.database}`); + await client.execute( + `CREATE TABLE IF NOT EXISTS ${testTableName} (id UUID, ${testTableColumnName} TEXT, ${testTableSecondColumnName} TEXT, age INT, created_at TIMESTAMP, updated_at TIMESTAMP, PRIMARY KEY (id, age))`, + ); + } catch (error) { + console.error(`Error creating Cassandra table: ${error.message}`); + throw error; + } + const insertedSearchedIds = []; + for (let i = 0; i < testEntitiesSeedsCount; i++) { + const isSearchedUser = i === 0 || i === testEntitiesSeedsCount - 21 || i === testEntitiesSeedsCount - 5; + + const generatedId = uuidv4(); + const query = `INSERT INTO ${testTableName} (id, ${testTableColumnName}, ${testTableSecondColumnName}, age, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`; + const age = isSearchedUser + ? i === 0 + ? 14 + : i === testEntitiesSeedsCount - 21 + ? 90 + : 95 + : faker.number.int({ min: 16, max: 80 }); + const params = [ + generatedId, + isSearchedUser ? testSearchedUserName : faker.person.firstName(), + faker.internet.email(), + age, + new Date(), + new Date(), + ]; + try { + await client.execute(query, params, { prepare: true }); + if (isSearchedUser) { + insertedSearchedIds.push({ + number: i, + id: generatedId, + }); + } + } catch (error) { + console.error(`Error inserting into Cassandra table: ${error.message}`); + throw error; + } + } + return { + testTableName: testTableName, + testTableColumnName: testTableColumnName, + testTableSecondColumnName: testTableSecondColumnName, + testEntitiesSeedsCount: testEntitiesSeedsCount, + insertedSearchedIds, + }; + } finally { + await client.shutdown().catch((err) => console.error('Error shutting down Cassandra client:', err)); + } +} diff --git a/backend/test/utils/get-test-data.ts b/backend/test/utils/get-test-data.ts index 8111889ef..2ff672a60 100644 --- a/backend/test/utils/get-test-data.ts +++ b/backend/test/utils/get-test-data.ts @@ -28,6 +28,8 @@ export function getTestData(mockFactory: MockFactory) { const mongoDbAgentConnection = mockFactory.generateConnectionToTestMongoDBAgent(); const dynamoDBConnection = mockFactory.generateConnectionToTestDynamoDBInDocker(); const elasticsearchTestConnection = mockFactory.generateConnectionToTestElasticsearchInDocker(); + const cassandraTestConnection = mockFactory.generateConnectionToTestCassandraInDocker(); + const cassandraAgentTestConnection = mockFactory.generateConnectionToTestCassandraAgent(); return { newConnection, newEncryptedConnection, @@ -56,5 +58,7 @@ export function getTestData(mockFactory: MockFactory) { mongoDbAgentConnection, dynamoDBConnection, elasticsearchTestConnection, + cassandraTestConnection, + cassandraAgentTestConnection, }; } diff --git a/docker-compose.yml b/docker-compose.yml index 800c5970f..fca26f83b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,6 +20,8 @@ services: - test-mongo-e2e-testing - test-dynamodb-e2e-testing - test-elasticsearch-e2e-testing + - cassandra-init + - test-cassandra-e2e-testing links: - postgres - testMySQL-e2e-testing @@ -30,6 +32,7 @@ services: - test-mongo-e2e-testing - test-dynamodb-e2e-testing - test-elasticsearch-e2e-testing + - test-cassandra-e2e-testing command: ["yarn", "start"] testMySQL-e2e-testing: @@ -162,6 +165,42 @@ services: timeout: 10s retries: 3 + test-cassandra-e2e-testing: + image: cassandra:5.0.4 + ports: + - 9042:9042 + environment: + - CASSANDRA_CLUSTER_NAME=TestCluster + - CASSANDRA_DC=TestDC + - CASSANDRA_RACK=TestRack + restart: always + healthcheck: + test: + [ + "CMD", + "cqlsh", + "-u", + "cassandra", + "-p", + "cassandra", + "-e", + "describe keyspaces", + ] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + + cassandra-init: + image: cassandra:5.0.4 + depends_on: + test-cassandra-e2e-testing: + condition: service_healthy + volumes: + - ./init-cassandra.sh:/init-cassandra.sh + command: ["/init-cassandra.sh"] + restart: "no" + rocketadmin-agent_mongo: build: context: . @@ -271,3 +310,27 @@ services: - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - APPLICATION_CONFIG_FILE_NAME=.mssql_test_agent_config.txt command: ["yarn", "start:dev"] + + rocketadmin-agent_cassandra: + build: + context: . + dockerfile: ./rocketadmin-agent/Dockerfile + ports: + - 8128:8128 + volumes: + - ./rocketadmin-agent/dist:/app/dist + - ./rocketadmin-agent/src:/app/src + links: + - autoadmin-ws-server + - test-cassandra-e2e-testing + depends_on: + test-cassandra-e2e-testing: + condition: service_healthy + cassandra-init: + condition: service_completed_successfully + autoadmin-ws-server: + condition: service_started + environment: + - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 + - APPLICATION_CONFIG_FILE_NAME=.cassandra_test_agent_config.txt + command: ["yarn", "start:dev"] diff --git a/init-cassandra.sh b/init-cassandra.sh new file mode 100755 index 000000000..231d221bb --- /dev/null +++ b/init-cassandra.sh @@ -0,0 +1,13 @@ +#!/bin/bash +echo "Waiting for Cassandra to start..." +until cqlsh test-cassandra-e2e-testing -u cassandra -p cassandra -e "describe keyspaces" >/dev/null 2>&1; do + echo "Cassandra is unavailable - sleeping" + sleep 5 +done + +echo "Cassandra is up - initializing..." + +# Create the testdb keyspace if it doesn't exist +cqlsh test-cassandra-e2e-testing -u cassandra -p cassandra -e "CREATE KEYSPACE IF NOT EXISTS testdb WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1};" + +echo "Cassandra initialization completed - testdb keyspace created" diff --git a/rocketadmin-agent/.cassandra_test_agent_config.txt b/rocketadmin-agent/.cassandra_test_agent_config.txt new file mode 100644 index 000000000..4332ddaa3 --- /dev/null +++ b/rocketadmin-agent/.cassandra_test_agent_config.txt @@ -0,0 +1,22 @@ +{ + "encrypted": false, + "hash": null, + "credentials": { + "app_port": 3000, + "azure_encryption": false, + "cert": null, + "database": "testdb", + "host": "test-cassandra-e2e-testing", + "password": "cassandra", + "port": 9042, + "token": "CASSANDRA-TEST-AGENT-TOKEN", + "type": "cassandra", + "username": "cassandra", + "ssl": false, + "application_save_option": true, + "config_encryption_option": false, + "encryption_password": null, + "saving_logs_option": false, + "dataCenter": "datacenter1" + } +} diff --git a/rocketadmin-agent/Dockerfile b/rocketadmin-agent/Dockerfile index de8427bde..4c96ae69a 100644 --- a/rocketadmin-agent/Dockerfile +++ b/rocketadmin-agent/Dockerfile @@ -17,6 +17,7 @@ COPY ./rocketadmin-agent/.mysql_test_agent_config.txt /app/rocketadmin-agent/ COPY ./rocketadmin-agent/.mssql_test_agent_config.txt /app/rocketadmin-agent/ 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 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 bfa232693..20c9c1c28 100644 --- a/rocketadmin-agent/src/helpers/cli/cli-questions.ts +++ b/rocketadmin-agent/src/helpers/cli/cli-questions.ts @@ -329,4 +329,30 @@ export class CLIQuestionUtility { } } } + + public static askConnectionDataCenter(): string { + const connectionDataCenter = readlineSync.question(Messages.INTRO_MESSAGES.CONNECTION_DATACENTER_MESSAGE).trim(); + if (connectionDataCenter === Constants.CLI_QUIT_COMMAND) { + console.log(Messages.INTRO_MESSAGES.APPLICATION_CLI_QUIT); + process.exit(0); + } + if (!connectionDataCenter || connectionDataCenter.length <= 0) { + return null; + } else { + return connectionDataCenter; + } + } + + public static askConnectionAuthSource(): string { + const connectionAuthSource = readlineSync.question(Messages.INTRO_MESSAGES.CONNECTION_AUTH_SOURCE_MESSAGE).trim(); + if (connectionAuthSource === Constants.CLI_QUIT_COMMAND) { + console.log(Messages.INTRO_MESSAGES.APPLICATION_CLI_QUIT); + process.exit(0); + } + if (!connectionAuthSource || connectionAuthSource.length <= 0) { + return null; + } else { + return connectionAuthSource; + } + } } diff --git a/rocketadmin-agent/src/interfaces/interfaces.ts b/rocketadmin-agent/src/interfaces/interfaces.ts index e72fbc486..5201e4b02 100644 --- a/rocketadmin-agent/src/interfaces/interfaces.ts +++ b/rocketadmin-agent/src/interfaces/interfaces.ts @@ -29,6 +29,8 @@ export interface ICLIConnectionCredentials { config_encryption_option: boolean; encryption_password: string; saving_logs_option: boolean; + dataCenter: string; + authSource: string; } export interface ISavedCLIConnectionCredentials { diff --git a/rocketadmin-agent/src/main.ts b/rocketadmin-agent/src/main.ts index fa1af9c55..44851278a 100644 --- a/rocketadmin-agent/src/main.ts +++ b/rocketadmin-agent/src/main.ts @@ -119,6 +119,8 @@ async function bootstrap() { config_encryption_option: false, encryption_password: null, saving_logs_option: false, + dataCenter: null, + authSource: null, }; const configFromEnvironment = Config.readConnectionConfigFromEnv(); @@ -160,6 +162,12 @@ async function bootstrap() { if (connectionCredentials.type === ConnectionTypesEnum.mssql) { connectionCredentials.azure_encryption = CLIQuestionUtility.askConnectionAzureEncryption(); } + if (connectionCredentials.type === ConnectionTypesEnum.cassandra) { + connectionCredentials.dataCenter = CLIQuestionUtility.askConnectionDataCenter(); + } + if (connectionCredentials.type === ConnectionTypesEnum.mongodb) { + connectionCredentials.authSource = CLIQuestionUtility.askConnectionAuthSource(); + } connectionCredentials.cert = null; connectionCredentials.ssl = CLIQuestionUtility.askConnectionSslOption(); connectionCredentials.application_save_option = CLIQuestionUtility.askApplicationSaveConfig(); diff --git a/rocketadmin-agent/src/shared/config/config.ts b/rocketadmin-agent/src/shared/config/config.ts index 064e007c2..d81fdeff3 100644 --- a/rocketadmin-agent/src/shared/config/config.ts +++ b/rocketadmin-agent/src/shared/config/config.ts @@ -28,6 +28,8 @@ const connectionConfig: ICLIConnectionCredentials = { config_encryption_option: false, encryption_password: null, saving_logs_option: false, + dataCenter: null, + authSource: null, }; export class Config { @@ -46,6 +48,7 @@ export class Config { connectionConfig.azure_encryption = config.azure_encryption; connectionConfig.config_encryption_option = config.config_encryption_option; connectionConfig.saving_logs_option = config.saving_logs_option; + connectionConfig.dataCenter = config.dataCenter; if (config.ssl && rewrite) { try { @@ -187,6 +190,8 @@ export class Config { config_encryption_option: false, encryption_password: null, saving_logs_option: process.env.LOGS_TO_TEXT_FILE === '1', + dataCenter: process.env.CONNECTION_DATA_CENTER || null, + authSource: process.env.CONNECTION_AUTH_SOURCE || null, }; const errors = validateConnectionData(connectionConfig); diff --git a/rocketadmin-agent/src/text/messages.ts b/rocketadmin-agent/src/text/messages.ts index 6ce86ae1f..bd0b8168b 100644 --- a/rocketadmin-agent/src/text/messages.ts +++ b/rocketadmin-agent/src/text/messages.ts @@ -74,6 +74,10 @@ export const Messages = { CONNECTION_SCHEMA_MESSAGE: `Please enter the schema name. If it not exists leave this field empty: \n ->`, CONNECTION_SID_MESSAGE: `You selected database type "Oracle Database", if it have instance identifier (SID) please enter it's name or leave this field empty, if it doesn't exist': \n ->`, + CONNECTION_DATACENTER_MESSAGE: `You selected database type "Cassandra", if it have data center name please enter it's name + or leave this field empty, if it doesn't exist': \n ->`, + CONNECTION_AUTH_SOURCE_MESSAGE: `You selected database type "MongoDB", if it have auth source name please enter it's name + or leave this field empty, if it doesn't exist': \n ->`, CONNECTION_AZURE_ENCRYPTION_MESSAGE: `Azure encryption option. If your database located in Microsoft Azure cloud, and requires encryption choose "Yes" or "No" - if it doesn't: \n ->`, CONNECTION_SSL_OPTION_MESSAGE: `SSL option. Choose "Yes" if your database support ssl connections and diff --git a/shared-code/package.json b/shared-code/package.json index 86d5abf12..3fe989152 100644 --- a/shared-code/package.json +++ b/shared-code/package.json @@ -12,6 +12,7 @@ "@elastic/elasticsearch": "8.18.1", "@types/multer": "^1.4.12", "axios": "^1.9.0", + "cassandra-driver": "^4.8.0", "csv": "^6.3.11", "get-port": "^7.1.0", "jsonwebtoken": "^9.0.2", diff --git a/shared-code/src/caching/caching-constants.ts b/shared-code/src/caching/caching-constants.ts index b26ff41d7..403dda0f0 100644 --- a/shared-code/src/caching/caching-constants.ts +++ b/shared-code/src/caching/caching-constants.ts @@ -1,6 +1,7 @@ import { Knex } from 'knex'; import { Database } from 'ibm_db'; import { MongoClientDB } from '../data-access-layer/data-access-objects/data-access-object-mongodb.js'; +import { Client } from 'cassandra-driver'; export const CACHING_CONSTANTS = { DEFAULT_CONNECTION_CACHE_OPTIONS: { max: 150, @@ -22,6 +23,20 @@ export const CACHING_CONSTANTS = { }, }, + DEFAULT_CASSANDRA_CLIENT_CACHE_OPTIONS: { + max: 150, + ttl: 1000 * 60 * 60, + updateAgeOnGet: false, + updateAgeOnHas: false, + dispose: async (client: Client) => { + try { + await client.shutdown(); + } catch (_e) { + return; + } + }, + }, + DEFAULT_MONGO_DB_CACHE_OPTIONS: { max: 150, ttl: 1000 * 60 * 60, diff --git a/shared-code/src/caching/lru-storage.ts b/shared-code/src/caching/lru-storage.ts index f60e1639d..0113404bf 100644 --- a/shared-code/src/caching/lru-storage.ts +++ b/shared-code/src/caching/lru-storage.ts @@ -1,6 +1,8 @@ -import { LRUCache } from 'lru-cache'; +import { Client } from 'cassandra-driver'; +import { Database } from 'ibm_db'; import { Knex } from 'knex'; -import { CACHING_CONSTANTS } from './caching-constants.js'; +import { LRUCache } from 'lru-cache'; +import { MongoClientDB } from '../data-access-layer/data-access-objects/data-access-object-mongodb.js'; import { ConnectionAgentParams, ConnectionParams, @@ -8,17 +10,37 @@ import { import { ForeignKeyDS } from '../data-access-layer/shared/data-structures/foreign-key.ds.js'; import { PrimaryKeyDS } from '../data-access-layer/shared/data-structures/primary-key.ds.js'; import { TableStructureDS } from '../data-access-layer/shared/data-structures/table-structure.ds.js'; -import { Database } from 'ibm_db'; -import { MongoClientDB } from '../data-access-layer/data-access-objects/data-access-object-mongodb.js'; +import { CACHING_CONSTANTS } from './caching-constants.js'; const knexCache = new LRUCache(CACHING_CONSTANTS.DEFAULT_CONNECTION_CACHE_OPTIONS); const tunnelCache = new LRUCache(CACHING_CONSTANTS.DEFAULT_TUNNEL_CACHE_OPTIONS); const imdbDb2Cache = new LRUCache(CACHING_CONSTANTS.DEFAULT_IMDB_DB2_CACHE_OPTIONS); const mongoDbCache = new LRUCache(CACHING_CONSTANTS.DEFAULT_MONGO_DB_CACHE_OPTIONS); +const cassandraClientCache = new LRUCache(CACHING_CONSTANTS.DEFAULT_CASSANDRA_CLIENT_CACHE_OPTIONS); const tableStructureCache = new LRUCache(CACHING_CONSTANTS.DEFAULT_TABLE_STRUCTURE_ELEMENTS_CACHE_OPTIONS); const tableForeignKeysCache = new LRUCache(CACHING_CONSTANTS.DEFAULT_TABLE_STRUCTURE_ELEMENTS_CACHE_OPTIONS); const tablePrimaryKeysCache = new LRUCache(CACHING_CONSTANTS.DEFAULT_TABLE_STRUCTURE_ELEMENTS_CACHE_OPTIONS); - export class LRUStorage { + public static setCassandraClientCache(connection: ConnectionParams, client: Client): void { + cassandraClientCache.set(this.getConnectionIdentifier(connection), client); + } + + public static getCassandraClientCache(connection: ConnectionParams): Client | null { + const cachedClient = cassandraClientCache.get(this.getConnectionIdentifier(connection)) as Client; + return cachedClient ? cachedClient : null; + } + + public static delCassandraClientCache(connection: ConnectionParams): void { + cassandraClientCache.delete(this.getConnectionIdentifier(connection)); + } + + public static getCassandraClientCount(): number { + return cassandraClientCache.size; + } + + public static clearCassandraClientCache(): void { + cassandraClientCache.clear(); + } + public static setMongoDbCache(connection: ConnectionParams, newDb: MongoClientDB): void { mongoDbCache.set(this.getConnectionIdentifier(connection), newDb); } @@ -166,12 +188,17 @@ export class LRUStorage { return JSON.stringify(cacheObj); } const cacheObj = { - id: connectionParams.id, - signing_key: connectionParams.signing_key, + // id: connectionParams.id, + // signing_key: connectionParams.signing_key, host: connectionParams.host, port: connectionParams.port, username: connectionParams.username, database: connectionParams.database, + type: connectionParams.type, + schema: connectionParams.schema ? connectionParams.schema : null, + authSource: connectionParams.authSource ? connectionParams.authSource : null, + sid: connectionParams.sid ? connectionParams.sid : null, + dataCenter: connectionParams.dataCenter ? connectionParams.dataCenter : null, }; return JSON.stringify(cacheObj); } diff --git a/shared-code/src/data-access-layer/data-access-objects/data-access-object-agent.ts b/shared-code/src/data-access-layer/data-access-objects/data-access-object-agent.ts index baf3fc940..b774c0e47 100644 --- a/shared-code/src/data-access-layer/data-access-objects/data-access-object-agent.ts +++ b/shared-code/src/data-access-layer/data-access-objects/data-access-object-agent.ts @@ -47,30 +47,32 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.addRowInTable, - tableName, - row, - email: userEmail, - }); - - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.addRowInTable, + tableName, + row, + email: userEmail, + }); + + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async deleteRowInTable( @@ -81,30 +83,32 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.deleteRowInTable, - tableName, - primaryKey, - email: userEmail, - }); - - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.deleteRowInTable, + tableName, + primaryKey, + email: userEmail, + }); + + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async getIdentityColumns( @@ -117,32 +121,34 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.getIdentityColumns, - tableName, - referencedFieldName, - identityColumnName, - fieldValues, - email: userEmail, - }); - - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.getIdentityColumns, + tableName, + referencedFieldName, + identityColumnName, + fieldValues, + email: userEmail, + }); + + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async getRowByPrimaryKey( @@ -154,35 +160,37 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.getRowByPrimaryKey, - tableName, - primaryKey, - tableSettings: settings, - email: userEmail, - }); - - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.getRowByPrimaryKey, + tableName, + primaryKey, + tableSettings: settings, + email: userEmail, + }); + + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - if (Array.isArray(commandResult)) { - return commandResult[0]; - } + if (Array.isArray(commandResult)) { + return commandResult[0]; + } - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async bulkGetRowsFromTableByPrimaryKeys( @@ -194,31 +202,33 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.bulkGetRowsFromTableByPrimaryKeys, - tableName, - primaryKey: primaryKeys, - tableSettings: settings, - email: userEmail, - }); - - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.bulkGetRowsFromTableByPrimaryKeys, + tableName, + primaryKey: primaryKeys, + tableSettings: settings, + email: userEmail, + }); + + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async getRowsFromTable( @@ -234,35 +244,37 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.getRowsFromTable, - tableName, - tableSettings: settings, - page, - perPage, - searchedFieldValue, - filteringFields, - autocompleteFields, - email: userEmail, - }); - - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.getRowsFromTable, + tableName, + tableSettings: settings, + page, + perPage, + searchedFieldValue, + filteringFields, + autocompleteFields, + email: userEmail, + }); + + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async getTableForeignKeys(tableName: string, userEmail: string): Promise { @@ -274,30 +286,32 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { return cachedForeignKeys; } - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.getTableForeignKeys, - tableName, - email: userEmail, - }); + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.getTableForeignKeys, + tableName, + email: userEmail, + }); - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - LRUStorage.setTableForeignKeysCache(this.connection, tableName, commandResult); - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + LRUStorage.setTableForeignKeysCache(this.connection, tableName, commandResult); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async getTablePrimaryColumns(tableName: string, userEmail: string): Promise { @@ -309,58 +323,62 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { return cachedPrimaryColumns; } - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.getTablePrimaryColumns, - tableName, - email: userEmail, - }); + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.getTablePrimaryColumns, + tableName, + email: userEmail, + }); - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - LRUStorage.setTablePrimaryKeysCache(this.connection, tableName, commandResult); - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + LRUStorage.setTablePrimaryKeysCache(this.connection, tableName, commandResult); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async getTablesFromDB(userEmail: string): Promise { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.getTablesFromDB, - email: userEmail, - }); + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.getTablesFromDB, + email: userEmail, + }); - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async getTableStructure(tableName: string, userEmail: string): Promise { @@ -372,54 +390,58 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { return cachedTableStructure; } - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.getTableStructure, - tableName, - email: userEmail, - }); + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.getTableStructure, + tableName, + email: userEmail, + }); - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - LRUStorage.setTableStructureCache(this.connection, tableName, commandResult); - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + LRUStorage.setTableStructureCache(this.connection, tableName, commandResult); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async testConnect(userEmail: string = 'unknown'): Promise { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.testConnect, - email: userEmail, - }); + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.testConnect, + email: userEmail, + }); - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async updateRowInTable( @@ -431,31 +453,33 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.updateRowInTable, - tableName, - row, - primaryKey, - email: userEmail, - }); - - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.updateRowInTable, + tableName, + row, + primaryKey, + email: userEmail, + }); + + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async bulkUpdateRowsInTable( @@ -467,31 +491,33 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.bulkUpdateRowsInTable, - tableName, - row: newValues, - primaryKey: primaryKeys, - email: userEmail, - }); - - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.bulkUpdateRowsInTable, + tableName, + row: newValues, + primaryKey: primaryKeys, + email: userEmail, + }); + + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async bulkDeleteRowsInTable( @@ -502,30 +528,32 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.bulkDeleteRowsInTable, - tableName, - primaryKey: primaryKeys, - email: userEmail, - }); - - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.bulkDeleteRowsInTable, + tableName, + primaryKey: primaryKeys, + email: userEmail, + }); + + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async validateSettings( @@ -536,30 +564,32 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.validateSettings, - tableName, - tableSettings: settings, - email: userEmail, - }); - - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.validateSettings, + tableName, + tableSettings: settings, + email: userEmail, + }); + + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async getReferencedTableNamesAndColumns( @@ -569,58 +599,62 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.getReferencedTableNamesAndColumns, - email: userEmail, - tableName, - }); + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.getReferencedTableNamesAndColumns, + email: userEmail, + tableName, + }); - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async isView(tableName: string, userEmail: string): Promise { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.isView, - email: userEmail, - tableName, - }); + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.isView, + email: userEmail, + tableName, + }); - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (commandResult === null || commandResult === undefined) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (commandResult === null || commandResult === undefined) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - return commandResult; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async getTableRowsStream( @@ -634,63 +668,68 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.getRowsAsStream, - tableName, - tableSettings: settings, - page, - perPage, - searchedFieldValue, - filteringFields, - }); - - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.getRowsAsStream, + tableName, + tableSettings: settings, + page, + perPage, + searchedFieldValue, + filteringFields, + }); + + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - return commandResult?.data; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult?.data; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async importCSVInTable(file: Express.Multer.File, tableName: string, userEmail: string): Promise { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - 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); - } - const rowsWithProcessedDates = await this.processTimeColumnsInRows(rows, tableName, userEmail); - const queue = new PQueue({ concurrency: 3 }); - await Promise.all( - rowsWithProcessedDates.map(async (row) => { - return await queue.add(async () => { - return await this.addRowInTable(tableName, row, userEmail); - }); - }), - ); - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + + return this.executeWithRetry(async () => { + 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); + } + const rowsWithProcessedDates = await this.processTimeColumnsInRows(rows, tableName, userEmail); + const queue = new PQueue({ concurrency: 3 }); + await Promise.all( + rowsWithProcessedDates.map(async (row) => { + return await queue.add(async () => { + return await this.addRowInTable(tableName, row, userEmail); + }); + }), + ); + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } public async executeRawQuery( @@ -701,30 +740,32 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { const jwtAuthToken = this.generateJWT(this.connection.token); axios.defaults.headers.common['Authorization'] = `Bearer ${jwtAuthToken}`; - try { - const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { - operationType: DataAccessObjectCommandsEnum.executeRawQuery, - query, - email: userEmail, - tableName, - }); - - if (commandResult instanceof Error) { - throw new Error(commandResult.message); - } + return this.executeWithRetry(async () => { + try { + const { data: { commandResult } = {} } = await axios.post(this.serverAddress, { + operationType: DataAccessObjectCommandsEnum.executeRawQuery, + query, + email: userEmail, + tableName, + }); + + if (commandResult instanceof Error) { + throw new Error(commandResult.message); + } - if (!commandResult) { - throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); - } + if (!commandResult) { + throw new Error(ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT); + } - return commandResult?.data; - } catch (e) { - if (axios.isAxiosError(e)) { - this.checkIsErrorLocalAndThrowException(e); - throw new Error(e.response?.data); + return commandResult?.data; + } catch (e) { + if (axios.isAxiosError(e)) { + this.checkIsErrorLocalAndThrowException(e); + throw new Error(e.response?.data); + } + throw e; } - throw e; - } + }); } private generateJWT(connectionToken: string): string { @@ -740,13 +781,37 @@ export class DataAccessObjectAgent implements IDataAccessObjectAgent { ); } - private checkIsErrorLocalAndThrowException(e: Error & { code?: string; hostname?: string }): void { + private async executeWithRetry(operationFunction: () => Promise, maxRetries: number = 1): Promise { + let lastError: Error; + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + return await operationFunction(); + } catch (e) { + lastError = e as Error; + if (e instanceof Error && e.message === ERROR_MESSAGES.CLIENT_NOT_CONNECTED && attempt < maxRetries) { + console.log(`Retry attempt ${attempt + 1} due to: ${e.message}`); + await new Promise((resolve) => setTimeout(resolve, 500)); + continue; + } + throw e; + } + } + + throw lastError; + } + + private checkIsErrorLocalAndThrowException( + e: Error & { code?: string; hostname?: string; response?: { data?: string } }, + ): void { if (e?.message === ERROR_MESSAGES.NO_DATA_RETURNED_FROM_AGENT) { throw new Error(e.message); } if (e?.code?.toLowerCase() === 'enotfound' && e?.hostname === 'autoadmin-ws.local') { throw new Error(ERROR_MESSAGES.CANT_CONNECT_AUTOADMIN_WS); } + if (e?.response?.data === 'Client is not connected' || e?.response?.data === ERROR_MESSAGES.CLIENT_NOT_CONNECTED) { + throw new Error(ERROR_MESSAGES.CLIENT_NOT_CONNECTED); + } } private async processTimeColumnsInRows( diff --git a/shared-code/src/data-access-layer/data-access-objects/data-access-object-cassandra.ts b/shared-code/src/data-access-layer/data-access-objects/data-access-object-cassandra.ts new file mode 100644 index 000000000..d01151a40 --- /dev/null +++ b/shared-code/src/data-access-layer/data-access-objects/data-access-object-cassandra.ts @@ -0,0 +1,766 @@ +/* eslint-disable security/detect-object-injection */ +import * as cassandra from 'cassandra-driver'; +import * as csv from 'csv'; +import getPort from 'get-port'; +import { Readable, Stream } from 'stream'; +import { validate as isUuid } from 'uuid'; +import { LRUStorage } from '../../caching/lru-storage.js'; +import { DAO_CONSTANTS } from '../../helpers/data-access-objects-constants.js'; +import { getTunnel } from '../../helpers/get-ssh-tunnel.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 { QueryOrderingEnum } from '../shared/enums/query-ordering.enum.js'; +import { IDataAccessObject } from '../shared/interfaces/data-access-object.interface.js'; +import { BasicDataAccessObject } from './basic-data-access-object.js'; + +export class DataAccessObjectCassandra extends BasicDataAccessObject implements IDataAccessObject { + constructor(connection: ConnectionParams) { + super(connection); + } + + public async addRowInTable( + tableName: string, + row: Record, + ): Promise | number> { + try { + const client = await this.getCassandraClient(); + const columns = Object.keys(row).map((col) => col.toLowerCase()); + const values = Object.values(row); + const placeholders = columns.map(() => '?').join(', '); + const query = `INSERT INTO ${tableName.toLowerCase()} (${columns.join(', ')}) VALUES (${placeholders})`; + await client.execute(query, values, { prepare: true }); + return row; + } catch (error) { + throw new Error(`Failed to add row in table: ${error.message}`); + } + } + + public async deleteRowInTable( + tableName: string, + primaryKey: Record, + ): Promise> { + try { + const client = await this.getCassandraClient(); + const primaryKeys = await this.getTablePrimaryColumns(tableName); + const whereConditions = []; + const params = []; + for (const key of primaryKeys) { + const keyName = key.column_name; + if (primaryKey[keyName] !== undefined) { + whereConditions.push(`${keyName} = ?`); + params.push(primaryKey[keyName]); + } + } + if (whereConditions.length === 0) { + throw new Error('No primary key values provided for deletion'); + } + const deleteQuery = `DELETE FROM ${tableName.toLowerCase()} WHERE ${whereConditions.join(' AND ')}`; + await client.execute(deleteQuery, params, { prepare: true }); + return primaryKey; + } catch (error) { + throw new Error(`Failed to delete row in table: ${error.message}`); + } + } + + public async getIdentityColumns( + tableName: string, + referencedFieldName: string, + identityColumnName: string, + fieldValues: Array, + ): Promise>> { + try { + const client = await this.getCassandraClient(); + const results = []; + for (const value of fieldValues) { + const query = `SELECT ${identityColumnName.toLowerCase()} FROM ${tableName.toLowerCase()} WHERE ${referencedFieldName.toLowerCase()} = ? ALLOW FILTERING`; + const result = await client.execute(query, [value], { prepare: true }); + for (const row of result.rows) { + results.push(row); + } + } + return results; + } catch (error) { + throw new Error(`Failed to get identity columns: ${error.message}`); + } + } + + public async getRowByPrimaryKey( + tableName: string, + primaryKey: Record, + settings: TableSettingsDS, + ): Promise> { + try { + const client = await this.getCassandraClient(); + let availableFields: string[] = []; + if (settings) { + const tableStructure = await this.getTableStructure(tableName); + availableFields = this.findAvailableFields(settings, tableStructure); + } + const primaryKeys = await this.getTablePrimaryColumns(tableName); + + const whereConditions = []; + const params = []; + for (const key of primaryKeys) { + const keyName = key.column_name; + if (primaryKey[keyName] !== undefined) { + whereConditions.push(`${keyName} = ?`); + params.push(primaryKey[keyName]); + } + } + if (whereConditions.length === 0) { + throw new Error('No primary key values provided'); + } + const fieldsToSelect = availableFields.length ? availableFields.join(', ') : '*'; + const query = `SELECT ${fieldsToSelect} FROM ${tableName.toLowerCase()} WHERE ${whereConditions.join(' AND ')}`; + const result = await client.execute(query, params, { prepare: true }); + return result.rows[0]; + } catch (error) { + throw new Error(`Failed to get row by primary key: ${error.message}`); + } + } + + public async bulkGetRowsFromTableByPrimaryKeys( + tableName: string, + primaryKeys: Array>, + settings: TableSettingsDS, + ): Promise>> { + try { + const client = await this.getCassandraClient(); + let availableFields: string[] = []; + if (settings) { + const tableStructure = await this.getTableStructure(tableName); + availableFields = this.findAvailableFields(settings, tableStructure); + } + const tablePrimaryKeys = await this.getTablePrimaryColumns(tableName); + const results = []; + for (const primaryKey of primaryKeys) { + const whereConditions = []; + const params = []; + for (const key of tablePrimaryKeys) { + const keyName = key.column_name; + if (primaryKey[keyName] !== undefined) { + whereConditions.push(`${keyName} = ?`); + params.push(primaryKey[keyName]); + } + } + if (whereConditions.length === 0) { + continue; + } + const fieldsToSelect = availableFields.length ? availableFields.join(', ') : '*'; + const query = `SELECT ${fieldsToSelect} FROM ${tableName.toLowerCase()} WHERE ${whereConditions.join(' AND ')}`; + const result = await client.execute(query, params, { prepare: true }); + for (const row of result.rows) { + results.push(row); + } + } + return results; + } catch (error) { + throw new Error(`Failed to bulk get rows from table by primary keys: ${error.message}`); + } + } + + public async getRowsFromTable( + tableName: string, + settings: TableSettingsDS, + page: number, + perPage: number, + searchedFieldValue: string, + filteringFields: Array, + autocompleteFields: AutocompleteFieldsDS, + ): 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; + try { + const client = await this.getCassandraClient(); + const 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.length > 0 ? fields.map((f) => f.toLowerCase()).join(', ') : '*'; + const allRows: any[] = []; + const seen = new Set(); + for (const field of fields) { + const query = `SELECT ${selectFields} FROM ${tableName.toLowerCase()} WHERE ${field.toLowerCase()} = ? ALLOW FILTERING LIMIT ${DAO_CONSTANTS.AUTOCOMPLETE_ROW_LIMIT}`; + const result = await client.execute(query, [value], { prepare: true }); + for (const row of result.rows) { + const key = JSON.stringify(row); + if (!seen.has(key)) { + seen.add(key); + allRows.push(row); + } + } + } + return { + data: allRows, + pagination: { + total: allRows.length, + lastPage: 1, + perPage: allRows.length, + currentPage: 1, + }, + large_dataset: allRows.length > DAO_CONSTANTS.LARGE_DATASET_ROW_LIMIT, + }; + } + + const whereConditions: string[] = []; + const params: any[] = []; + + let { search_fields } = settings; + if ((!search_fields || search_fields.length === 0) && searchedFieldValue) { + search_fields = availableFields; + } + + if (searchedFieldValue && search_fields?.length > 0) { + for (const field of search_fields) { + const fieldDef = tableStructure.find((col) => col.column_name === field); + if (!fieldDef) continue; + const type = fieldDef.data_type; + let valid = false; + let param; + if (type === 'uuid' && isUuid(searchedFieldValue)) { + valid = true; + param = searchedFieldValue; + } else if ((type === 'int' || type === 'bigint') && !isNaN(Number(searchedFieldValue))) { + valid = true; + param = Number(searchedFieldValue); + } else if (type === 'date') { + if (typeof searchedFieldValue === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(searchedFieldValue)) { + valid = true; + param = searchedFieldValue; + } + } else if (type !== 'uuid' && type !== 'int' && type !== 'bigint') { + valid = true; + param = searchedFieldValue; + } + if (valid) { + whereConditions.push(`${field.toLowerCase()} = ?`); + params.push(param); + break; + } + } + } + + if (filteringFields?.length) { + for (const filter of filteringFields) { + if (filter.value !== undefined && filter.value !== null) { + switch (filter.criteria) { + case FilterCriteriaEnum.eq: + whereConditions.push(`${filter.field.toLowerCase()} = ?`); + params.push(filter.value); + break; + case FilterCriteriaEnum.gt: + whereConditions.push(`${filter.field.toLowerCase()} > ?`); + params.push(filter.value); + break; + case FilterCriteriaEnum.lt: + whereConditions.push(`${filter.field.toLowerCase()} < ?`); + params.push(filter.value); + break; + case FilterCriteriaEnum.gte: + whereConditions.push(`${filter.field.toLowerCase()} >= ?`); + params.push(filter.value); + break; + case FilterCriteriaEnum.lte: + whereConditions.push(`${filter.field.toLowerCase()} <= ?`); + params.push(filter.value); + break; + default: + break; + } + } + } + } + + const selectFields = availableFields.length > 0 ? availableFields.map((f) => f.toLowerCase()).join(', ') : '*'; + let query = `SELECT ${selectFields} FROM ${tableName.toLowerCase()}`; + if (whereConditions.length > 0) { + query += ` WHERE ${whereConditions.join(' AND ')} ALLOW FILTERING`; + } + + const countQuery = + `SELECT COUNT(*) as count FROM ${tableName.toLowerCase()}` + + (whereConditions.length > 0 ? ` WHERE ${whereConditions.join(' AND ')} ALLOW FILTERING` : ''); + const countResult = await client.execute(countQuery, params, { prepare: true }); + const totalCount = parseInt(countResult.rows[0].count?.toString() || '0', 10); + + const result = await client.execute(query, params, { prepare: true }); + const startIndex = (page - 1) * perPage; + + const allRows = [...result.rows]; + if (settings?.ordering_field && settings?.ordering) { + const orderingField = settings.ordering_field.toLowerCase(); + const direction = settings.ordering === QueryOrderingEnum.DESC ? -1 : 1; + + allRows.sort((a, b) => { + const valueA = a[orderingField]; + const valueB = b[orderingField]; + if (valueA === null || valueA === undefined) return direction * -1; + if (valueB === null || valueB === undefined) return direction; + if (typeof valueA === 'number' && typeof valueB === 'number') { + return (valueA - valueB) * direction; + } else if (valueA instanceof Date && valueB instanceof Date) { + return (valueA.getTime() - valueB.getTime()) * direction; + } else { + return String(valueA).localeCompare(String(valueB)) * direction; + } + }); + } + + const rows = allRows.slice(startIndex, startIndex + perPage); + + const pagination = { + total: totalCount, + lastPage: Math.ceil(totalCount / perPage), + perPage, + currentPage: page, + }; + + return { + data: rows, + pagination, + large_dataset: totalCount > DAO_CONSTANTS.LARGE_DATASET_ROW_LIMIT, + }; + } catch (error) { + throw new Error(`Failed to get rows from table: ${error.message}`); + } + } + + public async getTableForeignKeys(_tableName: string): Promise> { + return []; + } + private cassandraTypeToReadable(type: any): string { + if (typeof type === 'string') return type; + if (type && typeof type === 'object') { + if (type.info && type.info.name) return type.info.name; + if (type.code !== undefined) { + switch (type.code) { + case 4: + return 'int'; + case 5: + return 'bigint'; + case 6: + return 'varint'; + case 7: + return 'decimal'; + case 8: + return 'boolean'; + case 9: + return 'float'; + case 10: + return 'double'; + case 12: + return 'text'; + case 13: + return 'varchar'; + case 16: + return 'uuid'; + case 17: + return 'timeuuid'; + case 19: + return 'timestamp'; + case 20: + return 'inet'; + case 21: + return 'date'; + case 22: + return 'time'; + case 32: + return 'list'; + case 33: + return 'map'; + case 34: + return 'set'; + case 0: + return 'custom'; + default: + return `type_code_${type.code}`; + } + } + } + return 'unknown'; + } + + public async getTablePrimaryColumns(tableName: string): Promise> { + try { + const client = await this.getCassandraClient(); + const keyspaceMetadata = client.metadata.keyspaces[this.connection.database]; + if (!keyspaceMetadata) { + throw new Error(`Keyspace ${this.connection.database} not found`); + } + const tableMetadata = await client.metadata.getTable(this.connection.database, tableName.toLowerCase()); + if (!tableMetadata) { + throw new Error(`Table ${tableName} not found`); + } + const primaryKeys: Array = []; + tableMetadata.partitionKeys.forEach((column) => { + primaryKeys.push({ + column_name: column.name, + data_type: this.cassandraTypeToReadable(column.type), + }); + }); + tableMetadata.clusteringKeys.forEach((column) => { + primaryKeys.push({ + column_name: column.name, + data_type: this.cassandraTypeToReadable(column.type), + }); + }); + return primaryKeys; + } catch (error) { + throw new Error(`Failed to get table primary columns: ${error.message}`); + } + } + + public async getTablesFromDB(): Promise> { + try { + const client = await this.getCassandraClient(); + const keyspace = this.connection.database; + const tables: Array = []; + const tablesResult = await client.execute( + 'SELECT table_name FROM system_schema.tables WHERE keyspace_name = ?', + [keyspace], + { prepare: true }, + ); + for (const row of tablesResult.rows) { + tables.push({ + tableName: row['table_name'], + isView: false, + }); + } + const viewsResult = await client.execute( + 'SELECT view_name FROM system_schema.views WHERE keyspace_name = ?', + [keyspace], + { prepare: true }, + ); + for (const row of viewsResult.rows) { + tables.push({ + tableName: row['view_name'], + isView: true, + }); + } + return tables; + } catch (error) { + throw new Error(`Failed to get tables from database: ${error.message}`); + } + } + + public async getTableStructure(tableName: string): Promise> { + try { + const client = await this.getCassandraClient(); + const tableStructure: Array = []; + const keyspace = this.connection.database; + const tableNameLower = tableName.toLowerCase(); + const columnsResult = await client.execute( + 'SELECT column_name, type, kind FROM system_schema.columns WHERE keyspace_name = ? AND table_name = ?', + [keyspace, tableNameLower], + { prepare: true }, + ); + for (const row of columnsResult.rows) { + tableStructure.push({ + column_name: row['column_name'], + column_default: null, + data_type: this.cassandraTypeToReadable(row['type']), + allow_null: true, + character_maximum_length: null, + data_type_params: null, + udt_name: null, + }); + } + if (tableStructure.length === 0) { + throw new Error(`Table or view ${tableName} not found`); + } + return tableStructure; + } catch (error) { + throw new Error(`Failed to get table structure: ${error.message}`); + } + } + + public async testConnect(): Promise { + try { + const client = await this.getCassandraClient(); + await client.execute('SELECT key FROM system.local'); + return { + result: true, + message: 'Successfully connected', + }; + } catch (error) { + return { + result: false, + message: `Connection failed: ${error.message}`, + }; + } + } + + public async updateRowInTable( + tableName: string, + row: Record, + primaryKey: Record, + ): Promise> { + try { + const client = await this.getCassandraClient(); + const primaryKeys = await this.getTablePrimaryColumns(tableName); + const setClause = []; + const setParams = []; + for (const [key, value] of Object.entries(row)) { + if (!primaryKeys.some((pk) => pk.column_name === key)) { + setClause.push(`${key} = ?`); + setParams.push(value); + } + } + if (setClause.length === 0) { + throw new Error('No fields to update'); + } + const whereConditions = []; + const whereParams = []; + for (const key of primaryKeys) { + const keyName = key.column_name; + if (primaryKey[keyName] !== undefined) { + whereConditions.push(`${keyName} = ?`); + whereParams.push(primaryKey[keyName]); + } + } + if (whereConditions.length === 0) { + throw new Error('No primary key values provided for update'); + } + const query = `UPDATE ${tableName} SET ${setClause.join(', ')} WHERE ${whereConditions.join(' AND ')}`; + await client.execute(query, [...setParams, ...whereParams], { prepare: true }); + return primaryKey; + } catch (error) { + throw new Error(`Failed to update row in table: ${error.message}`); + } + } + + public async bulkUpdateRowsInTable( + tableName: string, + newValues: Record, + primaryKeys: Array>, + ): Promise>> { + try { + for (const primaryKey of primaryKeys) { + await this.updateRowInTable(tableName, newValues, primaryKey); + } + return primaryKeys; + } catch (error) { + throw new Error(`Failed to bulk update rows in table: ${error.message}`); + } + } + + public async bulkDeleteRowsInTable(tableName: string, primaryKeys: Array>): Promise { + try { + let deletedCount = 0; + for (const primaryKey of primaryKeys) { + try { + await this.deleteRowInTable(tableName, primaryKey); + deletedCount++; + } catch (error) { + console.error(`Error deleting row: ${error.message}`); + } + } + return deletedCount; + } catch (error) { + throw new Error(`Failed to bulk delete rows in table: ${error.message}`); + } + } + + public async validateSettings(settings: ValidateTableSettingsDS, tableName: string): Promise> { + try { + const [tableStructure, primaryColumns] = await Promise.all([ + this.getTableStructure(tableName), + this.getTablePrimaryColumns(tableName), + ]); + return tableSettingsFieldValidator(tableStructure, primaryColumns, settings); + } catch (error) { + throw new Error(`Failed to validate settings: ${error.message}`); + } + } + + public async getReferencedTableNamesAndColumns(_tableName: string): Promise> { + return []; + } + + public async isView(tableName: string): Promise { + try { + const client = await this.getCassandraClient(); + const keyspace = this.connection.database; + const result = await client.execute( + 'SELECT view_name FROM system_schema.views WHERE keyspace_name = ? AND view_name = ?', + [keyspace, tableName.toLowerCase()], + { prepare: true }, + ); + return result.rows.length > 0; + } catch (error) { + throw new Error(`Failed to check if table is a view: ${error.message}`); + } + } + + public async getTableRowsStream( + tableName: string, + settings: TableSettingsDS, + page: number, + perPage: number, + searchedFieldValue: string, + filteringFields: Array, + ): Promise> { + try { + const result = await this.getRowsFromTable( + tableName, + settings, + page, + perPage, + searchedFieldValue, + filteringFields, + null, + ); + const readable = new Readable({ + objectMode: true, + read() { + for (const row of result.data) { + this.push(row); + } + this.push(null); + }, + }); + return readable; + } catch (error) { + throw new Error(`Failed to get table rows stream: ${error.message}`); + } + } + + public async importCSVInTable(file: Express.Multer.File, tableName: string): Promise { + try { + const tableStructure = await this.getTableStructure(tableName); + const bufferStream = new Readable(); + bufferStream.push(file.buffer); + bufferStream.push(null); + const parser = csv.parse({ columns: true, trim: true }); + const rows = []; + await new Promise((resolve, reject) => { + bufferStream + .pipe(parser) + .on('data', (row) => rows.push(row)) + .on('error', (err) => reject(err)) + .on('end', () => resolve()); + }); + for (const row of rows) { + const processedRow = {}; + for (const field of tableStructure) { + const columnName = field.column_name; + + if (row[columnName] !== undefined) { + let value = row[columnName]; + if (field.data_type === 'uuid' && typeof value === 'string') { + value = value.replace(/^"+|"+$/g, ''); + } else if ( + field.data_type.includes('int') || + field.data_type.includes('float') || + field.data_type.includes('double') + ) { + value = isNaN(parseFloat(value)) ? null : parseFloat(value); + } else if (field.data_type === 'timestamp' && !isNaN(Number(value))) { + const timestamp = Number(value); + value = new Date(timestamp); + } else if (field.data_type.includes('boolean')) { + value = value.toLowerCase() === 'true'; + } + processedRow[columnName] = value; + } + } + + await this.addRowInTable(tableName, processedRow); + } + } catch (error) { + throw new Error(`Failed to import CSV: ${error.message}`); + } + } + + public async executeRawQuery(query: string): Promise>> { + try { + const client = await this.getCassandraClient(); + const result = await client.execute(query, [], { prepare: true }); + return result.rows; + } catch (error) { + throw new Error(`Failed to execute raw query: ${error.message}`); + } + } + + private async getCassandraClient(): Promise { + const cachedClient = LRUStorage.getCassandraClientCache(this.connection); + + if (cachedClient) { + try { + await cachedClient.execute('SELECT key FROM system.local LIMIT 1'); + return cachedClient as cassandra.Client; + } catch (error) { + console.log(`Cached client connection failed: ${error.message}. Creating new connection.`); + try { + await cachedClient.shutdown(); + } catch (_) { + } finally { + LRUStorage.delCassandraClientCache(this.connection); + } + } + } + try { + let contactPoints = [this.connection.host]; + let port = this.connection.port; + if (this.connection.ssh) { + if (!this.connection.sshHost || !this.connection.sshPort || !this.connection.sshUsername) { + throw new Error('SSH connection requires host, port, and username'); + } + const portForSSH = await getPort(); + const tunnelConnection: any = { + host: this.connection.host, + port: this.connection.port, + sshHost: this.connection.sshHost, + sshPort: this.connection.sshPort, + sshUsername: this.connection.sshUsername, + privateSSHKey: this.connection.privateSSHKey, + }; + await getTunnel(tunnelConnection, portForSSH); + contactPoints = ['127.0.0.1']; + port = portForSSH; + } + const authProvider = new cassandra.auth.PlainTextAuthProvider(this.connection.username, this.connection.password); + const clientOptions: cassandra.ClientOptions = { + contactPoints, + localDataCenter: this.connection.dataCenter || undefined, + keyspace: this.connection.database, + authProvider, + protocolOptions: { + port, + }, + }; + if (this.connection.ssl) { + clientOptions.sslOptions = { + rejectUnauthorized: false, + }; + if (this.connection.cert) { + clientOptions.sslOptions.ca = [this.connection.cert]; + } + } + const client = new cassandra.Client(clientOptions); + await client.connect(); + LRUStorage.setCassandraClientCache(this.connection, client); + return client; + } catch (error) { + throw new Error(`Failed to create Cassandra client: ${error.message}`); + } + } +} 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 05a31f264..3dba36e72 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,5 +1,6 @@ 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 { 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'; @@ -26,6 +27,7 @@ export function getDataAccessObject( ConnectionTypesEnum.agent_postgres, ConnectionTypesEnum.agent_ibmdb2, ConnectionTypesEnum.agent_mongodb, + ConnectionTypesEnum.agent_cassandra, ]; if (!connectionParams || connectionParams === null) { throw new Error(ERROR_MESSAGES.CONNECTION_PARAMS_SHOULD_BE_DEFINED); @@ -58,6 +60,9 @@ export function getDataAccessObject( case ConnectionTypesEnum.elasticsearch: const connectionParamsElasticsearch = buildConnectionParams(connectionParams); return new DataAccessObjectElasticsearch(connectionParamsElasticsearch); + case ConnectionTypesEnum.cassandra: + const connectionParamsCassandra = buildConnectionParams(connectionParams); + return new DataAccessObjectCassandra(connectionParamsCassandra); default: if (!agentTypes.includes(connectionParams.type)) { throw new Error(ERROR_MESSAGES.CONNECTION_TYPE_INVALID); @@ -118,6 +123,7 @@ function buildConnectionParams(connectionParams: IUnknownConnectionParams): Conn azure_encryption: connectionParams.azure_encryption || false, signing_key: connectionParams.signing_key || null, authSource: connectionParams.authSource || null, + dataCenter: connectionParams.dataCenter || null, isTestConnection: connectionParams.isTestConnection || false, }; return connection; 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 0d64893a8..9eccb7737 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 @@ -3,7 +3,16 @@ export class ConnectionParams { title: string | null; - type: 'postgres' | 'oracledb' | 'mysql2' | 'mssql' | 'ibmdb2' | 'mongodb' | 'dynamodb' | 'elasticsearch'; + type: + | 'postgres' + | 'oracledb' + | 'mysql2' + | 'mssql' + | 'ibmdb2' + | 'mongodb' + | 'dynamodb' + | 'elasticsearch' + | 'cassandra'; host: string; @@ -39,6 +48,8 @@ export class ConnectionParams { authSource: string | null; + dataCenter?: string | null; + isTestConnection: boolean; } 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 6494d1c5b..956a34380 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 @@ -8,10 +8,12 @@ export enum ConnectionTypesEnum { mongodb = 'mongodb', dynamodb = 'dynamodb', elasticsearch = 'elasticsearch', + cassandra = 'cassandra', agent_postgres = 'agent_postgres', agent_mysql = 'agent_mysql', agent_oracledb = 'agent_oracledb', agent_mssql = 'agent_mssql', agent_ibmdb2 = 'agent_ibmdb2', agent_mongodb = 'agent_mongodb', + agent_cassandra = 'agent_cassandra', } diff --git a/shared-code/src/helpers/errors/error-messages.ts b/shared-code/src/helpers/errors/error-messages.ts index eb87df3fc..fcb3f26d6 100644 --- a/shared-code/src/helpers/errors/error-messages.ts +++ b/shared-code/src/helpers/errors/error-messages.ts @@ -10,4 +10,5 @@ export const ERROR_MESSAGES = { AGENT_SHOULD_BE_DEFINED: `Agent and agent token should be defined`, NO_DATA_RETURNED_FROM_AGENT: `No data returned from agent`, INVALID_OBJECT_ID_FORMAT: `Invalid object id format`, + CLIENT_NOT_CONNECTED: `Client is not connected`, }; diff --git a/yarn.lock b/yarn.lock index b259948a8..3cc96416e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3076,6 +3076,7 @@ __metadata: "@typescript-eslint/eslint-plugin": ^8.32.1 "@typescript-eslint/parser": ^8.32.1 axios: ^1.9.0 + cassandra-driver: ^4.8.0 csv: ^6.3.11 eslint: ^9.27.0 eslint-config-prettier: ^10.1.5 @@ -4808,7 +4809,7 @@ __metadata: languageName: node linkType: hard -"adm-zip@npm:^0.5.16": +"adm-zip@npm:^0.5.16, adm-zip@npm:~0.5.10": version: 0.5.16 resolution: "adm-zip@npm:0.5.16" checksum: 1f4104f3462b99e1b34d78ccfbdcf47e533a9cc7f894cedec6cd67b06cc6ad0b3a45241d66df5471050c7abbdd67e5707e3959fc76d75176ed6101a5b2a580d5 @@ -5915,6 +5916,17 @@ __metadata: languageName: node linkType: hard +"cassandra-driver@npm:^4.8.0": + version: 4.8.0 + resolution: "cassandra-driver@npm:4.8.0" + dependencies: + "@types/node": ^18.11.18 + adm-zip: ~0.5.10 + long: ~5.2.3 + checksum: 29f074bc550e76466b3f7becf4cea1713947e00d37f0c207c695a0fdd00863e3d57c64358a9740b62ea79fb8b4f583ccd87159449de1f1f66150e9276fa0bde3 + languageName: node + linkType: hard + "cbor@npm:^10.0.3": version: 10.0.3 resolution: "cbor@npm:10.0.3" @@ -10082,6 +10094,13 @@ __metadata: languageName: node linkType: hard +"long@npm:~5.2.3": + version: 5.2.5 + resolution: "long@npm:5.2.5" + checksum: e69605f84d24ded688b5654ff41352cbcf816f8201bc9a2cd89dfce38fab0563017ed15d39ec8007f1e09db908f034e8c5b386530fe689f9caa837bf35e75c42 + languageName: node + linkType: hard + "lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": version: 10.2.0 resolution: "lru-cache@npm:10.2.0"