diff --git a/backend/test/ava-tests/complex-table-tests/complex-mssql-table-e2e.test.ts b/backend/test/ava-tests/complex-table-tests/complex-mssql-table-e2e.test.ts new file mode 100644 index 000000000..fe8a0ff54 --- /dev/null +++ b/backend/test/ava-tests/complex-table-tests/complex-mssql-table-e2e.test.ts @@ -0,0 +1,232 @@ +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 path from 'path'; +import request from 'supertest'; +import { fileURLToPath } from 'url'; +import { ApplicationModule } from '../../../src/app.module.js'; +import { WinstonLogger } from '../../../src/entities/logging/winston-logger.js'; +import { AllExceptionsFilter } from '../../../src/exceptions/all-exceptions.filter.js'; +import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; +import { Cacher } from '../../../src/helpers/cache/cacher.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 { getTestData } from '../../utils/get-test-data.js'; +import { registerUserAndReturnUserInfo } from '../../utils/register-user-and-return-user-info.js'; +import { + createTestTablesWithComplexPFKeys, + createTestTablesWithSimplePFKeys, +} from '../../utils/test-utilities/create-test-postgres-tables.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 connectionToTestDB = getTestData(mockFactory).connectionToTestMSSQL; + +let testTablesCompositeKeysData: TableCreationResult; +let testTablesSimpleKeysData: TableCreationResult; + +test.before(async () => { + const moduleFixture = await Test.createTestingModule({ + imports: [ApplicationModule, DatabaseModule], + providers: [DatabaseService, TestUtils], + }).compile(); + app = moduleFixture.createNestApplication() as any; + testUtils = moduleFixture.get(TestUtils); + + app.use(cookieParser()); + app.useGlobalFilters(new AllExceptionsFilter(app.get(WinstonLogger))); + app.useGlobalPipes( + new ValidationPipe({ + exceptionFactory(validationErrors: ValidationError[] = []) { + return new ValidationException(validationErrors); + }, + }), + ); + await app.init(); + app.getHttpServer().listen(0); + + testTablesCompositeKeysData = await createTestTablesWithComplexPFKeys(connectionToTestDB); + testTablesSimpleKeysData = await createTestTablesWithSimplePFKeys(connectionToTestDB); +}); + +test.after(async () => { + try { + await Cacher.clearAllCache(); + await app.close(); + } catch (e) { + console.error('After tests error ' + e); + } +}); + +export type TableCreationResult = { + main_table: { + table_name: string; + column_names: string[]; + foreign_key_column_names: string[]; + binary_column_names: string[]; + primary_key_column_names: string[]; + }; + first_referenced_table: { + table_name: string; + column_names: string[]; + primary_key_column_names: string[]; + }; + second_referenced_table: { + table_name: string; + column_names: string[]; + primary_key_column_names: string[]; + foreign_key_column_names: string[]; + }; +}; + +// GET /connection/tables/:slug + +test.serial( + `GET /connection/tables/:slug - Should return list of table rows with referenced columns, when primary and foreign keys has composite structure`, + 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 { main_table, first_referenced_table, second_referenced_table } = testTablesCompositeKeysData; + + const getMainTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${main_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getMainTableRowsRO = JSON.parse(getMainTableRowsResponse.text); + t.is(getMainTableRowsResponse.status, 200); + + const getFirstReferencedTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${first_referenced_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getFirstReferencedTableRowsRO = JSON.parse(getFirstReferencedTableRowsResponse.text); + t.is(getFirstReferencedTableRowsResponse.status, 200); + + const firstReturnedRows = getFirstReferencedTableRowsRO.rows; + + for (const row of firstReturnedRows) { + t.is(typeof row.order_id, 'object'); + t.truthy(row.order_id.hasOwnProperty('order_id')); + t.truthy(row.order_id.order_id); + t.truthy(typeof row.order_id.order_id === 'number'); + t.is(typeof row.customer_id, 'object'); + t.truthy(row.customer_id.hasOwnProperty('customer_id')); + t.truthy(row.customer_id.customer_id); + t.truthy(typeof row.customer_id.customer_id === 'number'); + } + + const getSecondReferencedTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${second_referenced_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getSecondReferencedTableRowsRO = JSON.parse(getSecondReferencedTableRowsResponse.text); + t.is(getSecondReferencedTableRowsResponse.status, 200); + const secondReturnedRows = getSecondReferencedTableRowsRO.rows; + for (const row of secondReturnedRows) { + t.is(typeof row.order_id, 'object'); + t.truthy(row.order_id.hasOwnProperty('order_id')); + t.truthy(row.order_id.order_id); + t.truthy(typeof row.order_id.order_id === 'number'); + t.is(typeof row.customer_id, 'object'); + t.truthy(row.customer_id.hasOwnProperty('customer_id')); + t.truthy(row.customer_id.customer_id); + t.truthy(typeof row.customer_id.customer_id === 'number'); + } + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `GET /connection/tables/:slug - Should return list of table rows with referenced columns, when primary and foreign keys has simple structure`, + 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 { main_table, first_referenced_table, second_referenced_table } = testTablesSimpleKeysData; + + const getMainTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${main_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getMainTableRowsRO = JSON.parse(getMainTableRowsResponse.text); + t.is(getMainTableRowsResponse.status, 200); + + const getFirstReferencedTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${first_referenced_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getFirstReferencedTableRowsRO = JSON.parse(getFirstReferencedTableRowsResponse.text); + t.is(getFirstReferencedTableRowsResponse.status, 200); + + const firstReturnedRows = getFirstReferencedTableRowsRO.rows; + + for (const element of firstReturnedRows) { + t.is(typeof element.customer_id, 'object'); + t.truthy(element.customer_id.hasOwnProperty('customer_id')); + t.truthy(element.customer_id.customer_id); + t.truthy(typeof element.customer_id.customer_id === 'number'); + } + + const getSecondReferencedTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${second_referenced_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getSecondReferencedTableRowsRO = JSON.parse(getSecondReferencedTableRowsResponse.text); + t.is(getSecondReferencedTableRowsResponse.status, 200); + + const secondReturnedRows = getSecondReferencedTableRowsRO.rows; + for (const element of secondReturnedRows) { + t.is(typeof element.order_id, 'object'); + t.truthy(element.order_id.hasOwnProperty('order_id')); + t.truthy(element.order_id.order_id); + t.truthy(typeof element.order_id.order_id === 'number'); + } + } catch (e) { + console.error(e); + throw e; + } + }, +); diff --git a/backend/test/ava-tests/complex-table-tests/complex-mysql-table-e2e.test.ts b/backend/test/ava-tests/complex-table-tests/complex-mysql-table-e2e.test.ts new file mode 100644 index 000000000..60fb23ae8 --- /dev/null +++ b/backend/test/ava-tests/complex-table-tests/complex-mysql-table-e2e.test.ts @@ -0,0 +1,232 @@ +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 path from 'path'; +import request from 'supertest'; +import { fileURLToPath } from 'url'; +import { ApplicationModule } from '../../../src/app.module.js'; +import { WinstonLogger } from '../../../src/entities/logging/winston-logger.js'; +import { AllExceptionsFilter } from '../../../src/exceptions/all-exceptions.filter.js'; +import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; +import { Cacher } from '../../../src/helpers/cache/cacher.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 { getTestData } from '../../utils/get-test-data.js'; +import { registerUserAndReturnUserInfo } from '../../utils/register-user-and-return-user-info.js'; +import { + createTestMySQLTablesWithComplexPFKeys, + createTestMySQLTablesWithSimplePFKeys, +} from '../../utils/test-utilities/create-test-mysql-tables.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 connectionToTestDB = getTestData(mockFactory).connectionToMySQL; + +let testTablesCompositeKeysData: TableCreationResult; +let testTablesSimpleKeysData: TableCreationResult; + +test.before(async () => { + const moduleFixture = await Test.createTestingModule({ + imports: [ApplicationModule, DatabaseModule], + providers: [DatabaseService, TestUtils], + }).compile(); + app = moduleFixture.createNestApplication() as any; + testUtils = moduleFixture.get(TestUtils); + + app.use(cookieParser()); + app.useGlobalFilters(new AllExceptionsFilter(app.get(WinstonLogger))); + app.useGlobalPipes( + new ValidationPipe({ + exceptionFactory(validationErrors: ValidationError[] = []) { + return new ValidationException(validationErrors); + }, + }), + ); + await app.init(); + app.getHttpServer().listen(0); + + testTablesCompositeKeysData = await createTestMySQLTablesWithComplexPFKeys(connectionToTestDB); + testTablesSimpleKeysData = await createTestMySQLTablesWithSimplePFKeys(connectionToTestDB); +}); + +test.after(async () => { + try { + await Cacher.clearAllCache(); + await app.close(); + } catch (e) { + console.error('After tests error ' + e); + } +}); + +export type TableCreationResult = { + main_table: { + table_name: string; + column_names: string[]; + foreign_key_column_names: string[]; + binary_column_names: string[]; + primary_key_column_names: string[]; + }; + first_referenced_table: { + table_name: string; + column_names: string[]; + primary_key_column_names: string[]; + }; + second_referenced_table: { + table_name: string; + column_names: string[]; + primary_key_column_names: string[]; + foreign_key_column_names: string[]; + }; +}; + +// GET /connection/tables/:slug + +test.serial( + `GET /connection/tables/:slug - Should return list of table rows with referenced columns, when primary and foreign keys has composite structure`, + 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 { main_table, first_referenced_table, second_referenced_table } = testTablesCompositeKeysData; + + const getMainTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${main_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getMainTableRowsRO = JSON.parse(getMainTableRowsResponse.text); + t.is(getMainTableRowsResponse.status, 200); + + const getFirstReferencedTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${first_referenced_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getFirstReferencedTableRowsRO = JSON.parse(getFirstReferencedTableRowsResponse.text); + t.is(getFirstReferencedTableRowsResponse.status, 200); + + const firstReturnedRows = getFirstReferencedTableRowsRO.rows; + + for (const row of firstReturnedRows) { + t.is(typeof row.order_id, 'object'); + t.truthy(row.order_id.hasOwnProperty('order_id')); + t.truthy(row.order_id.order_id); + t.truthy(typeof row.order_id.order_id === 'number'); + t.is(typeof row.customer_id, 'object'); + t.truthy(row.customer_id.hasOwnProperty('customer_id')); + t.truthy(row.customer_id.customer_id); + t.truthy(typeof row.customer_id.customer_id === 'number'); + } + + const getSecondReferencedTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${second_referenced_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getSecondReferencedTableRowsRO = JSON.parse(getSecondReferencedTableRowsResponse.text); + t.is(getSecondReferencedTableRowsResponse.status, 200); + const secondReturnedRows = getSecondReferencedTableRowsRO.rows; + for (const row of secondReturnedRows) { + t.is(typeof row.order_id, 'object'); + t.truthy(row.order_id.hasOwnProperty('order_id')); + t.truthy(row.order_id.order_id); + t.truthy(typeof row.order_id.order_id === 'number'); + t.is(typeof row.customer_id, 'object'); + t.truthy(row.customer_id.hasOwnProperty('customer_id')); + t.truthy(row.customer_id.customer_id); + t.truthy(typeof row.customer_id.customer_id === 'number'); + } + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `GET /connection/tables/:slug - Should return list of table rows with referenced columns, when primary and foreign keys has simple structure`, + 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 { main_table, first_referenced_table, second_referenced_table } = testTablesSimpleKeysData; + + const getMainTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${main_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getMainTableRowsRO = JSON.parse(getMainTableRowsResponse.text); + t.is(getMainTableRowsResponse.status, 200); + + const getFirstReferencedTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${first_referenced_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getFirstReferencedTableRowsRO = JSON.parse(getFirstReferencedTableRowsResponse.text); + t.is(getFirstReferencedTableRowsResponse.status, 200); + + const firstReturnedRows = getFirstReferencedTableRowsRO.rows; + + for (const element of firstReturnedRows) { + t.is(typeof element.customer_id, 'object'); + t.truthy(element.customer_id.hasOwnProperty('customer_id')); + t.truthy(element.customer_id.customer_id); + t.truthy(typeof element.customer_id.customer_id === 'number'); + } + + const getSecondReferencedTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${second_referenced_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getSecondReferencedTableRowsRO = JSON.parse(getSecondReferencedTableRowsResponse.text); + t.is(getSecondReferencedTableRowsResponse.status, 200); + + const secondReturnedRows = getSecondReferencedTableRowsRO.rows; + for (const element of secondReturnedRows) { + t.is(typeof element.order_id, 'object'); + t.truthy(element.order_id.hasOwnProperty('order_id')); + t.truthy(element.order_id.order_id); + t.truthy(typeof element.order_id.order_id === 'number'); + } + } catch (e) { + console.error(e); + throw e; + } + }, +); diff --git a/backend/test/ava-tests/complex-table-tests/complex-oracle-table-e2e.test.ts b/backend/test/ava-tests/complex-table-tests/complex-oracle-table-e2e.test.ts new file mode 100644 index 000000000..58f43f8d8 --- /dev/null +++ b/backend/test/ava-tests/complex-table-tests/complex-oracle-table-e2e.test.ts @@ -0,0 +1,232 @@ +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 path from 'path'; +import request from 'supertest'; +import { fileURLToPath } from 'url'; +import { ApplicationModule } from '../../../src/app.module.js'; +import { WinstonLogger } from '../../../src/entities/logging/winston-logger.js'; +import { AllExceptionsFilter } from '../../../src/exceptions/all-exceptions.filter.js'; +import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; +import { Cacher } from '../../../src/helpers/cache/cacher.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 { getTestData } from '../../utils/get-test-data.js'; +import { registerUserAndReturnUserInfo } from '../../utils/register-user-and-return-user-info.js'; +import { + createTestTablesWithComplexPFKeys, + createTestTablesWithSimplePFKeys, +} from '../../utils/test-utilities/create-test-postgres-tables.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 connectionToTestDB = getTestData(mockFactory).connectionToOracleDB; + +let testTablesCompositeKeysData: TableCreationResult; +let testTablesSimpleKeysData: TableCreationResult; + +test.before(async () => { + const moduleFixture = await Test.createTestingModule({ + imports: [ApplicationModule, DatabaseModule], + providers: [DatabaseService, TestUtils], + }).compile(); + app = moduleFixture.createNestApplication() as any; + testUtils = moduleFixture.get(TestUtils); + + app.use(cookieParser()); + app.useGlobalFilters(new AllExceptionsFilter(app.get(WinstonLogger))); + app.useGlobalPipes( + new ValidationPipe({ + exceptionFactory(validationErrors: ValidationError[] = []) { + return new ValidationException(validationErrors); + }, + }), + ); + await app.init(); + app.getHttpServer().listen(0); + + testTablesCompositeKeysData = await createTestTablesWithComplexPFKeys(connectionToTestDB); + testTablesSimpleKeysData = await createTestTablesWithSimplePFKeys(connectionToTestDB); +}); + +test.after(async () => { + try { + await Cacher.clearAllCache(); + await app.close(); + } catch (e) { + console.error('After tests error ' + e); + } +}); + +export type TableCreationResult = { + main_table: { + table_name: string; + column_names: string[]; + foreign_key_column_names: string[]; + binary_column_names: string[]; + primary_key_column_names: string[]; + }; + first_referenced_table: { + table_name: string; + column_names: string[]; + primary_key_column_names: string[]; + }; + second_referenced_table: { + table_name: string; + column_names: string[]; + primary_key_column_names: string[]; + foreign_key_column_names: string[]; + }; +}; + +// GET /connection/tables/:slug + +test.serial( + `GET /connection/tables/:slug - Should return list of table rows with referenced columns, when primary and foreign keys has composite structure`, + 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 { main_table, first_referenced_table, second_referenced_table } = testTablesCompositeKeysData; + + const getMainTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${main_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getMainTableRowsRO = JSON.parse(getMainTableRowsResponse.text); + t.is(getMainTableRowsResponse.status, 200); + + const getFirstReferencedTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${first_referenced_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getFirstReferencedTableRowsRO = JSON.parse(getFirstReferencedTableRowsResponse.text); + t.is(getFirstReferencedTableRowsResponse.status, 200); + + const firstReturnedRows = getFirstReferencedTableRowsRO.rows; + + for (const row of firstReturnedRows) { + t.is(typeof row.order_id, 'object'); + t.truthy(row.order_id.hasOwnProperty('order_id')); + t.truthy(row.order_id.order_id); + t.truthy(typeof row.order_id.order_id === 'number'); + t.is(typeof row.customer_id, 'object'); + t.truthy(row.customer_id.hasOwnProperty('customer_id')); + t.truthy(row.customer_id.customer_id); + t.truthy(typeof row.customer_id.customer_id === 'number'); + } + + const getSecondReferencedTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${second_referenced_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getSecondReferencedTableRowsRO = JSON.parse(getSecondReferencedTableRowsResponse.text); + t.is(getSecondReferencedTableRowsResponse.status, 200); + const secondReturnedRows = getSecondReferencedTableRowsRO.rows; + for (const row of secondReturnedRows) { + t.is(typeof row.order_id, 'object'); + t.truthy(row.order_id.hasOwnProperty('order_id')); + t.truthy(row.order_id.order_id); + t.truthy(typeof row.order_id.order_id === 'number'); + t.is(typeof row.customer_id, 'object'); + t.truthy(row.customer_id.hasOwnProperty('customer_id')); + t.truthy(row.customer_id.customer_id); + t.truthy(typeof row.customer_id.customer_id === 'number'); + } + } catch (e) { + console.error(e); + throw e; + } + }, +); + +test.serial( + `GET /connection/tables/:slug - Should return list of table rows with referenced columns, when primary and foreign keys has simple structure`, + 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 { main_table, first_referenced_table, second_referenced_table } = testTablesSimpleKeysData; + + const getMainTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${main_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getMainTableRowsRO = JSON.parse(getMainTableRowsResponse.text); + t.is(getMainTableRowsResponse.status, 200); + + const getFirstReferencedTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${first_referenced_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getFirstReferencedTableRowsRO = JSON.parse(getFirstReferencedTableRowsResponse.text); + t.is(getFirstReferencedTableRowsResponse.status, 200); + + const firstReturnedRows = getFirstReferencedTableRowsRO.rows; + + for (const element of firstReturnedRows) { + t.is(typeof element.customer_id, 'object'); + t.truthy(element.customer_id.hasOwnProperty('customer_id')); + t.truthy(element.customer_id.customer_id); + t.truthy(typeof element.customer_id.customer_id === 'number'); + } + + const getSecondReferencedTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${second_referenced_table.table_name}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getSecondReferencedTableRowsRO = JSON.parse(getSecondReferencedTableRowsResponse.text); + t.is(getSecondReferencedTableRowsResponse.status, 200); + + const secondReturnedRows = getSecondReferencedTableRowsRO.rows; + for (const element of secondReturnedRows) { + t.is(typeof element.order_id, 'object'); + t.truthy(element.order_id.hasOwnProperty('order_id')); + t.truthy(element.order_id.order_id); + t.truthy(typeof element.order_id.order_id === 'number'); + } + } catch (e) { + console.error(e); + throw e; + } + }, +); diff --git a/backend/test/ava-tests/complex-table-tests/complex-postgres-table-e2e.test.ts b/backend/test/ava-tests/complex-table-tests/complex-postgres-table-e2e.test.ts index b284193be..c9e3d023d 100644 --- a/backend/test/ava-tests/complex-table-tests/complex-postgres-table-e2e.test.ts +++ b/backend/test/ava-tests/complex-table-tests/complex-postgres-table-e2e.test.ts @@ -1,34 +1,26 @@ -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 path 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 { WinstonLogger } from '../../../src/entities/logging/winston-logger.js'; import { AllExceptionsFilter } from '../../../src/exceptions/all-exceptions.filter.js'; import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; -import { Messages } from '../../../src/exceptions/text/messages.js'; import { Cacher } from '../../../src/helpers/cache/cacher.js'; -import { Constants } from '../../../src/helpers/constants/constants.js'; import { DatabaseModule } from '../../../src/shared/database/database.module.js'; import { DatabaseService } from '../../../src/shared/database/database.service.js'; import { MockFactory } from '../../mock.factory.js'; -import { createTestTable } from '../../utils/create-test-table.js'; import { getTestData } from '../../utils/get-test-data.js'; import { registerUserAndReturnUserInfo } from '../../utils/register-user-and-return-user-info.js'; -import { TestUtils } from '../../utils/test.utils.js'; -import { WinstonLogger } from '../../../src/entities/logging/winston-logger.js'; -import { getTestKnex } from '../../utils/get-test-knex.js'; -import { getRandomTestTableName } from '../../utils/get-random-test-table-name.js'; import { createTestTablesWithComplexPFKeys, createTestTablesWithSimplePFKeys, } from '../../utils/test-utilities/create-test-postgres-tables.js'; +import { TestUtils } from '../../utils/test.utils.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); diff --git a/backend/test/utils/test-utilities/create-test-mysql-tables.ts b/backend/test/utils/test-utilities/create-test-mysql-tables.ts new file mode 100644 index 000000000..26f28fadc --- /dev/null +++ b/backend/test/utils/test-utilities/create-test-mysql-tables.ts @@ -0,0 +1,272 @@ +import { getTestKnex } from '../get-test-knex.js'; +import { faker } from '@faker-js/faker'; + +export const createTestMySQLTablesWithComplexPFKeys = async (connectionParams: any) => { + const connectionParamsCopy = { + ...connectionParams, + }; + if (connectionParams.type === 'mysql') { + connectionParamsCopy.type = 'mysql2'; + } + + const knex = getTestKnex(connectionParamsCopy); + + const mainTableName = 'Orders_Complex'; + const firstReferencedTableName = 'OrderItems_Complex'; + const referencedOnTableName = 'Shipments_Complex'; + + await knex.schema.dropTableIfExists(referencedOnTableName); + await knex.schema.dropTableIfExists(firstReferencedTableName); + await knex.schema.dropTableIfExists(mainTableName); + + await knex.schema.createTable(mainTableName, (table) => { + table.integer('order_id').notNullable(); + table.integer('customer_id').notNullable(); + table.date('order_date'); + table.string('status', 20); + table.decimal('total_amount', 10, 2); + table.primary(['order_id', 'customer_id']); + }); + + const firstReferencedTableQuery = knex.schema.createTable(firstReferencedTableName, (table) => { + table.integer('order_id').notNullable(); + table.integer('customer_id').notNullable(); + table.increments('item_id').primary(); + table.string('product_name', 100); + table.integer('quantity'); + table.decimal('price_per_unit', 10, 2); + table + .foreign(['order_id', 'customer_id']) + .references(['order_id', 'customer_id']) + .inTable(mainTableName) + .onDelete('CASCADE'); + }); + console.log('SQL Query for first_referenced_table:', firstReferencedTableQuery.toString()); + await firstReferencedTableQuery; + + await knex.schema.createTable(referencedOnTableName, (table) => { + table.increments('shipment_id').primary(); + table.integer('order_id'); + table.integer('customer_id'); + table.date('shipped_date'); + table.string('carrier', 50); + table.string('tracking_number', 100); + table + .foreign(['order_id', 'customer_id']) + .references(['order_id', 'customer_id']) + .inTable(mainTableName) + .onDelete('SET NULL'); + }); + + // seeding data in test tables + + const testEntitiesCount = 42; + + const mainTableInserts = []; + const firstReferencedTableInserts = []; + const secondReferencedTableInserts = []; + + for (let i = 0; i < testEntitiesCount; i++) { + const orderId = i + 1; + const customerId = 100 + i; + mainTableInserts.push({ + order_id: orderId, + customer_id: customerId, + order_date: faker.date.past({ years: 1 }), + status: faker.helpers.arrayElement(['Pending', 'Shipped', 'Delivered', 'Cancelled']), + total_amount: parseFloat(faker.commerce.price({ min: 20, max: 500, dec: 2 })), + }); + + const itemsCount = faker.number.int({ min: 1, max: 5 }); + for (let j = 0; j < itemsCount; j++) { + firstReferencedTableInserts.push({ + order_id: orderId, + customer_id: customerId, + product_name: faker.commerce.productName(), + quantity: faker.number.int({ min: 1, max: 10 }), + price_per_unit: parseFloat(faker.commerce.price({ min: 5, max: 100, dec: 2 })), + }); + } + + if (faker.datatype.boolean()) { + secondReferencedTableInserts.push({ + order_id: orderId, + customer_id: customerId, + shipped_date: faker.date.recent({ days: 30 }), + carrier: faker.company.name(), + tracking_number: faker.string.uuid(), + }); + } + } + + await knex(mainTableName).insert(mainTableInserts); + await knex(firstReferencedTableName).insert(firstReferencedTableInserts); + await knex(referencedOnTableName).insert(secondReferencedTableInserts); + + const mainTableColumnData = await knex(mainTableName).columnInfo(); + const firstReferencedTableColumnData = await knex(firstReferencedTableName).columnInfo(); + const secondReferencedTableColumnData = await knex(referencedOnTableName).columnInfo(); + + return { + main_table: { + table_name: mainTableName, + column_names: Object.keys(mainTableColumnData), + foreign_key_column_names: [], + binary_column_names: [], + primary_key_column_names: ['order_id', 'customer_id'], + }, + first_referenced_table: { + table_name: firstReferencedTableName, + column_names: Object.keys(firstReferencedTableColumnData), + primary_key_column_names: ['item_id'], + }, + second_referenced_table: { + table_name: referencedOnTableName, + column_names: Object.keys(secondReferencedTableColumnData), + primary_key_column_names: ['shipment_id'], + foreign_key_column_names: [], + }, + }; +}; + +export const createTestMySQLTablesWithSimplePFKeys = async (connectionParams: any) => { + const connectionParamsCopy = { + ...connectionParams, + }; + if (connectionParams.type === 'mysql') { + connectionParamsCopy.type = 'mysql2'; + } + + const knex = getTestKnex(connectionParamsCopy); + + // -- Table 1: Customers (simple primary key) + // CREATE TABLE customers ( + // customer_id SERIAL PRIMARY KEY, + // name VARCHAR(100), + // email VARCHAR(100), + // created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + // ); + + // -- Table 2: Orders (foreign key to Customers) + // CREATE TABLE orders ( + // order_id SERIAL PRIMARY KEY, + // customer_id INT, + // order_date DATE, + // status VARCHAR(20), + // total_amount DECIMAL(10, 2), + // FOREIGN KEY (customer_id) + // REFERENCES customers(customer_id) + // ON DELETE CASCADE + // ); + + // -- Table 3: Shipments (foreign key to Orders) + // CREATE TABLE shipments ( + // shipment_id SERIAL PRIMARY KEY, + // order_id INT, + // shipped_date DATE, + // carrier VARCHAR(50), + // tracking_number VARCHAR(100), + // FOREIGN KEY (order_id) + // REFERENCES orders(order_id) + // ON DELETE SET NULL + // ); + + const mainTableName = 'Customers_Simple'; + const firstReferencedTableName = 'Orders_Simple'; + const secondReferencedTableName = 'Shipments_Simple'; + + await knex.schema.dropTableIfExists(secondReferencedTableName); + await knex.schema.dropTableIfExists(firstReferencedTableName); + await knex.schema.dropTableIfExists(mainTableName); + + await knex.schema.createTable(mainTableName, (table) => { + table.increments('customer_id').primary(); + table.string('name', 100); + table.string('email', 100); + table.timestamp('created_at').defaultTo(knex.fn.now()); + }); + + await knex.schema.createTable(firstReferencedTableName, (table) => { + table.increments('order_id').primary(); + table.integer('customer_id').unsigned().notNullable(); + table.date('order_date'); + table.string('status', 20); + table.decimal('total_amount', 10, 2); + table.foreign('customer_id').references('customer_id').inTable(mainTableName).onDelete('CASCADE'); + }); + + await knex.schema.createTable(secondReferencedTableName, (table) => { + table.increments('shipment_id').primary(); + table.integer('order_id').unsigned(); + table.date('shipped_date'); + table.string('carrier', 50); + table.string('tracking_number', 100); + table.foreign('order_id').references('order_id').inTable(firstReferencedTableName).onDelete('SET NULL'); + }); + + // seeding data in test tables + + const testEntitiesCount = 42; + + const secondReferencedTableInserts = []; + + const insertedCustomerIds = []; + for (let i = 0; i < testEntitiesCount; i++) { + const [customerId] = await knex(mainTableName).insert({ + name: faker.person.fullName(), + email: faker.internet.email(), + }); + insertedCustomerIds.push(customerId); + } + + const insertedOrderIds = []; + for (let i = 0; i < testEntitiesCount; i++) { + const customerId = insertedCustomerIds[i]; + const [orderId] = await knex(firstReferencedTableName).insert({ + customer_id: customerId, + order_date: faker.date.past({ years: 1 }), + status: faker.helpers.arrayElement(['Pending', 'Shipped', 'Delivered', 'Cancelled']), + total_amount: parseFloat(faker.commerce.price({ min: 20, max: 500, dec: 2 })), + }); + insertedOrderIds.push(orderId); + + if (faker.datatype.boolean()) { + secondReferencedTableInserts.push({ + order_id: orderId, + shipped_date: faker.date.recent({ days: 30 }), + carrier: faker.company.name(), + tracking_number: faker.string.uuid(), + }); + } + } + + if (secondReferencedTableInserts.length > 0) { + await knex(secondReferencedTableName).insert(secondReferencedTableInserts); + } + + const mainTableColumnData = await knex(mainTableName).columnInfo(); + const firstReferencedTableColumnData = await knex(firstReferencedTableName).columnInfo(); + const secondReferencedTableColumnData = await knex(secondReferencedTableName).columnInfo(); + + return { + main_table: { + table_name: mainTableName, + column_names: Object.keys(mainTableColumnData), + foreign_key_column_names: [], + binary_column_names: [], + primary_key_column_names: ['customer_id'], + }, + first_referenced_table: { + table_name: firstReferencedTableName, + column_names: Object.keys(firstReferencedTableColumnData), + primary_key_column_names: ['order_id'], + foreign_key_column_names: ['customer_id'], + }, + second_referenced_table: { + table_name: secondReferencedTableName, + column_names: Object.keys(secondReferencedTableColumnData), + primary_key_column_names: ['shipment_id'], + foreign_key_column_names: ['order_id'], + }, + }; +}; diff --git a/backend/test/utils/test-utilities/create-test-postgres-tables.ts b/backend/test/utils/test-utilities/create-test-postgres-tables.ts index a0d5c1ae2..c7b4a11fd 100644 --- a/backend/test/utils/test-utilities/create-test-postgres-tables.ts +++ b/backend/test/utils/test-utilities/create-test-postgres-tables.ts @@ -43,7 +43,6 @@ export const createTestTablesWithComplexPFKeys = async (connectionParams: any) = .inTable(mainTableName) .onDelete('CASCADE'); }); - console.log('SQL Query for first_referenced_table:', firstReferencedTableQuery.toString()); await firstReferencedTableQuery; // Create second_referenced_table diff --git a/shared-code/src/data-access-layer/data-access-objects/data-access-object-mssql.ts b/shared-code/src/data-access-layer/data-access-objects/data-access-object-mssql.ts index 6dccc43c9..db5b01e79 100644 --- a/shared-code/src/data-access-layer/data-access-objects/data-access-object-mssql.ts +++ b/shared-code/src/data-access-layer/data-access-objects/data-access-object-mssql.ts @@ -259,17 +259,18 @@ export class DataAccessObjectMssql extends BasicDataAccessObject implements IDat const knex = await this.configureKnex(); const schema = await this.getSchemaNameWithoutBrackets(tableName); const foreignKeys = await knex.raw( - `SELECT ccu.constraint_name AS constraint_name - , ccu.column_name AS column_name - , kcu.table_name AS referenced_table_name - , kcu.column_name AS referenced_column_name - FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu + `SELECT fk_kcu.constraint_name AS constraint_name + , fk_kcu.column_name AS column_name + , pk_kcu.table_name AS referenced_table_name + , pk_kcu.column_name AS referenced_column_name + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE fk_kcu INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc - ON ccu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME - INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu - ON kcu.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME - WHERE ccu.TABLE_NAME = ? - AND ccu.TABLE_SCHEMA = ?`, + ON fk_kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME + INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE pk_kcu + ON pk_kcu.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME + AND pk_kcu.ORDINAL_POSITION = fk_kcu.ORDINAL_POSITION + WHERE fk_kcu.TABLE_NAME = ? + AND fk_kcu.TABLE_SCHEMA = ?`, [tableName, schema], );