diff --git a/backend/package.json b/backend/package.json index 683c01e25..be367a1bc 100644 --- a/backend/package.json +++ b/backend/package.json @@ -89,6 +89,7 @@ "lodash": "4.17.21", "lru-cache": "^10.1.0", "moment": "2.29.4", + "mongodb": "^6.5.0", "nanoid": "5.0.4", "nest-winston": "1.9.4", "node-gyp": "^10.0.1", diff --git a/backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts b/backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts index f7560b2c2..448314143 100644 --- a/backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts +++ b/backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts @@ -87,7 +87,12 @@ export class ExportCSVFromTableUseCase extends AbstractUseCase = []; +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); +}); + +currentTest = 'GET /connection/tables/:slug'; + +test(`${currentTest} should return list of tables in connection`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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(`${currentTest} should throw an error when connectionId not passed in request`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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(`${currentTest} should throw an error when connection id is incorrect`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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(`${currentTest} should return rows of selected table without search and without pagination`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testTableSecondColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.hasOwnProperty('large_dataset'), true); + t.is(getTableRowsRO.rows.length, Constants.DEFAULT_PAGINATION.perPage); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0].hasOwnProperty('_id'), true); + t.is(getTableRowsRO.rows[1].hasOwnProperty(testTableColumnName), true); + t.is(getTableRowsRO.rows[10].hasOwnProperty(testTableSecondColumnName), true); + t.is(getTableRowsRO.rows[15].hasOwnProperty('created_at'), true); + t.is(getTableRowsRO.rows[19].hasOwnProperty('updated_at'), true); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } +}); + +test(`${currentTest} should return rows of selected table with search and without pagination`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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.find((id) => id.number === 5)._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'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 1); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + t.is(getTableRowsRO.rows[0]._id, 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(`${currentTest} should return page of all rows with pagination page=1, perPage=2`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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, 'string'); + + 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(`${currentTest} should return page of all rows with pagination page=3, perPage=2`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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, 'string'); + + 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(`${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).mongoDbConnection; + 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, 'string'); + + 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(`${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).mongoDbConnection; + 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, 'string'); + + 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(`${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).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 42, + QueryOrderingEnum.DESC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 42); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + // t.is(getTableRowsRO.rows[0]._id, 42); + // t.is(getTableRowsRO.rows[1]._id, 41); + // t.is(getTableRowsRO.rows[41]._id, 1); + + t.is(typeof getTableRowsRO.primaryColumns, 'object'); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true); + t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true); + } catch (e) { + console.error(e); + throw e; + } +}); + +test(`${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).mongoDbConnection; + 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.ASC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + const searchedLastId = insertedSearchedIds.find((id) => id.number === 41)._id; + const searchedFirstId = insertedSearchedIds.find((id) => id.number === 0)._id; + const searchedSecondId = insertedSearchedIds.find((id) => id.number === 1)._id; + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 42); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0]._id, searchedFirstId); + t.is(getTableRowsRO.rows[1]._id, searchedSecondId); + t.is(getTableRowsRO.rows[41]._id, searchedLastId); + + 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(`${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).mongoDbConnection; + 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, + 3, + QueryOrderingEnum.DESC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=2`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + const searchedLastId = insertedSearchedIds.find((id) => id.number === 1)._id; + + const preSearchedLastId = insertedSearchedIds.find((id) => id.number === 0)._id; + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0]._id, preSearchedLastId); + t.is(getTableRowsRO.rows[1]._id, searchedLastId); + + 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(`${currentTest} should return all found rows with sorting ports by ASC and with pagination page=1, perPage=2`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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, + 3, + QueryOrderingEnum.ASC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=2`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + const searchedFirstId = insertedSearchedIds.find((id) => id.number === 0)._id; + + const preSearchedSecondId = insertedSearchedIds.find((id) => id.number === 1)._id; + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0]._id, searchedFirstId); + t.is(getTableRowsRO.rows[1]._id, preSearchedSecondId); + + 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(`${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).mongoDbConnection; + 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, + 3, + QueryOrderingEnum.DESC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=2&perPage=3`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + const searchedFirstId = insertedSearchedIds.find((id) => id.number === 3)._id; + + const searchedSecondId = insertedSearchedIds.find((id) => id.number === 4)._id; + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 3); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0]._id, searchedFirstId); + t.is(getTableRowsRO.rows[1]._id, searchedSecondId); + + 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(`${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).mongoDbConnection; + 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, + 3, + QueryOrderingEnum.DESC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=2&search=${testSearchedUserName}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + const searchedFirstId = insertedSearchedIds.find((id) => id.number === 0)._id; + + const searchedSecondId = insertedSearchedIds.find((id) => id.number === 21)._id; + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[1]).length, 6); + t.is(getTableRowsRO.rows[0]._id, searchedFirstId); + t.is(getTableRowsRO.rows[1]._id, searchedSecondId); + 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(`${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).mongoDbConnection; + 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, + 3, + QueryOrderingEnum.DESC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=2&perPage=2&search=${testSearchedUserName}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + const searchedFirstId = insertedSearchedIds.find((id) => id.number === 37)._id; + + 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, searchedFirstId); + 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(`${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).mongoDbConnection; + 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, + 3, + QueryOrderingEnum.ASC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=2&search=${testSearchedUserName}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(getTableRowsResponse.status, 200); + + const searchedFirstId = insertedSearchedIds.find((id) => id.number === 0)._id; + const searchedSecondId = insertedSearchedIds.find((id) => id.number === 21)._id; + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 2); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + t.is(getTableRowsRO.rows[0]._id, searchedFirstId); + t.is(getTableRowsRO.rows[1]._id, searchedSecondId); + 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(`${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).mongoDbConnection; + 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, + 3, + QueryOrderingEnum.ASC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=2&perPage=2&search=${testSearchedUserName}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + const searchedFirstId = insertedSearchedIds.find((id) => id.number === 37)._id; + + 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, searchedFirstId); + 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; + } +}); + +// todo: rework for other tables after removing old endpoint +test(`${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).mongoDbConnection; + 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, + 3, + QueryOrderingEnum.DESC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'age'; + const fieldvalue = 18; + + const filters = { + [fieldname]: { lt: fieldvalue }, + }; + + const getTableRowsResponse = await request(app.getHttpServer()) + .post( + `/table/rows/find/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2`, + ) + .send({ filters }) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(typeof getTableRowsRO, 'object'); + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + t.is(getTableRowsRO.rows.length, 1); + t.is(Object.keys(getTableRowsRO.rows[0]).length, 6); + + const foundId = insertedSearchedIds.find((id) => id.number === 0)._id; + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[0]._id, foundId); + + 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(`${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).mongoDbConnection; + 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, + 3, + QueryOrderingEnum.DESC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'age'; + const fieldvalue = 18; + + const filters = { + [fieldname]: { lt: fieldvalue }, + }; + + const getTableRowsResponse = await request(app.getHttpServer()) + .post( + `/table/rows/find/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=10`, + ) + .send({ filters }) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 201); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + 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); + + const foundId = insertedSearchedIds.find((id) => id.number === 0)._id; + + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTableRowsRO.rows[0]._id, foundId); + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + + 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(`${currentTest} with search, with pagination, with sorting and with filtering +should return all found rows with search, pagination: page=1, perPage=2 and DESC sorting and with multi filtering`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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, + 3, + QueryOrderingEnum.DESC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = 'age'; + const fieldGtvalue = 14; + const fieldLtvalue = 95; + + const filters = { + [fieldname]: { lt: fieldLtvalue, gt: fieldGtvalue }, + }; + + const getTableRowsResponse = await request(app.getHttpServer()) + .post( + `/table/rows/find/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=3`, + ) + .send({ filters }) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 201); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + 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); + + const findRowId = insertedSearchedIds.find((id) => id.number === 21)._id; + + t.is(getTableRowsRO.rows[0]._id, findRowId); + t.is(getTableRowsRO.rows[0][testTableColumnName], testSearchedUserName); + + t.is(getTableRowsRO.pagination.currentPage, 1); + t.is(getTableRowsRO.pagination.perPage, 3); + + t.is(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(`${currentTest} should throw an exception when connection id is not passed in request`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = '_id'; + const fieldGtvalue = '25'; + const fieldLtvalue = '40'; + createConnectionRO.id = ''; + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}&f_${fieldname}__gt=${fieldGtvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 404); + } catch (e) { + console.error(e); + throw e; + } +}); + +test(`${currentTest} should throw an exception when connection id passed in request is incorrect`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = '_id'; + const fieldGtvalue = '25'; + const fieldLtvalue = '40'; + + createConnectionRO.id = faker.string.uuid(); + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}&f_${fieldname}__gt=${fieldGtvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 400); + + const { message } = JSON.parse(getTableRowsResponse.text); + + t.is(message, Messages.CONNECTION_NOT_FOUND); + } catch (e) { + console.error(e); + throw e; + } +}); + +test(`${currentTest} should throw an exception when table name passed in request is incorrect`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = '_id'; + const fieldGtvalue = '25'; + const fieldLtvalue = '40'; + + const fakeTableName = `${faker.lorem.words(1)}_${faker.string.uuid()}`; + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${fakeTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}&f_${fieldname}__gt=${fieldGtvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + 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(`${currentTest} should return an array with searched fields when filtered name passed in request is incorrect`, async (t) => { + try { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName } = await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const fieldname = faker.lorem.words(1); + const fieldGtvalue = '25'; + const fieldLtvalue = '40'; + + const getTableRowsResponse = await request(app.getHttpServer()) + .get( + `/table/rows/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2&f_${fieldname}__lt=${fieldLtvalue}&f_${fieldname}__gt=${fieldGtvalue}`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTablesRO = JSON.parse(getTableRowsResponse.text); + t.is(getTablesRO.rows.length, 2); + t.is(getTablesRO.rows[0][testTableColumnName], testSearchedUserName); + t.is(getTablesRO.rows[1][testTableColumnName], testSearchedUserName); + t.is(getTablesRO.hasOwnProperty('primaryColumns'), true); + t.is(getTablesRO.hasOwnProperty('pagination'), true); + } catch (e) { + console.error(e); + throw e; + } +}); + +currentTest = 'GET /table/structure/:slug'; +test(`${currentTest} should return table structure`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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(`${currentTest} should throw an exception whe connection id not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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(`${currentTest} should throw an exception whe connection id passed in request id incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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, 400); + const { message } = JSON.parse(getTableStructure.text); + t.is(message, Messages.CONNECTION_NOT_FOUND); +}); + +test(`${currentTest}should throw an exception when tableName not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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(`${currentTest} should throw an exception when tableName passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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 { message } = JSON.parse(getTableStructure.text); + t.is(message, Messages.TABLE_NOT_FOUND); +}); + +currentTest = 'POST /table/row/:slug'; + +test(`${currentTest} should add row in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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 addRowInTableResponse = await request(app.getHttpServer()) + .post(`/table/row/${createConnectionRO.id}?tableName=${testTableName}`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const addRowInTableRO = JSON.parse(addRowInTableResponse.text); + t.is(addRowInTableResponse.status, 201); + + t.is(addRowInTableRO.hasOwnProperty('row'), true); + t.is(addRowInTableRO.hasOwnProperty('structure'), true); + t.is(addRowInTableRO.hasOwnProperty('foreignKeys'), true); + t.is(addRowInTableRO.hasOwnProperty('primaryColumns'), true); + t.is(addRowInTableRO.hasOwnProperty('readonly_fields'), true); + t.is(addRowInTableRO.row[testTableColumnName], row[testTableColumnName]); + t.is(addRowInTableRO.row[testTableSecondColumnName], row[testTableSecondColumnName]); + + //checking that the line was added + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 43); + t.is(rows[42][testTableColumnName], row[testTableColumnName]); + t.is(rows[42][testTableSecondColumnName], row[testTableSecondColumnName]); +}); + +test(`${currentTest} should throw an exception when connection id is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + _id: 999, + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + const fakeConnectionId = ''; + const addRowInTableResponse = await request(app.getHttpServer()) + .post(`/table/row/${fakeConnectionId}?tableName=${testTableName}`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(addRowInTableResponse.status, 404); + + //checking that the line wasn't added + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); +}); + +test(`${currentTest} should throw an exception when table name is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + _id: 999, + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const addRowInTableResponse = await request(app.getHttpServer()) + .post(`/table/row/${createConnectionRO.id}?tableName=`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(addRowInTableResponse.status, 400); + const { message } = JSON.parse(addRowInTableResponse.text); + + t.is(message, Messages.TABLE_NAME_MISSING); + + //checking that the line wasn't added + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 42); +}); + +test(`${currentTest} should throw an exception when row is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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(`${currentTest} should throw an exception when table name passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + _id: 999, + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const fakeTableName = `${faker.lorem.words(1)}_${faker.datatype.number({ 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'); + t.is(addRowInTableResponse.status, 400); + const { message } = JSON.parse(addRowInTableResponse.text); + + 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(`${currentTest} should update row in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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}`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 200); + const updateRowInTableRO = JSON.parse(updateRowInTableResponse.text); + + t.is(updateRowInTableRO.hasOwnProperty('row'), true); + t.is(updateRowInTableRO.hasOwnProperty('structure'), true); + t.is(updateRowInTableRO.hasOwnProperty('foreignKeys'), true); + t.is(updateRowInTableRO.hasOwnProperty('primaryColumns'), true); + t.is(updateRowInTableRO.hasOwnProperty('readonly_fields'), true); + t.is(updateRowInTableRO.row[testTableColumnName], row[testTableColumnName]); + t.is(updateRowInTableRO.row[testTableSecondColumnName], row[testTableSecondColumnName]); + + //checking that the line was updated + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + const updateRowIndex = rows.map((row) => row._id).indexOf(foundIdForUpdate); + t.is(rows.length, 42); + t.is(rows[updateRowIndex][testTableColumnName], row[testTableColumnName]); + t.is(rows[updateRowIndex][testTableSecondColumnName], row[testTableSecondColumnName]); +}); + +test(`${currentTest} should throw an exception when connection id not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + createConnectionRO.id = ''; + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&_id=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 404); +}); + +test(`${currentTest} should throw an exception when connection id passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + createConnectionRO.id = faker.string.uuid(); + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&_id=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 400); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.CONNECTION_NOT_FOUND); +}); + +test(`${currentTest} should throw an exception when tableName not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + createConnectionRO.id = faker.string.uuid(); + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=&_id=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 400); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.TABLE_NAME_MISSING); +}); + +test(`${currentTest} should throw an exception when tableName passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const fakeTableName = faker.string.uuid(); + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}&_id=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 400); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.TABLE_NOT_FOUND); +}); + +test(`${currentTest} should throw an exception when primary key not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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(`${currentTest} should throw an exception when primary key passed in request has incorrect field name`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&IncorrectField=1`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 400); + const { message } = JSON.parse(updateRowInTableResponse.text); + t.is(message, Messages.PRIMARY_KEY_INVALID); +}); + +test(`${currentTest} should throw an exception when primary key passed in request has incorrect field value`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTableSecondColumnName]: fakeMail, + }; + + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&_id=100000000`) + .send(JSON.stringify(row)) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 500); + const responseObject = JSON.parse(updateRowInTableResponse.text); + t.is(responseObject.originalMessage, `Invalid object id format`); +}); + +currentTest = 'PUT /table/rows/update/:connectionId'; + +test(`${currentTest} should update multiple rows and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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 foundFirstIdForUpdate = insertedSearchedIds[0]._id; + const foundSecondIdForUpdate = insertedSearchedIds[1]._id; + const requestData = { + primaryKeys: [{ _id: foundFirstIdForUpdate }, { _id: foundSecondIdForUpdate }], + 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=${foundFirstIdForUpdate}`) + .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=${foundSecondIdForUpdate}`) + .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(`${currentTest} should delete row in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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}`) + .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(`${currentTest} should throw an exception when connection id not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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(`${currentTest} should throw an exception when connection id passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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, 400); + const { message } = JSON.parse(deleteRowInTableResponse.text); + t.is(message, Messages.CONNECTION_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(`${currentTest} should throw an exception when tableName not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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(`${currentTest} should throw an exception when tableName passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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(`${currentTest} should throw an exception when primary key not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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(`${currentTest} should throw an exception when primary key passed in request has incorrect field name`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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(`${currentTest} should throw an exception when primary key passed in request has incorrect field value`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&_id=100000`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const deleteRowInTableRO = JSON.parse(deleteRowInTableResponse.text); + t.is(deleteRowInTableResponse.status, 500); + t.is(deleteRowInTableRO.originalMessage, `Invalid object id format`); +}); + +currentTest = 'GET /table/row/:slug'; + +test(`${currentTest} found row`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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}`) + .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(`${currentTest} should throw an exception, when connection id is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const idForSearch = 1; + createConnectionRO.id = ''; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&_id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 404); +}); + +test(`${currentTest} should throw an exception, when connection id passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1; + createConnectionRO.id = faker.string.uuid(); + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&_id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 400); + const { message } = JSON.parse(foundRowInTableResponse.text); + t.is(message, Messages.CONNECTION_NOT_FOUND); +}); + +test(`${currentTest} should throw an exception, when tableName in not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1; + const fakeTableName = ''; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}&_id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 400); + const { message } = JSON.parse(foundRowInTableResponse.text); + t.is(message, Messages.TABLE_NAME_MISSING); +}); + +test(`${currentTest} should throw an exception, when tableName passed in request is incorrect`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const idForSearch = 1; + const fakeTableName = `${faker.lorem.words(1)}_${faker.string.uuid()}`; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${fakeTableName}&_id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 400); + const { message } = JSON.parse(foundRowInTableResponse.text); + t.is(message, Messages.TABLE_NOT_FOUND); +}); + +test(`${currentTest} should throw an exception, when primary key is not passed in request`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } = + await createTestTable(connectionToTestDB); + + testTables.push(testTableName); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 400); + const { message } = JSON.parse(foundRowInTableResponse.text); + t.is(message, Messages.PRIMARY_KEY_INVALID); +}); + +test(`${currentTest} should throw an exception, when primary key passed in request has incorrect name`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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(`${currentTest} should throw an exception, when primary key passed in request has incorrect value`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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 = '6604197dab8d910eb77783f9'; + const foundRowInTableResponse = await request(app.getHttpServer()) + .get(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&_id=${idForSearch}`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(foundRowInTableResponse.status, 400); + const findRowResponse = JSON.parse(foundRowInTableResponse.text); + t.is(findRowResponse.message, Messages.ROW_PRIMARY_KEY_NOT_FOUND); +}); + +currentTest = 'PUT /table/rows/delete/:slug'; + +test(`${currentTest} should delete row in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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, + }, + { + _id: insertedSearchedIds[9]._id, + }, + { + _id: insertedSearchedIds[31]._id, + }, + ]; + 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 deletaion 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(`${currentTest} should delete row in table and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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, + }, + { + _id: insertedSearchedIds[9]._id, + }, + { + _id: insertedSearchedIds[31]._id, + }, + ]; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/rows/delete/${createConnectionRO.id}?tableName=${testTableName}`) + .send(primaryKeysForDeletion) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteRowInTableResponse.status, 200); + const deleteRowInTableRO = JSON.parse(deleteRowInTableResponse.text); + + //checking that the line was deleted + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsResponse.status, 200); + + t.is(getTableRowsRO.hasOwnProperty('rows'), true); + t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true); + t.is(getTableRowsRO.hasOwnProperty('pagination'), true); + + const { rows, primaryColumns, pagination } = getTableRowsRO; + + t.is(rows.length, 39); + for (const primaryKey of primaryKeysForDeletion) { + t.is( + rows.findIndex((row) => row._id === primaryKey._id), + -1, + ); + } +}); + +test(`${currentTest} should test connection and return result`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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'); +}); + +currentTest = 'GET table/csv/:slug'; + +test(`${currentTest} should return csv file with table data`, async (t) => { + const connectionToTestDB = getTestData(mockFactory).mongoDbConnection; + 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) { + console.log(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); +}); + +test(`${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).mongoDbConnection; + const firstUserToken = (await registerUserAndReturnUserInfo(app)).token; + + const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName, insertedSearchedIds } = + await createTestTable(connectionToTestDB); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToTestDB) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const createTableSettingsDTO = mockFactory.generateTableSettings( + createConnectionRO.id, + testTableName, + [testTableColumnName], + undefined, + undefined, + 3, + QueryOrderingEnum.DESC, + '_id', + undefined, + undefined, + undefined, + undefined, + undefined, + ); + + const createTableSettingsResponse = await request(app.getHttpServer()) + .post(`/settings?connectionId=${createConnectionRO.id}&tableName=${testTableName}`) + .send(createTableSettingsDTO) + .set('Cookie', firstUserToken) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(createTableSettingsResponse.status, 201); + + const getTableCsvResponse = await request(app.getHttpServer()) + .post( + `/table/csv/export/${createConnectionRO.id}?tableName=${testTableName}&search=${testSearchedUserName}&page=1&perPage=2`, + ) + .set('Cookie', firstUserToken) + .set('Content-Type', 'text/csv') + .set('Accept', 'text/csv'); + + if (getTableCsvResponse.status !== 201) { + console.log(getTableCsvResponse.text); + } + t.is(getTableCsvResponse.status, 201); + const fileName = `${testTableName}.csv`; + const downloadedFilePatch = join(__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); +}); diff --git a/backend/test/mock.factory.ts b/backend/test/mock.factory.ts index 3fda5d4c8..a60e2dbfe 100644 --- a/backend/test/mock.factory.ts +++ b/backend/test/mock.factory.ts @@ -128,6 +128,19 @@ export class MockFactory { return dto; } + generateConnectionToTestMongoDBInDocker() { + const dto = new CreateConnectionDto() as any; + dto.title = 'Test connection to MongoDB in Docker'; + dto.type = 'mongodb'; + dto.host = 'test-mongo-e2e-testing' + dto.port = 27017; + dto.database = 'admin', + dto.username = 'root', + dto.password = 'example', + dto.ssh = false; + return dto; + } + generateEncryptedConnectionToTestPostgresDBInDocker() { const dto = new CreateConnectionDto() as any; dto.title = 'Test connection to Postgres in Docker'; diff --git a/backend/test/utils/create-test-table.ts b/backend/test/utils/create-test-table.ts index f34064ddb..f8bf031f4 100644 --- a/backend/test/utils/create-test-table.ts +++ b/backend/test/utils/create-test-table.ts @@ -4,6 +4,7 @@ import { getRandomConstraintName, getRandomTestTableName } from './get-random-te import { getTestKnex } from './get-test-knex.js'; import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/enums/connection-types-enum.js'; import ibmdb, { Database } from 'ibm_db'; +import { MongoClient, Db, ObjectId } from 'mongodb'; export async function createTestTable( connectionParams: any, @@ -13,6 +14,11 @@ export async function createTestTable( if (connectionParams.type === ConnectionTypesEnum.ibmdb2) { return createTestTableIbmDb2(connectionParams, testEntitiesSeedsCount, testSearchedUserName); } + + if (connectionParams.type === ConnectionTypesEnum.mongodb) { + return createTestMongoTable(connectionParams, testEntitiesSeedsCount, testSearchedUserName); + } + const testTableName = getRandomTestTableName(); const testTableColumnName = `${faker.lorem.words(1)}_${faker.lorem.words(1)}`; const testTableSecondColumnName = `${faker.lorem.words(1)}_${faker.lorem.words(1)}`; @@ -74,7 +80,7 @@ async function createTestTableIbmDb2( const schemaExists = await ibmDatabase.query(queryCheckSchemaExists); if (!schemaExists.length || !schemaExists[0]['1']) { - let queryCreateSchema = `CREATE SCHEMA ${connectionParams.schema}`; + const queryCreateSchema = `CREATE SCHEMA ${connectionParams.schema}`; try { await ibmDatabase.query(queryCreateSchema); } catch (error) { @@ -135,11 +141,71 @@ async function createTestTableIbmDb2( }; } +async function createTestMongoTable( + connectionParams, + testEntitiesSeedsCount, + testSearchedUserName, +): Promise { + const testTableName = getRandomTestTableName(); + const testTableColumnName = `${faker.lorem.words(1)}_${faker.lorem.words(1)}`; + const testTableSecondColumnName = `${faker.lorem.words(1)}_${faker.lorem.words(1)}`; + + const mongoConnectionString = + `mongodb://${connectionParams.username}` + + `:${connectionParams.password}` + + `@${connectionParams.host}` + + `:${connectionParams.port}` + + `/${connectionParams.database}`; + + const client = new MongoClient(mongoConnectionString); + await client.connect(); + const db = client.db(connectionParams.database); + const collection = db.collection(testTableName); + + await collection.drop(); + const insertedSearchedIds = []; + for (let i = 0; i < testEntitiesSeedsCount; i++) { + if (i === 0 || i === testEntitiesSeedsCount - 21 || i === testEntitiesSeedsCount - 5) { + const insertionResult = await collection.insertOne({ + [testTableColumnName]: testSearchedUserName, + [testTableSecondColumnName]: faker.internet.email(), + created_at: new Date(), + updated_at: new Date(), + age: i === 0 ? 14 : i === testEntitiesSeedsCount - 21 ? 90 : 95, + }); + insertedSearchedIds.push({ + number: i, + _id: insertionResult.insertedId.toHexString(), + }); + } else { + const insertionResult = await collection.insertOne({ + [testTableColumnName]: faker.person.firstName(), + [testTableSecondColumnName]: faker.internet.email(), + created_at: new Date(), + updated_at: new Date(), + age: faker.number.int({ min: 16, max: 80 }), + }); + insertedSearchedIds.push({ + number: i, + _id: insertionResult.insertedId.toHexString(), + }); + } + } + return { + testTableName: testTableName, + testTableColumnName: testTableColumnName, + testTableSecondColumnName: testTableSecondColumnName, + testEntitiesSeedsCount: testEntitiesSeedsCount, + insertedSearchedIds, + }; +} + export type CreatedTableInfo = { testTableName: string; testTableColumnName: string; testTableSecondColumnName: string; testEntitiesSeedsCount: number; + insertedSearchedIds?: Array<{ number: number; _id: string }>; }; export async function createTestTableForMSSQLWithChema( diff --git a/backend/test/utils/get-test-data.ts b/backend/test/utils/get-test-data.ts index bb62a8bac..d160c1e4e 100644 --- a/backend/test/utils/get-test-data.ts +++ b/backend/test/utils/get-test-data.ts @@ -29,6 +29,7 @@ export function getTestData(mockFactory: MockFactory) { const connectionToIbmDb2 = mockFactory.generateConnectionToTestDbIbmDb2(); const connectionToAgentIbmDB2 = mockFactory.generateConnectionToTestDbIbmDb2Agent(); const ibmDb2CliConnection = mockFactory.generateConnectionToTestDbIbmDb2Cli(); + const mongoDbConnection = mockFactory.generateConnectionToTestMongoDBInDocker(); return { newConnection, newEncryptedConnection, @@ -58,5 +59,6 @@ export function getTestData(mockFactory: MockFactory) { connectionToIbmDb2, connectionToAgentIbmDB2, ibmDb2CliConnection, + mongoDbConnection, }; } diff --git a/docker-compose-test.yml b/docker-compose-test.yml deleted file mode 100644 index eab99d498..000000000 --- a/docker-compose-test.yml +++ /dev/null @@ -1,295 +0,0 @@ -version: "3.9" -services: - backend: - build: - context: . - ports: - - 3000:3000 - env_file: ./backend/.development.env - volumes: - - ./backend/dist:/app/dist - - ./backend/src:/app/src - - ./backend/src/migrations:/app/src/migrations - depends_on: - - postgres - - testDB - - testMySQL-e2e-testing - - testPg-e2e-testing - - mssql-e2e-testing - - test-oracle-e2e-testing - - test-ibm-db2-e2e-testing - links: - - postgres - - testDB - - testMySQL-e2e-testing - - testPg-e2e-testing - - mssql-e2e-testing - - test-oracle-e2e-testing - - test-ibm-db2-e2e-testing - command: ["yarn", "start"] - - testDB: - image: postgres - ports: - - 8000:8000 - environment: - POSTGRES_PASSWORD: 123 - command: postgres -c 'max_connections=300' - - testMySQL-e2e-testing: - image: mysql:8.0.23 - ports: - - 3308:3306 - environment: - MYSQL_ROOT_PASSWORD: 123 - MYSQL_DATABASE: testDB - - testPg-e2e-testing: - image: postgres - ports: - - 9002:5432 - environment: - POSTGRES_PASSWORD: 123 - command: postgres -c 'max_connections=300' - - postgres: - image: postgres - ports: - - 5432:5432 - environment: - POSTGRES_PASSWORD: abc123 - command: postgres -c 'max_connections=300' - - mssql-e2e-testing: - image: mcr.microsoft.com/mssql/server:2019-latest - environment: - - SA_PASSWORD=yNuXf@6T#BgoQ%U6knMp - - ACCEPT_EULA=Y - ports: - - "5434:1433" - - test-oracle-e2e-testing: - image: gvenzl/oracle-xe - ports: - - 1521:1521 - environment: - ORACLE_PASSWORD: 12345 - - autoadmin-ws-server: - build: - context: autoadmin-ws-server - ports: - - 8008:8008 - env_file: ./autoadmin-ws-server/.ws-server-development.env - volumes: - - ./autoadmin-ws-server/dist:/app/dist - - ./autoadmin-ws-server/src:/app/src - links: - - backend - depends_on: - - backend - - rocketadmin-agent_oracle: - build: - context: . - dockerfile: ./rocketadmin-agent/Dockerfile - ports: - - 8088:8088 - volumes: - - ./rocketadmin-agent/dist:/app/dist - - ./rocketadmin-agent/src:/app/src - links: - - autoadmin-ws-server - depends_on: - - autoadmin-ws-server - environment: - - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - - APPLICATION_CONFIG_FILE_NAME=.oracle_test_agent_config.txt - command: ["yarn", "start:dev"] - - rocketadmin-agent_postgres: - build: - context: . - dockerfile: ./rocketadmin-agent/Dockerfile - ports: - - 8098:8098 - volumes: - - ./rocketadmin-agent/dist:/app/dist - - ./rocketadmin-agent/src:/app/src - links: - - autoadmin-ws-server - depends_on: - - autoadmin-ws-server - environment: - - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - - APPLICATION_CONFIG_FILE_NAME=.postgres_test_agent_config.txt - command: ["yarn", "start:dev"] - - rocketadmin-agent_mysql: - build: - context: . - dockerfile: ./rocketadmin-agent/Dockerfile - ports: - - 8108:8108 - volumes: - - ./rocketadmin-agent/dist:/app/dist - - ./rocketadmin-agent/src:/app/src - links: - - autoadmin-ws-server - depends_on: - - autoadmin-ws-server - environment: - - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - - APPLICATION_CONFIG_FILE_NAME=.mysql_test_agent_config.txt - command: ["yarn", "start:dev"] - - rocketadmin-agent_mssql: - build: - context: . - dockerfile: ./rocketadmin-agent/Dockerfile - ports: - - 8118:8118 - volumes: - - ./rocketadmin-agent/dist:/app/dist - - ./rocketadmin-agent/src:/app/src - links: - - autoadmin-ws-server - depends_on: - - autoadmin-ws-server - environment: - - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - - APPLICATION_CONFIG_FILE_NAME=.mssql_test_agent_config.txt - command: ["yarn", "start:dev"] - - rocketadmin-agent_ibmdb2: - build: - context: . - dockerfile: ./rocketadmin-agent/Dockerfile - ports: - - 8308:8308 - volumes: - - ./rocketadmin-agent/dist:/app/dist - - ./rocketadmin-agent/src:/app/src - - ./rocketadmin-agent/wait-for-db2.sh:/app/wait-for-db2.sh - links: - - autoadmin-ws-server - depends_on: - - autoadmin-ws-server - environment: - - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - - APPLICATION_CONFIG_FILE_NAME=.ibmdb2_test_agent_config.txt - command: ["/bin/sh", "/app/wait-for-db2.sh"] - - rocketadmin-cli_oracle: - build: - context: . - dockerfile: ./rocketadmin-cli/Dockerfile - ports: - - 8288:8288 - volumes: - - ./rocketadmin-cli/dist:/app/dist - - ./rocketadmin-cli/src:/app/src - links: - - autoadmin-ws-server - depends_on: - - autoadmin-ws-server - environment: - - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - - APPLICATION_CONFIG_FILE_NAME=.oracle_test_cli_config.txt - command: ["yarn", "start:dev"] - - rocketadmin-cli_postgres: - build: - context: . - dockerfile: ./rocketadmin-cli/Dockerfile - ports: - - 8298:8298 - volumes: - - ./rocketadmin-cli/dist:/app/dist - - ./rocketadmin-cli/src:/app/src - links: - - autoadmin-ws-server - depends_on: - - autoadmin-ws-server - environment: - - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - - APPLICATION_CONFIG_FILE_NAME=.postgres_test_cli_config.txt - command: ["yarn", "start:dev"] - - rocketadmin-cli_mysql: - build: - context: . - dockerfile: ./rocketadmin-cli/Dockerfile - ports: - - 8208:8208 - volumes: - - ./rocketadmin-cli/dist:/app/dist - - ./rocketadmin-cli/src:/app/src - links: - - autoadmin-ws-server - depends_on: - - autoadmin-ws-server - environment: - - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - - APPLICATION_CONFIG_FILE_NAME=.mysql_test_cli_config.txt - command: ["yarn", "start:dev"] - - rocketadmin-cli_mssql: - build: - context: . - dockerfile: ./rocketadmin-cli/Dockerfile - ports: - - 8218:8218 - volumes: - - ./rocketadmin-cli/dist:/app/dist - - ./rocketadmin-cli/src:/app/src - links: - - autoadmin-ws-server - depends_on: - - autoadmin-ws-server - environment: - - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - - APPLICATION_CONFIG_FILE_NAME=.mssql_test_cli_config.txt - command: ["yarn", "start:dev"] - - rocketadmin-cli_ibmdb2: - build: - context: . - dockerfile: ./rocketadmin-cli/Dockerfile - ports: - - 8228:8228 - volumes: - - ./rocketadmin-cli/dist:/app/dist - - ./rocketadmin-cli/src:/app/src - - ./rocketadmin-cli/wait-for-db2.sh:/app/wait-for-db2.sh - links: - - autoadmin-ws-server - depends_on: - - autoadmin-ws-server - environment: - - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 - - APPLICATION_CONFIG_FILE_NAME=.ibmdb2_test_cli_config.txt - command: ["/bin/sh", "/app/wait-for-db2.sh"] - - test-ibm-db2-e2e-testing: - image: icr.io/db2_community/db2 - restart: always - privileged: true - environment: - - LICENSE=accept - - DB2INSTANCE=db2inst1 - - DB2INST1_PASSWORD=password - - DBNAME=testdb - - BLU=false - - ENABLE_ORACLE_COMPATIBILITY=false - - UPDATEAVAIL=NO - - TO_CREATE_SAMPLEDB=true - - REPODB=false - - IS_OSXFS=false - - PERSISTENT_HOME=true - - HADR_ENABLED=false - - ETCD_ENDPOINT= - - ETCD_USERNAME= - - ETCD_PASSWORD= - ports: - - 50000:50000 diff --git a/docker-compose.tst.yml b/docker-compose.tst.yml index 99b0aefce..eab99d498 100644 --- a/docker-compose.tst.yml +++ b/docker-compose.tst.yml @@ -1,4 +1,295 @@ -version: '2' +version: "3.9" services: backend: - command: ["sh", "-c", "yarn test"] + build: + context: . + ports: + - 3000:3000 + env_file: ./backend/.development.env + volumes: + - ./backend/dist:/app/dist + - ./backend/src:/app/src + - ./backend/src/migrations:/app/src/migrations + depends_on: + - postgres + - testDB + - testMySQL-e2e-testing + - testPg-e2e-testing + - mssql-e2e-testing + - test-oracle-e2e-testing + - test-ibm-db2-e2e-testing + links: + - postgres + - testDB + - testMySQL-e2e-testing + - testPg-e2e-testing + - mssql-e2e-testing + - test-oracle-e2e-testing + - test-ibm-db2-e2e-testing + command: ["yarn", "start"] + + testDB: + image: postgres + ports: + - 8000:8000 + environment: + POSTGRES_PASSWORD: 123 + command: postgres -c 'max_connections=300' + + testMySQL-e2e-testing: + image: mysql:8.0.23 + ports: + - 3308:3306 + environment: + MYSQL_ROOT_PASSWORD: 123 + MYSQL_DATABASE: testDB + + testPg-e2e-testing: + image: postgres + ports: + - 9002:5432 + environment: + POSTGRES_PASSWORD: 123 + command: postgres -c 'max_connections=300' + + postgres: + image: postgres + ports: + - 5432:5432 + environment: + POSTGRES_PASSWORD: abc123 + command: postgres -c 'max_connections=300' + + mssql-e2e-testing: + image: mcr.microsoft.com/mssql/server:2019-latest + environment: + - SA_PASSWORD=yNuXf@6T#BgoQ%U6knMp + - ACCEPT_EULA=Y + ports: + - "5434:1433" + + test-oracle-e2e-testing: + image: gvenzl/oracle-xe + ports: + - 1521:1521 + environment: + ORACLE_PASSWORD: 12345 + + autoadmin-ws-server: + build: + context: autoadmin-ws-server + ports: + - 8008:8008 + env_file: ./autoadmin-ws-server/.ws-server-development.env + volumes: + - ./autoadmin-ws-server/dist:/app/dist + - ./autoadmin-ws-server/src:/app/src + links: + - backend + depends_on: + - backend + + rocketadmin-agent_oracle: + build: + context: . + dockerfile: ./rocketadmin-agent/Dockerfile + ports: + - 8088:8088 + volumes: + - ./rocketadmin-agent/dist:/app/dist + - ./rocketadmin-agent/src:/app/src + links: + - autoadmin-ws-server + depends_on: + - autoadmin-ws-server + environment: + - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 + - APPLICATION_CONFIG_FILE_NAME=.oracle_test_agent_config.txt + command: ["yarn", "start:dev"] + + rocketadmin-agent_postgres: + build: + context: . + dockerfile: ./rocketadmin-agent/Dockerfile + ports: + - 8098:8098 + volumes: + - ./rocketadmin-agent/dist:/app/dist + - ./rocketadmin-agent/src:/app/src + links: + - autoadmin-ws-server + depends_on: + - autoadmin-ws-server + environment: + - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 + - APPLICATION_CONFIG_FILE_NAME=.postgres_test_agent_config.txt + command: ["yarn", "start:dev"] + + rocketadmin-agent_mysql: + build: + context: . + dockerfile: ./rocketadmin-agent/Dockerfile + ports: + - 8108:8108 + volumes: + - ./rocketadmin-agent/dist:/app/dist + - ./rocketadmin-agent/src:/app/src + links: + - autoadmin-ws-server + depends_on: + - autoadmin-ws-server + environment: + - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 + - APPLICATION_CONFIG_FILE_NAME=.mysql_test_agent_config.txt + command: ["yarn", "start:dev"] + + rocketadmin-agent_mssql: + build: + context: . + dockerfile: ./rocketadmin-agent/Dockerfile + ports: + - 8118:8118 + volumes: + - ./rocketadmin-agent/dist:/app/dist + - ./rocketadmin-agent/src:/app/src + links: + - autoadmin-ws-server + depends_on: + - autoadmin-ws-server + environment: + - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 + - APPLICATION_CONFIG_FILE_NAME=.mssql_test_agent_config.txt + command: ["yarn", "start:dev"] + + rocketadmin-agent_ibmdb2: + build: + context: . + dockerfile: ./rocketadmin-agent/Dockerfile + ports: + - 8308:8308 + volumes: + - ./rocketadmin-agent/dist:/app/dist + - ./rocketadmin-agent/src:/app/src + - ./rocketadmin-agent/wait-for-db2.sh:/app/wait-for-db2.sh + links: + - autoadmin-ws-server + depends_on: + - autoadmin-ws-server + environment: + - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 + - APPLICATION_CONFIG_FILE_NAME=.ibmdb2_test_agent_config.txt + command: ["/bin/sh", "/app/wait-for-db2.sh"] + + rocketadmin-cli_oracle: + build: + context: . + dockerfile: ./rocketadmin-cli/Dockerfile + ports: + - 8288:8288 + volumes: + - ./rocketadmin-cli/dist:/app/dist + - ./rocketadmin-cli/src:/app/src + links: + - autoadmin-ws-server + depends_on: + - autoadmin-ws-server + environment: + - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 + - APPLICATION_CONFIG_FILE_NAME=.oracle_test_cli_config.txt + command: ["yarn", "start:dev"] + + rocketadmin-cli_postgres: + build: + context: . + dockerfile: ./rocketadmin-cli/Dockerfile + ports: + - 8298:8298 + volumes: + - ./rocketadmin-cli/dist:/app/dist + - ./rocketadmin-cli/src:/app/src + links: + - autoadmin-ws-server + depends_on: + - autoadmin-ws-server + environment: + - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 + - APPLICATION_CONFIG_FILE_NAME=.postgres_test_cli_config.txt + command: ["yarn", "start:dev"] + + rocketadmin-cli_mysql: + build: + context: . + dockerfile: ./rocketadmin-cli/Dockerfile + ports: + - 8208:8208 + volumes: + - ./rocketadmin-cli/dist:/app/dist + - ./rocketadmin-cli/src:/app/src + links: + - autoadmin-ws-server + depends_on: + - autoadmin-ws-server + environment: + - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 + - APPLICATION_CONFIG_FILE_NAME=.mysql_test_cli_config.txt + command: ["yarn", "start:dev"] + + rocketadmin-cli_mssql: + build: + context: . + dockerfile: ./rocketadmin-cli/Dockerfile + ports: + - 8218:8218 + volumes: + - ./rocketadmin-cli/dist:/app/dist + - ./rocketadmin-cli/src:/app/src + links: + - autoadmin-ws-server + depends_on: + - autoadmin-ws-server + environment: + - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 + - APPLICATION_CONFIG_FILE_NAME=.mssql_test_cli_config.txt + command: ["yarn", "start:dev"] + + rocketadmin-cli_ibmdb2: + build: + context: . + dockerfile: ./rocketadmin-cli/Dockerfile + ports: + - 8228:8228 + volumes: + - ./rocketadmin-cli/dist:/app/dist + - ./rocketadmin-cli/src:/app/src + - ./rocketadmin-cli/wait-for-db2.sh:/app/wait-for-db2.sh + links: + - autoadmin-ws-server + depends_on: + - autoadmin-ws-server + environment: + - REMOTE_WEBSOCKET_ADDRESS=ws://autoadmin-ws-server:8009 + - APPLICATION_CONFIG_FILE_NAME=.ibmdb2_test_cli_config.txt + command: ["/bin/sh", "/app/wait-for-db2.sh"] + + test-ibm-db2-e2e-testing: + image: icr.io/db2_community/db2 + restart: always + privileged: true + environment: + - LICENSE=accept + - DB2INSTANCE=db2inst1 + - DB2INST1_PASSWORD=password + - DBNAME=testdb + - BLU=false + - ENABLE_ORACLE_COMPATIBILITY=false + - UPDATEAVAIL=NO + - TO_CREATE_SAMPLEDB=true + - REPODB=false + - IS_OSXFS=false + - PERSISTENT_HOME=true + - HADR_ENABLED=false + - ETCD_ENDPOINT= + - ETCD_USERNAME= + - ETCD_PASSWORD= + ports: + - 50000:50000 diff --git a/docker-compose.yml b/docker-compose.yml index 3d183fc96..e3f9d23fd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: - mssql-e2e-testing - test-oracle-e2e-testing - test-ibm-db2-e2e-testing + - test-mongo-e2e-testing links: - postgres - testDB @@ -26,6 +27,7 @@ services: - mssql-e2e-testing - test-oracle-e2e-testing - test-ibm-db2-e2e-testing + - test-mongo-e2e-testing command: ["yarn", "start"] testDB: @@ -316,3 +318,12 @@ services: - ETCD_PASSWORD= ports: - 50000:50000 + + test-mongo-e2e-testing: + image: mongo + restart: always + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: example + ports: + - 27017:27017 diff --git a/justfile b/justfile index 0907c8c1c..de52e37d9 100644 --- a/justfile +++ b/justfile @@ -1,2 +1,2 @@ test: - docker compose -f docker-compose-test.yml -f docker-compose.tst.yml up --abort-on-container-exit --force-recreate --build + docker compose -f docker-compose.tst.yml up --abort-on-container-exit --force-recreate --build diff --git a/rocketadmin-agent/package.json b/rocketadmin-agent/package.json index 2903bd6b2..7eaf15bf1 100644 --- a/rocketadmin-agent/package.json +++ b/rocketadmin-agent/package.json @@ -36,6 +36,7 @@ "get-port": "^7.0.0", "ibm_db": "^3.2.3", "knex": "2.4.2", + "mongodb": "^6.5.0", "mysql2": "^3.4.2", "nest-winston": "^1.9.3", "oracledb": "^6.0.1", diff --git a/rocketadmin-cli/package.json b/rocketadmin-cli/package.json index 245c5c6ee..1dcea4f6d 100644 --- a/rocketadmin-cli/package.json +++ b/rocketadmin-cli/package.json @@ -32,6 +32,7 @@ "get-port": "^7.0.0", "ibm_db": "^3.2.3", "knex": "^2.4.2", + "mongodb": "^6.5.0", "mysql2": "^3.4.2", "nest-winston": "^1.9.3", "oracledb": "^6.0.1", diff --git a/shared-code/src/data-access-layer/data-access-objects/data-access-object-mongodb.ts b/shared-code/src/data-access-layer/data-access-objects/data-access-object-mongodb.ts new file mode 100644 index 000000000..4f41cb39b --- /dev/null +++ b/shared-code/src/data-access-layer/data-access-objects/data-access-object-mongodb.ts @@ -0,0 +1,382 @@ +import { Stream } from 'stream'; +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 { IDataAccessObject } from '../shared/interfaces/data-access-object.interface.js'; +import { BasicDataAccessObject } from './basic-data-access-object.js'; +import { MongoClient, Db, ObjectId } from 'mongodb'; +import { DAO_CONSTANTS } from '../../helpers/data-access-objects-constants.js'; +import { FilterCriteriaEnum } from '../shared/enums/filter-criteria.enum.js'; +import { tableSettingsFieldValidator } from '../../helpers/validation/table-settings-validator.js'; +import { ERROR_MESSAGES } from '../../helpers/errors/error-messages.js'; + +export class DataAccessObjectMongo extends BasicDataAccessObject implements IDataAccessObject { + constructor(connection: ConnectionParams) { + super(connection); + } + + public async addRowInTable( + tableName: string, + row: Record, + ): Promise> { + const db = await this.getConnectionToDatabase(); + const collection = db.collection(tableName); + const result = await collection.insertOne(row); + return { _id: result.insertedId.toHexString() }; + } + + public async deleteRowInTable( + tableName: string, + primaryKey: Record, + ): Promise> { + const db = await this.getConnectionToDatabase(); + const collection = db.collection(tableName); + const objectId = this.createObjectIdFromSting(primaryKey._id as string); + await collection.deleteOne({ _id: objectId }); + return { _id: objectId.toHexString() }; + } + + public async getIdentityColumns( + tableName: string, + referencedFieldName: string, + identityColumnName: string, + fieldValues: (string | number)[], + ): Promise[]> { + return []; + } + + public async getRowByPrimaryKey( + tableName: string, + primaryKey: Record, + settings: TableSettingsDS, + ): Promise> { + const db = await this.getConnectionToDatabase(); + const collection = db.collection(tableName); + const objectId = this.createObjectIdFromSting(primaryKey._id as string); + + let availableFields: string[] = []; + if (settings) { + const tableStructure = await this.getTableStructure(tableName); + availableFields = this.findAvailableFields(settings, tableStructure); + } + + const foundRow = await collection.findOne({ _id: objectId }); + if (!foundRow) { + return null; + } + + const rowKeys = Object.keys(foundRow); + if (availableFields.length > 0) { + for (const key of rowKeys) { + if (!availableFields.includes(key)) { + delete foundRow[key]; + } + } + } + return { + ...foundRow, + _id: objectId.toHexString(), + }; + } + + public async getRowsFromTable( + tableName: string, + settings: TableSettingsDS, + page: number, + perPage: number, + searchedFieldValue: string, + filteringFields: FilteringFieldsDS[], + 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; + + const offset = (page - 1) * perPage; + const db = await this.getConnectionToDatabase(); + const collection = db.collection(tableName); + + const tableStructure = await this.getTableStructure(tableName); + const availableFields = this.findAvailableFields(settings, tableStructure); + + if (autocompleteFields?.value && autocompleteFields.fields?.length > 0) { + const { fields, value } = autocompleteFields; + const query = fields.reduce((acc, field) => { + acc[field] = new RegExp(String(value), 'i'); + return acc; + }, {}); + const rows = await collection.find(query).limit(DAO_CONSTANTS.AUTOCOMPLETE_ROW_LIMIT).toArray(); + const { large_dataset } = await this.getRowsCount(tableName, query); + return { + data: rows.map((row) => { + Object.keys(row).forEach((key) => { + if (!availableFields.includes(key)) { + delete row[key]; + } + }); + return { + ...row, + _id: row._id.toHexString(), + }; + }), + large_dataset, + pagination: {} as any, + }; + } + + const query = {}; + + let { search_fields } = settings; + if ((!search_fields || search_fields?.length === 0) && searchedFieldValue) { + search_fields = availableFields; + } + + if (searchedFieldValue && search_fields?.length > 0) { + const searchQuery = search_fields.reduce((acc, field) => { + let condition; + if (field === '_id') { + condition = { [field]: this.createObjectIdFromSting(searchedFieldValue) }; + } else { + condition = { [field]: new RegExp('^' + String(searchedFieldValue), 'i') }; + } + acc.push(condition); + return acc; + }, []); + Object.assign(query, { $or: searchQuery }); + } + + if (filteringFields?.length > 0) { + const groupedFilters = filteringFields.reduce((acc, filterObject) => { + let { field, criteria, value } = filterObject; + if (field === '_id') { + value = this.createObjectIdFromSting(value as string); + } + if (!acc[field]) { + acc[field] = {}; + } + switch (criteria) { + case FilterCriteriaEnum.eq: + acc[field] = value; + break; + case FilterCriteriaEnum.contains: + acc[field] = new RegExp(String(value), 'i'); + break; + case FilterCriteriaEnum.gt: + acc[field]['$gt'] = value; + break; + case FilterCriteriaEnum.lt: + acc[field]['$lt'] = value; + break; + case FilterCriteriaEnum.gte: + acc[field]['$gte'] = value; + break; + case FilterCriteriaEnum.lte: + acc[field]['$lte'] = value; + break; + case FilterCriteriaEnum.icontains: + acc[field]['$not'] = new RegExp(String(value), 'i'); + break; + case FilterCriteriaEnum.startswith: + acc[field] = new RegExp(`^${String(value)}`, 'i'); + break; + case FilterCriteriaEnum.endswith: + acc[field] = new RegExp(`${String(value)}$`, 'i'); + break; + case FilterCriteriaEnum.empty: + acc[field]['$exists'] = false; + break; + default: + break; + } + return acc; + }, {}); + + Object.assign(query, groupedFilters); + } + + const { large_dataset, rowsCount } = await this.getRowsCount(tableName, query); + const rows = await collection.find(query).skip(offset).limit(perPage).toArray(); + const pagination = { + total: rowsCount, + lastPage: Math.ceil(rowsCount / perPage), + perPage: perPage, + currentPage: page, + }; + return { + data: rows.map((row) => { + Object.keys(row).forEach((key) => { + if (!availableFields.includes(key)) { + delete row[key]; + } + }); + return { + ...row, + _id: row._id.toHexString(), + }; + }), + pagination, + large_dataset, + }; + } + + public async getTableForeignKeys(tableName: string): Promise { + return []; + } + + public async getTablePrimaryColumns(tableName: string): Promise { + return [ + { + column_name: '_id', + data_type: 'string', + }, + ]; + } + + public async getTablesFromDB(): Promise { + const db = await this.getConnectionToDatabase(); + const collections = await db.listCollections().toArray(); + return collections.map((collection) => { + return { + tableName: collection.name, + isView: false, + }; + }); + } + + public async getTableStructure(tableName: string): Promise { + const db = await this.getConnectionToDatabase(); + const collection = db.collection(tableName); + const document = await collection.findOne({}); + const structure: TableStructureDS[] = Object.keys(document).map((key) => ({ + allow_null: document[key] === null, + character_maximum_length: null, + column_default: key === '_id' ? 'autoincrement' : null, + column_name: key, + data_type: typeof document[key], + data_type_params: null, + udt_name: null, + extra: null, + })); + + return structure; + } + + public async testConnect(): Promise { + try { + await this.getConnectionToDatabase(); + return { + result: true, + message: 'Successfully connected', + }; + } catch (error) { + return { + result: false, + message: error.message, + }; + } + } + + public async updateRowInTable( + tableName: string, + row: Record, + primaryKey: Record, + ): Promise> { + const db = await this.getConnectionToDatabase(); + const collection = db.collection(tableName); + const objectId = this.createObjectIdFromSting(primaryKey._id as string); + await collection.updateOne({ _id: objectId }, { $set: row }); + return { _id: objectId.toHexString() }; + } + + public async bulkUpdateRowsInTable( + tableName: string, + newValues: Record, + primaryKeys: Record[], + ): Promise> { + const db = await this.getConnectionToDatabase(); + const collection = db.collection(tableName); + const objectIds = primaryKeys.map((primaryKey) => this.createObjectIdFromSting(primaryKey._id as string)); + await collection.updateMany({ _id: { $in: objectIds } }, { $set: newValues }); + return { _id: objectIds.map((objectId) => objectId.toHexString()) }; + } + + public async validateSettings(settings: ValidateTableSettingsDS, tableName: string): Promise { + const [tableStructure, primaryColumns] = await Promise.all([ + this.getTableStructure(tableName), + this.getTablePrimaryColumns(tableName), + ]); + return tableSettingsFieldValidator(tableStructure, primaryColumns, settings); + } + + public async getReferencedTableNamesAndColumns(tableName: string): Promise { + return []; + } + + public async isView(tableName: string): Promise { + return false; + } + + public async getTableRowsStream( + tableName: string, + settings: TableSettingsDS, + page: number, + perPage: number, + searchedFieldValue: string, + filteringFields: FilteringFieldsDS[], + ): Promise> { + const result = await this.getRowsFromTable( + tableName, + settings, + page, + perPage, + searchedFieldValue, + filteringFields, + null, + ); + return result.data as any; + } + + private async getConnectionToDatabase(): Promise { + const mongoConnectionString = + `mongodb://${this.connection.username}` + + `:${this.connection.password}` + + `@${this.connection.host}` + + `:${this.connection.port}` + + `/${this.connection.database}`; + + const client = new MongoClient(mongoConnectionString); + await client.connect(); + return client.db(this.connection.database); + } + + private async getRowsCount( + tableName: string, + query: Record, + ): Promise<{ rowsCount: number; large_dataset: boolean }> { + const db = await this.getConnectionToDatabase(); + const collection = db.collection(tableName); + const count = await collection.countDocuments(query); + return { rowsCount: count, large_dataset: count > DAO_CONSTANTS.LARGE_DATASET_ROW_LIMIT }; + } + + private createObjectIdFromSting(id: string): ObjectId { + try { + return new ObjectId(id); + } catch (error) { + throw new Error(ERROR_MESSAGES.INVALID_OBJECT_ID_FORMAT); + } + } +} 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 965b4e879..3ff01d8ea 100644 --- a/shared-code/src/data-access-layer/shared/create-data-access-object.ts +++ b/shared-code/src/data-access-layer/shared/create-data-access-object.ts @@ -1,6 +1,7 @@ import { ERROR_MESSAGES } from '../../helpers/errors/error-messages.js'; import { DataAccessObjectAgent } from '../data-access-objects/data-access-object-agent.js'; import { DataAccessObjectIbmDb2 } from '../data-access-objects/data-access-object-ibmdb2.js'; +import { DataAccessObjectMongo } from '../data-access-objects/data-access-object-mongodb.js'; import { DataAccessObjectMssql } from '../data-access-objects/data-access-object-mssql.js'; import { DataAccessObjectMysql } from '../data-access-objects/data-access-object-mysql.js'; import { DataAccessObjectOracle } from '../data-access-objects/data-access-object-oracle.js'; @@ -54,6 +55,9 @@ export function getDataAccessObject( case ConnectionTypesEnum.ibmdb2: const connectionParamsToIbmDB2 = buildConnectionParams(connectionParams); return new DataAccessObjectIbmDb2(connectionParamsToIbmDB2); + case ConnectionTypesEnum.mongodb: + const connectionParamsMongo = buildConnectionParams(connectionParams); + return new DataAccessObjectMongo(connectionParamsMongo); default: if (!agentTypes.includes(connectionParams.type)) { throw new Error(ERROR_MESSAGES.CONNECTION_TYPE_INVALID); 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 bca04088d..394ee44b7 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 @@ -5,9 +5,11 @@ export enum ConnectionTypesEnum { oracledb = 'oracledb', mssql = 'mssql', ibmdb2 = 'ibmdb2', + mongodb = 'mongodb', agent_postgres = 'agent_postgres', agent_mysql = 'agent_mysql', agent_oracledb = 'agent_oracledb', agent_mssql = 'agent_mssql', agent_ibmdb2 = 'agent_ibmdb2', + agent_mongodb = 'agent_mongodb', } diff --git a/shared-code/src/helpers/errors/error-messages.ts b/shared-code/src/helpers/errors/error-messages.ts index 0d4fef188..eb87df3fc 100644 --- a/shared-code/src/helpers/errors/error-messages.ts +++ b/shared-code/src/helpers/errors/error-messages.ts @@ -9,4 +9,5 @@ export const ERROR_MESSAGES = { CONNECTION_TYPE_INVALID: `Connection type is invalid`, 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`, }; diff --git a/yarn.lock b/yarn.lock index 8b3f7ae96..7d9ee195f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1237,6 +1237,15 @@ __metadata: languageName: node linkType: hard +"@mongodb-js/saslprep@npm:^1.1.5": + version: 1.1.5 + resolution: "@mongodb-js/saslprep@npm:1.1.5" + dependencies: + sparse-bitfield: ^3.0.3 + checksum: 8e38bc604707933959f34576ea3db667b14ef6ca604e42ae4a825d3dab00149fc1b78cb83a542c01016f3c9072872210e78610005565e1fd25d8df78415a159b + languageName: node + linkType: hard + "@nestjs/cli@npm:^10.0.5, @nestjs/cli@npm:^10.2.1": version: 10.2.1 resolution: "@nestjs/cli@npm:10.2.1" @@ -2445,6 +2454,22 @@ __metadata: languageName: node linkType: hard +"@types/webidl-conversions@npm:*": + version: 7.0.3 + resolution: "@types/webidl-conversions@npm:7.0.3" + checksum: 535ead9de4d3d6c8e4f4fa14e9db780d2a31e8020debc062f337e1420a41c3265e223e4f4b628f97a11ecf3b96390962cd88a9ffe34f44e159dec583ff49aa34 + languageName: node + linkType: hard + +"@types/whatwg-url@npm:^11.0.2": + version: 11.0.4 + resolution: "@types/whatwg-url@npm:11.0.4" + dependencies: + "@types/webidl-conversions": "*" + checksum: 5acd60dbd49df34b9131ea75dc5e24b03e084cc4ed5a273ddd801fdaf21c896abbbb846bb3ba71ffd5c715120bac0454debe569f39b9177887fb7636d65cdae4 + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.0 resolution: "@types/yargs-parser@npm:21.0.0" @@ -3465,6 +3490,7 @@ __metadata: lodash: 4.17.21 lru-cache: ^10.1.0 moment: 2.29.4 + mongodb: ^6.5.0 nanoid: 5.0.4 nest-winston: 1.9.4 node-gyp: ^10.0.1 @@ -3901,6 +3927,13 @@ __metadata: languageName: node linkType: hard +"bson@npm:^6.4.0": + version: 6.5.0 + resolution: "bson@npm:6.5.0" + checksum: 1746f6c2f92d3c644edec8ecfef53c9f7fda21aa5e6648369193bd97b614447be027eea57eb1181f667223b7cba86dcbfad4edf8883f2a113aa2ef42db78b305 + languageName: node + linkType: hard + "buffer-alloc-unsafe@npm:^1.1.0": version: 1.1.0 resolution: "buffer-alloc-unsafe@npm:1.1.0" @@ -8508,6 +8541,13 @@ __metadata: languageName: node linkType: hard +"memory-pager@npm:^1.0.2": + version: 1.5.0 + resolution: "memory-pager@npm:1.5.0" + checksum: d1a2e684583ef55c61cd3a49101da645b11ad57014dfc565e0b43baa9004b743f7e4ab81493d8fff2ab24e9950987cc3209c94bcc4fc8d7e30a475489a1f15e9 + languageName: node + linkType: hard + "merge-descriptors@npm:1.0.1": version: 1.0.1 resolution: "merge-descriptors@npm:1.0.1" @@ -8773,6 +8813,50 @@ __metadata: languageName: node linkType: hard +"mongodb-connection-string-url@npm:^3.0.0": + version: 3.0.0 + resolution: "mongodb-connection-string-url@npm:3.0.0" + dependencies: + "@types/whatwg-url": ^11.0.2 + whatwg-url: ^13.0.0 + checksum: 9d4885377345b14e2ba2ee63cb3085364a01aeae3aca622bbd568e7761caa58fdc664528f19f125c762cff230c9349f99e98396dd2271a4260286cf241a8c477 + languageName: node + linkType: hard + +"mongodb@npm:^6.5.0": + version: 6.5.0 + resolution: "mongodb@npm:6.5.0" + dependencies: + "@mongodb-js/saslprep": ^1.1.5 + bson: ^6.4.0 + mongodb-connection-string-url: ^3.0.0 + peerDependencies: + "@aws-sdk/credential-providers": ^3.188.0 + "@mongodb-js/zstd": ^1.1.0 + gcp-metadata: ^5.2.0 + kerberos: ^2.0.1 + mongodb-client-encryption: ">=6.0.0 <7" + snappy: ^7.2.2 + socks: ^2.7.1 + peerDependenciesMeta: + "@aws-sdk/credential-providers": + optional: true + "@mongodb-js/zstd": + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true + checksum: 5774dfdd02d8d8e6bb70bf870f19bfad332da612fa6685d182b2e09da4f1306995ddf54f13ae0f6c3936e74444741689c04e7c009e7a847473f08f522ad16542 + languageName: node + linkType: hard + "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" @@ -10043,7 +10127,7 @@ __metadata: languageName: node linkType: hard -"punycode@npm:^2.1.0": +"punycode@npm:^2.1.0, punycode@npm:^2.3.0": version: 2.3.1 resolution: "punycode@npm:2.3.1" checksum: bb0a0ceedca4c3c57a9b981b90601579058903c62be23c5e8e843d2c2d4148a3ecf029d5133486fb0e1822b098ba8bba09e89d6b21742d02fa26bda6441a6fb2 @@ -10454,6 +10538,7 @@ __metadata: ibm_db: ^3.2.3 jest: ^29.5.0 knex: 2.4.2 + mongodb: ^6.5.0 mysql2: ^3.4.2 nest-winston: ^1.9.3 oracledb: ^6.0.1 @@ -10503,6 +10588,7 @@ __metadata: get-port: ^7.0.0 ibm_db: ^3.2.3 knex: ^2.4.2 + mongodb: ^6.5.0 mysql2: ^3.4.2 nest-winston: ^1.9.3 oracledb: ^6.0.1 @@ -10966,6 +11052,15 @@ __metadata: languageName: node linkType: hard +"sparse-bitfield@npm:^3.0.3": + version: 3.0.3 + resolution: "sparse-bitfield@npm:3.0.3" + dependencies: + memory-pager: ^1.0.2 + checksum: 174da88dbbcc783d5dbd26921931cc83830280b8055fb05333786ebe6fc015b9601b24972b3d55920dd2d9f5fb120576fbfa2469b08e5222c9cadf3f05210aab + languageName: node + linkType: hard + "split-on-first@npm:^3.0.0": version: 3.0.0 resolution: "split-on-first@npm:3.0.0" @@ -11574,6 +11669,15 @@ __metadata: languageName: node linkType: hard +"tr46@npm:^4.1.1": + version: 4.1.1 + resolution: "tr46@npm:4.1.1" + dependencies: + punycode: ^2.3.0 + checksum: aeeb821ac2cd792e63ec84888b4fd6598ac6ed75d861579e21a5cf9d4ee78b2c6b94e7d45036f2ca2088bc85b9b46560ad23c4482979421063b24137349dbd96 + languageName: node + linkType: hard + "tr46@npm:~0.0.3": version: 0.0.3 resolution: "tr46@npm:0.0.3" @@ -12201,6 +12305,13 @@ __metadata: languageName: node linkType: hard +"webidl-conversions@npm:^7.0.0": + version: 7.0.0 + resolution: "webidl-conversions@npm:7.0.0" + checksum: f05588567a2a76428515333eff87200fae6c83c3948a7482ebb109562971e77ef6dc49749afa58abb993391227c5697b3ecca52018793e0cb4620a48f10bd21b + languageName: node + linkType: hard + "webpack-node-externals@npm:3.0.0": version: 3.0.0 resolution: "webpack-node-externals@npm:3.0.0" @@ -12259,6 +12370,16 @@ __metadata: languageName: node linkType: hard +"whatwg-url@npm:^13.0.0": + version: 13.0.0 + resolution: "whatwg-url@npm:13.0.0" + dependencies: + tr46: ^4.1.1 + webidl-conversions: ^7.0.0 + checksum: 7f69272a1bfd5f0d994988b9e234e35d21071a9bffe0d6fd4477d295552665c566b176ff8e0251a0a79c61c5a67a7a392e248aae5887d7e22bdff0125209e26b + languageName: node + linkType: hard + "whatwg-url@npm:^5.0.0": version: 5.0.0 resolution: "whatwg-url@npm:5.0.0"