From a9f4b21f98e34c78f9bcf1697380b2dd31a7b126 Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Sun, 28 May 2023 19:20:17 +0530 Subject: [PATCH] test: text based list api Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- packages/nocodb/tests/unit/factory/column.ts | 161 ++++- packages/nocodb/tests/unit/factory/row.ts | 33 +- packages/nocodb/tests/unit/factory/view.ts | 84 ++- packages/nocodb/tests/unit/init/index.ts | 7 +- packages/nocodb/tests/unit/rest/index.test.ts | 2 + .../tests/unit/rest/tests/newDataApis.test.ts | 653 ++++++++++++++++++ 6 files changed, 894 insertions(+), 46 deletions(-) diff --git a/packages/nocodb/tests/unit/factory/column.ts b/packages/nocodb/tests/unit/factory/column.ts index 1b9617f7b9..27818e0db2 100644 --- a/packages/nocodb/tests/unit/factory/column.ts +++ b/packages/nocodb/tests/unit/factory/column.ts @@ -1,13 +1,13 @@ import { UITypes } from 'nocodb-sdk'; import request from 'supertest'; -import Column from '../../../src/models/Column'; -import FormViewColumn from '../../../src/models/FormViewColumn'; -import GalleryViewColumn from '../../../src/models/GalleryViewColumn'; -import GridViewColumn from '../../../src/models/GridViewColumn'; import Model from '../../../src/models/Model'; -import Project from '../../../src/models/Project'; -import View from '../../../src/models/View'; -import { isSqlite, isPg } from '../init/db'; +import { isPg, isSqlite } from '../init/db'; +import type Column from '../../../src/models/Column'; +import type FormViewColumn from '../../../src/models/FormViewColumn'; +import type GalleryViewColumn from '../../../src/models/GalleryViewColumn'; +import type GridViewColumn from '../../../src/models/GridViewColumn'; +import type Project from '../../../src/models/Project'; +import type View from '../../../src/models/View'; const defaultColumns = function (context) { return [ @@ -46,6 +46,120 @@ const defaultColumns = function (context) { ]; }; +const customColumns = function (type: string) { + switch (type) { + case 'textBased': + return [ + { + column_name: 'Id', + title: 'Id', + uidt: UITypes.ID, + }, + { + column_name: 'SingleLineText', + title: 'SingleLineText', + uidt: UITypes.SingleLineText, + }, + { + column_name: 'MultiLineText', + title: 'MultiLineText', + uidt: UITypes.LongText, + }, + { + column_name: 'Email', + title: 'Email', + uidt: UITypes.Email, + }, + { + column_name: 'Phone', + title: 'Phone', + uidt: UITypes.PhoneNumber, + }, + { + column_name: 'Url', + title: 'Url', + uidt: UITypes.URL, + }, + ]; + case 'numberBased': + return [ + { + column_name: 'Id', + title: 'Id', + uidt: UITypes.ID, + }, + { + column_name: 'Number', + title: 'Number', + uidt: UITypes.Number, + }, + { + column_name: 'Decimal', + title: 'Decimal', + uidt: UITypes.Decimal, + }, + { + column_name: 'Currency', + title: 'Currency', + uidt: UITypes.Currency, + }, + { + column_name: 'Percent', + title: 'Percent', + uidt: UITypes.Percent, + }, + { + column_name: 'Duration', + title: 'Duration', + uidt: UITypes.Duration, + }, + { + column_name: 'Rating', + title: 'Rating', + uidt: UITypes.Rating, + }, + ]; + case 'dateBased': + return [ + { + column_name: 'Id', + title: 'Id', + uidt: UITypes.ID, + }, + { + column_name: 'Date', + title: 'Date', + uidt: UITypes.Date, + }, + { + column_name: 'DateTime', + title: 'DateTime', + uidt: UITypes.DateTime, + }, + ]; + case 'selectBased': + return [ + { + column_name: 'Id', + title: 'Id', + uidt: UITypes.ID, + }, + { + column_name: 'SingleSelect', + title: 'SingleSelect', + uidt: UITypes.SingleSelect, + dtxp: "'jan','feb','mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'", + }, + { + column_name: 'MultiSelect', + title: 'MultiSelect', + uidt: UITypes.MultiSelect, + dtxp: "'jan','feb','mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'", + }, + ]; + } +}; + const createColumn = async (context, table, columnAttr) => { await request(context.app) .post(`/api/v1/db/meta/tables/${table.id}/columns`) @@ -55,7 +169,7 @@ const createColumn = async (context, table, columnAttr) => { }); const column: Column = (await table.getColumns()).find( - (column) => column.title === columnAttr.title + (column) => column.title === columnAttr.title, ); return column; }; @@ -76,7 +190,7 @@ const createRollupColumn = async ( table: Model; relatedTableName: string; relatedTableColumnTitle: string; - } + }, ) => { const childBases = await project.getBases(); const childTable = await Model.getByIdOrName({ @@ -86,13 +200,13 @@ const createRollupColumn = async ( }); const childTableColumns = await childTable.getColumns(); const childTableColumn = await childTableColumns.find( - (column) => column.title === relatedTableColumnTitle + (column) => column.title === relatedTableColumnTitle, ); const ltarColumn = (await table.getColumns()).find( (column) => column.uidt === UITypes.LinkToAnotherRecord && - column.colOptions?.fk_related_model_id === childTable.id + column.colOptions?.fk_related_model_id === childTable.id, ); const rollupColumn = await createColumn(context, table, { @@ -122,7 +236,7 @@ const createLookupColumn = async ( table: Model; relatedTableName: string; relatedTableColumnTitle: string; - } + }, ) => { const childBases = await project.getBases(); const childTable = await Model.getByIdOrName({ @@ -132,19 +246,19 @@ const createLookupColumn = async ( }); const childTableColumns = await childTable.getColumns(); const childTableColumn = await childTableColumns.find( - (column) => column.title === relatedTableColumnTitle + (column) => column.title === relatedTableColumnTitle, ); if (!childTableColumn) { throw new Error( - `Could not find column ${relatedTableColumnTitle} in ${relatedTableName}` + `Could not find column ${relatedTableColumnTitle} in ${relatedTableName}`, ); } const ltarColumn = (await table.getColumns()).find( (column) => column.uidt === UITypes.LinkToAnotherRecord && - column.colOptions?.fk_related_model_id === childTable.id + column.colOptions?.fk_related_model_id === childTable.id, ); const lookupColumn = await createColumn(context, table, { title: title, @@ -168,15 +282,15 @@ const createQrCodeColumn = async ( title: string; table: Model; referencedQrValueTableColumnTitle: string; - } + }, ) => { const referencedQrValueTableColumnId = await table .getColumns() .then( (cols) => cols.find( - (column) => column.title == referencedQrValueTableColumnTitle - )['id'] + (column) => column.title == referencedQrValueTableColumnTitle, + )['id'], ); const qrCodeColumn = await createColumn(context, table, { @@ -198,15 +312,15 @@ const createBarcodeColumn = async ( title: string; table: Model; referencedBarcodeValueTableColumnTitle: string; - } + }, ) => { const referencedBarcodeValueTableColumnId = await table .getColumns() .then( (cols) => cols.find( - (column) => column.title == referencedBarcodeValueTableColumnTitle - )['id'] + (column) => column.title == referencedBarcodeValueTableColumnTitle, + )['id'], ); const barcodeColumn = await createColumn(context, table, { @@ -230,7 +344,7 @@ const createLtarColumn = async ( parentTable: Model; childTable: Model; type: string; - } + }, ) => { const ltarColumn = await createColumn(context, parentTable, { title: title, @@ -246,7 +360,7 @@ const createLtarColumn = async ( const updateViewColumn = async ( context, - { view, column, attr }: { column: Column; view: View; attr: any } + { view, column, attr }: { column: Column; view: View; attr: any }, ) => { const res = await request(context.app) .patch(`/api/v1/db/meta/views/${view.id}/columns/${column.id}`) @@ -263,6 +377,7 @@ const updateViewColumn = async ( }; export { + customColumns, defaultColumns, createColumn, createQrCodeColumn, diff --git a/packages/nocodb/tests/unit/factory/row.ts b/packages/nocodb/tests/unit/factory/row.ts index 1883f185fe..4f4f42abbe 100644 --- a/packages/nocodb/tests/unit/factory/row.ts +++ b/packages/nocodb/tests/unit/factory/row.ts @@ -1,11 +1,12 @@ -import { ColumnType, UITypes } from 'nocodb-sdk'; +import { UITypes } from 'nocodb-sdk'; import request from 'supertest'; -import Column from '../../../src/models/Column'; -import Filter from '../../../src/models/Filter'; import Model from '../../../src/models/Model'; -import Project from '../../../src/models/Project'; -import Sort from '../../../src/models/Sort'; import NcConnectionMgrv2 from '../../../src/utils/common/NcConnectionMgrv2'; +import type { ColumnType } from 'nocodb-sdk'; +import type Column from '../../../src/models/Column'; +import type Filter from '../../../src/models/Filter'; +import type Project from '../../../src/models/Project'; +import type Sort from '../../../src/models/Sort'; const rowValue = (column: ColumnType, index: number) => { switch (column.uidt) { @@ -175,9 +176,15 @@ const rowMixedValue = (column: ColumnType, index: number) => { case UITypes.Date: // set startDate as 400 days before today // eslint-disable-next-line no-case-declarations - const result = new Date(); - result.setDate(result.getDate() - 400 + index); - return result.toISOString().slice(0, 10); + const d1 = new Date(); + d1.setDate(d1.getDate() - 400 + index); + return d1.toISOString().slice(0, 10); + case UITypes.DateTime: + // set startDate as 400 days before today + // eslint-disable-next-line no-case-declarations + const d2 = new Date(); + d2.setDate(d2.getDate() - 400 + index); + return d2.toISOString(); case UITypes.URL: return urls[index % urls.length]; case UITypes.SingleSelect: @@ -228,7 +235,7 @@ const listRow = async ({ const getOneRow = async ( context, - { project, table }: { project: Project; table: Model } + { project, table }: { project: Project; table: Model }, ) => { const response = await request(context.app) .get(`/api/v1/db/data/noco/${project.id}/${table.id}/find-one`) @@ -266,7 +273,7 @@ const createRow = async ( project: Project; table: Model; index?: number; - } + }, ) => { const columns = await table.getColumns(); const rowData = generateDefaultRowAttributes({ columns, index }); @@ -289,7 +296,7 @@ const createBulkRows = async ( project: Project; table: Model; values: any[]; - } + }, ) => { await request(context.app) .post(`/api/v1/db/data/bulk/noco/${project.id}/${table.id}`) @@ -317,7 +324,7 @@ const createChildRow = async ( rowId?: string; childRowId?: string; type: string; - } + }, ) => { if (!rowId) { const row = await createRow(context, { project, table }); @@ -331,7 +338,7 @@ const createChildRow = async ( await request(context.app) .post( - `/api/v1/db/data/noco/${project.id}/${table.id}/${rowId}/${type}/${column.title}/${childRowId}` + `/api/v1/db/data/noco/${project.id}/${table.id}/${rowId}/${type}/${column.title}/${childRowId}`, ) .set('xc-auth', context.token); diff --git a/packages/nocodb/tests/unit/factory/view.ts b/packages/nocodb/tests/unit/factory/view.ts index 578b156423..a6193f9df8 100644 --- a/packages/nocodb/tests/unit/factory/view.ts +++ b/packages/nocodb/tests/unit/factory/view.ts @@ -1,9 +1,20 @@ import { ViewTypes } from 'nocodb-sdk'; import request from 'supertest'; -import Model from '../../../src/models/Model'; import View from '../../../src/models/View'; +import type Model from '../../../src/models/Model'; -const createView = async (context, {title, table, type}: {title: string, table: Model, type: ViewTypes}) => { +const createView = async ( + context, + { + title, + table, + type, + }: { + title: string; + table: Model; + type: ViewTypes; + }, +) => { const viewTypeStr = (type) => { switch (type) { case ViewTypes.GALLERY: @@ -26,13 +37,70 @@ const createView = async (context, {title, table, type}: {title: string, table: title, type, }); - if(response.status !== 200) { - throw new Error('createView',response.body.message); + if (response.status !== 200) { + throw new Error('createView', response.body.message); } - const view = await View.getByTitleOrId({fk_model_id: table.id, titleOrId:title}) as View; + const view = (await View.getByTitleOrId({ + fk_model_id: table.id, + titleOrId: title, + })) as View; + return view; +}; - return view -} +const updateView = async ( + context, + { + table, + view, + filter = [], + sort = [], + field = [], + }: { + table: Model; + view: View; + filter?: any[]; + sort?: any[]; + field?: any[]; + }, +) => { + if (filter.length) { + for (let i = 0; i < filter.length; i++) { + await request(context.app) + .post(`/api/v1/db/meta/views/${view.id}/filters`) + .set('xc-auth', context.token) + .send(filter[i]) + .expect(200); + } + } + + if (sort.length) { + for (let i = 0; i < sort.length; i++) { + await request(context.app) + .post(`/api/v1/db/meta/views/${view.id}/sorts`) + .set('xc-auth', context.token) + .send(sort[i]) + .expect(200); + } + } + + if (field.length) { + for (let i = 0; i < field.length; i++) { + const columns = await table.getColumns(); + const viewColumns = await view.getColumns(); + + const columnId = columns.find((c) => c.title === field[i]).id; + const viewColumnId = viewColumns.find( + (c) => c.fk_column_id === columnId, + ).id; + // configure view to hide selected fields + await request(context.app) + .patch(`/api/v1/db/meta/views/${view.id}/columns/${viewColumnId}`) + .set('xc-auth', context.token) + .send({ show: false }) + .expect(200); + } + } +}; -export {createView} +export { createView, updateView }; diff --git a/packages/nocodb/tests/unit/init/index.ts b/packages/nocodb/tests/unit/init/index.ts index 5081543ed7..f456e25c56 100644 --- a/packages/nocodb/tests/unit/init/index.ts +++ b/packages/nocodb/tests/unit/init/index.ts @@ -25,7 +25,7 @@ const serverInit = async () => { const isFirstTimeRun = () => !server; -export default async function () { +export default async function (isSakila = true) { const { default: TestDbMngr } = await import('../TestDbMngr'); if (isFirstTimeRun()) { @@ -33,7 +33,10 @@ export default async function () { server = await serverInit(); } - await cleanUpSakila(); + if (isSakila) { + await cleanUpSakila(); + } + await cleanupMeta(); const { token } = await createUser({ app: server }, { roles: 'editor' }); diff --git a/packages/nocodb/tests/unit/rest/index.test.ts b/packages/nocodb/tests/unit/rest/index.test.ts index e7954e4a35..469add0a82 100644 --- a/packages/nocodb/tests/unit/rest/index.test.ts +++ b/packages/nocodb/tests/unit/rest/index.test.ts @@ -8,6 +8,7 @@ import tableRowTests from './tests/tableRow.test'; import viewRowTests from './tests/viewRow.test'; import attachmentTests from './tests/attachment.test'; import filterTest from './tests/filter.test'; +import newDataApisTest from './tests/newDataApis.test'; function restTests() { authTests(); @@ -19,6 +20,7 @@ function restTests() { columnTypeSpecificTests(); attachmentTests(); filterTest(); + newDataApisTest(); } export default function () { diff --git a/packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts b/packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts index 552fa916fb..208618307e 100644 --- a/packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts +++ b/packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts @@ -80,3 +80,656 @@ * - invalid table ID * - invalid record ID */ + +import 'mocha'; +import { UITypes, ViewTypes } from 'nocodb-sdk'; +import { expect } from 'chai'; +import request from 'supertest'; +import init from '../../init'; +import { createProject, createSakilaProject } from '../../factory/project'; +import { createTable, getTable } from '../../factory/table'; +import { createBulkRows, listRow, rowMixedValue } from '../../factory/row'; +import { customColumns } from '../../factory/column'; +import { createView, updateView } from '../../factory/view'; +import type { Api } from 'nocodb-sdk'; + +import type { ColumnType } from 'nocodb-sdk'; +import type Project from '../../../../src/models/Project'; +import type Model from '../../../../src/models/Model'; + +let api: Api; + +const debugMode = true; + +let context; +let project: Project; +let table: Model; +let columns: any[]; +let insertedRecords: any[] = []; + +let sakilaProject: Project; +let customerTable: Model; +let customerColumns; + +// Optimisation scope for time reduction +// 1. BeforeEach can be changed to BeforeAll for List and Read APIs + +/////////////////////////////////////////////////////////////////////////////// +// Utility routines + +const verifyColumnsInRsp = (row, columns: ColumnType[]) => { + const responseColumnsListStr = Object.keys(row).sort().join(','); + const expectedColumnsListStr = columns + .map((c) => c.title) + .sort() + .join(','); + + return responseColumnsListStr === expectedColumnsListStr; +}; + +async function ncAxiosGet(url: string, query = {}, status = 200) { + const response = await request(context.app) + .get(url) + .set('xc-auth', context.token) + .query(query) + .send({}) + .expect(status); + return response; +} +async function ncAxiosPost(url: string, body = {}, status = 200) { + const response = await request(context.app) + .post(url) + .set('xc-auth', context.token) + .send(body) + .expect(status); + return response; +} + +/////////////////////////////////////////////////////////////////////////////// + +// generic table, sakila based +function generalDb() { + beforeEach(async function () { + context = await init(); + + sakilaProject = await createSakilaProject(context); + project = await createProject(context); + + customerTable = await getTable({ + project: sakilaProject, + name: 'customer', + }); + customerColumns = await customerTable.getColumns(); + }); +} + +function textBased() { + // prepare data for test cases + beforeEach(async function () { + context = await init(false); + project = await createProject(context); + table = await createTable(context, project, { + table_name: 'textBased', + title: 'TextBased', + columns: customColumns('textBased'), + }); + + // retrieve column meta + columns = await table.getColumns(); + + // build records + const rowAttributes = []; + for (let i = 0; i < 400; i++) { + const row = { + SingleLineText: rowMixedValue(columns[1], i), + MultiLineText: rowMixedValue(columns[2], i), + Email: rowMixedValue(columns[3], i), + Phone: rowMixedValue(columns[4], i), + Url: rowMixedValue(columns[5], i), + }; + rowAttributes.push(row); + } + + // insert records + // creating bulk records using older set of APIs + await createBulkRows(context, { + project, + table, + values: rowAttributes, + }); + + // retrieve inserted records + insertedRecords = await listRow({ project, table }); + + // verify length of unfiltered records to be 400 + expect(insertedRecords.length).to.equal(400); + }); + + ///////////////////////////////////////////////////////////////////////////// + + // LIST + // + + ///////////////////////////////////////////////////////////////////////////// + + it('List: default', async function () { + const rsp = await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + ); + + const expectedPageInfo = { + totalRows: 400, + page: 1, + pageSize: 25, + isFirstPage: true, + isLastPage: false, + }; + expect(rsp.body.pageInfo).to.deep.equal(expectedPageInfo); + expect(verifyColumnsInRsp(rsp.body.list[0], columns)).to.equal(true); + }); + + it('List: offset, limit', async function () { + const rsp = await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { offset: 200, limit: 100 }, + ); + + const expectedPageInfo = { + totalRows: 400, + page: 3, + pageSize: 100, + isFirstPage: false, + isLastPage: false, + }; + expect(rsp.body.pageInfo).to.deep.equal(expectedPageInfo); + }); + + it('List: fields, single', async function () { + const rsp = await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { fields: 'SingleLineText' }, + ); + + expect( + verifyColumnsInRsp(rsp.body.list[0], [{ title: 'SingleLineText' }]), + ).to.equal(true); + }); + + it('List: fields, multiple', async function () { + const rsp = await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { fields: ['SingleLineText', 'MultiLineText'] }, + ); + + expect( + verifyColumnsInRsp(rsp.body.list[0], [ + { title: 'SingleLineText' }, + { title: 'MultiLineText' }, + ]), + ).to.equal(true); + }); + + it('List: sort, ascending', async function () { + const sortColumn = columns.find((c) => c.title === 'SingleLineText'); + const rsp = await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { sort: 'SingleLineText', limit: 400 }, + ); + + expect(verifyColumnsInRsp(rsp.body.list[0], columns)).to.equal(true); + const sortedArray = rsp.body.list.map((r) => r[sortColumn.title]); + expect(sortedArray).to.deep.equal(sortedArray.sort()); + }); + + it('List: sort, descending', async function () { + const sortColumn = columns.find((c) => c.title === 'SingleLineText'); + const rsp = await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { sort: '-SingleLineText', limit: 400 }, + ); + + expect(verifyColumnsInRsp(rsp.body.list[0], columns)).to.equal(true); + const descSortedArray = rsp.body.list.map((r) => r[sortColumn.title]); + expect(descSortedArray).to.deep.equal(descSortedArray.sort().reverse()); + }); + + it('List: sort, multiple', async function () { + const rsp = await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { sort: ['-SingleLineText', '-MultiLineText'], limit: 400 }, + ); + + expect(verifyColumnsInRsp(rsp.body.list[0], columns)).to.equal(true); + // Combination of SingleLineText & MultiLineText should be in descending order + const sortedArray = rsp.body.list.map( + (r) => r.SingleLineText + r.MultiLineText, + ); + expect(sortedArray).to.deep.equal(sortedArray.sort().reverse()); + }); + + it('List: filter, single', async function () { + const rsp = await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { where: '(SingleLineText,eq,Afghanistan)', limit: 400 }, + ); + + expect(verifyColumnsInRsp(rsp.body.list[0], columns)).to.equal(true); + const filteredArray = rsp.body.list.map((r) => r.SingleLineText); + expect(filteredArray).to.deep.equal(filteredArray.fill('Afghanistan')); + }); + + it('List: filter, multiple', async function () { + const rsp = await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { + where: + '(SingleLineText,eq,Afghanistan)~and(MultiLineText,eq,Allahabad, India)', + limit: 400, + }, + ); + + expect(verifyColumnsInRsp(rsp.body.list[0], columns)).to.equal(true); + const filteredArray = rsp.body.list.map( + (r) => r.SingleLineText + ' ' + r.MultiLineText, + ); + expect(filteredArray).to.deep.equal( + filteredArray.fill('Afghanistan Allahabad, India'), + ); + }); + + it('List: view ID', async function () { + const gridView = await createView(context, { + title: 'grid0', + table, + type: ViewTypes.GRID, + }); + + const fk_column_id = columns.find((c) => c.title === 'SingleLineText').id; + await updateView(context, { + table, + view: gridView, + filter: [ + { + comparison_op: 'eq', + fk_column_id, + logical_op: 'or', + value: 'Afghanistan', + }, + ], + }); + + // fetch records from view + let rsp = await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { viewId: gridView.id }, + ); + expect(rsp.body.pageInfo.totalRows).to.equal(31); + + await updateView(context, { + table, + view: gridView, + filter: [ + { + comparison_op: 'eq', + fk_column_id, + logical_op: 'or', + value: 'Austria', + }, + ], + }); + + // fetch records from view + rsp = await ncAxiosGet(`/api/v1/base/${project.id}/tables/${table.id}`, { + viewId: gridView.id, + }); + expect(rsp.body.pageInfo.totalRows).to.equal(61); + + // Sort by SingleLineText + await updateView(context, { + table, + view: gridView, + sort: [ + { + direction: 'asc', + fk_column_id, + push_to_top: true, + }, + ], + }); + + // fetch records from view + rsp = await ncAxiosGet(`/api/v1/base/${project.id}/tables/${table.id}`, { + viewId: gridView.id, + }); + expect(rsp.body.pageInfo.totalRows).to.equal(61); + + // verify sorted order + // Would contain all 'Afghanistan' as we have 31 records for it + expect(verifyColumnsInRsp(rsp.body.list[0], columns)).to.equal(true); + const filteredArray = rsp.body.list.map((r) => r.SingleLineText); + expect(filteredArray).to.deep.equal(filteredArray.fill('Afghanistan')); + + await updateView(context, { + table, + view: gridView, + field: ['SingleLineText'], + }); + + // fetch records from view + rsp = await ncAxiosGet(`/api/v1/base/${project.id}/tables/${table.id}`, { + viewId: gridView.id, + }); + const displayColumns = columns.filter((c) => c.title !== 'SingleLineText'); + expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true); + }); + + async function prepareViewForTests() { + const gridView = await createView(context, { + title: 'grid0', + table, + type: ViewTypes.GRID, + }); + + const fk_column_id = columns.find((c) => c.title === 'SingleLineText').id; + await updateView(context, { + table, + view: gridView, + filter: [ + { + comparison_op: 'eq', + fk_column_id, + logical_op: 'or', + value: 'Afghanistan', + }, + { + comparison_op: 'eq', + fk_column_id, + logical_op: 'or', + value: 'Austria', + }, + ], + sort: [ + { + direction: 'asc', + fk_column_id, + push_to_top: true, + }, + ], + field: ['MultiLineText', 'Email'], + }); + + // fetch records from view + const rsp = await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { viewId: gridView.id }, + ); + expect(rsp.body.pageInfo.totalRows).to.equal(61); + const displayColumns = columns.filter( + (c) => c.title !== 'MultiLineText' && c.title !== 'Email', + ); + expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true); + return gridView; + } + + it('List: view ID + sort', async function () { + const gridView = await prepareViewForTests(); + + const rsp = await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { + viewId: gridView.id, + sort: 'Url', + limit: 100, + }, + ); + const displayColumns = columns.filter( + (c) => c.title !== 'MultiLineText' && c.title !== 'Email', + ); + expect(rsp.body.pageInfo.totalRows).to.equal(61); + expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true); + const sortedArray = rsp.body.list.map((r) => r['Url']); + expect(sortedArray).to.deep.equal(sortedArray.sort()); + }); + + it('List: view ID + filter', async function () { + const gridView = await prepareViewForTests(); + + const rsp = await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { + viewId: gridView.id, + where: '(Phone,eq,1-541-754-3010)', + limit: 100, + }, + ); + const displayColumns = columns.filter( + (c) => c.title !== 'MultiLineText' && c.title !== 'Email', + ); + expect(rsp.body.pageInfo.totalRows).to.equal(7); + expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true); + const filteredArray = rsp.body.list.map((r) => r['Phone']); + expect(filteredArray).to.deep.equal(filteredArray.fill('1-541-754-3010')); + }); + + it('List: view ID + fields', async function () { + const gridView = await prepareViewForTests(); + + const rsp = await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { + viewId: gridView.id, + fields: ['Phone', 'MultiLineText', 'SingleLineText', 'Email'], + limit: 100, + }, + ); + expect(rsp.body.pageInfo.totalRows).to.equal(61); + expect( + verifyColumnsInRsp(rsp.body.list[0], [ + { title: 'Phone' }, + { title: 'SingleLineText' }, + ]), + ).to.equal(true); + }); + + // Error handling + it('List: invalid ID', async function () { + // Invalid table ID + await ncAxiosGet(`/api/v1/base/${project.id}/tables/123456789`, {}, 400); + // Invalid project ID + await ncAxiosGet(`/api/v1/base/123456789/tables/123456789`, {}, 400); + // Invalid view ID + await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { viewId: '123456789' }, + 400, + ); + }); + + it('List: invalid limit & offset', async function () { + // Invalid limit + await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { + limit: -100, + }, + 200, + ); + await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { + limit: 'abc', + }, + 200, + ); + + // Invalid offset + await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { + offset: -100, + }, + 200, + ); + await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { + offset: 'abc', + }, + 200, + ); + await ncAxiosGet( + `/api/v1/base/${project.id}/tables/${table.id}`, + { + offset: 10000, + }, + 200, + ); + }); + + it('List: invalid sort, filter, fields', async function () { + await ncAxiosGet(`/api/v1/base/${project.id}/tables/${table.id}`, { + sort: 'abc', + }); + await ncAxiosGet(`/api/v1/base/${project.id}/tables/${table.id}`, { + where: 'abc', + }); + await ncAxiosGet(`/api/v1/base/${project.id}/tables/${table.id}`, { + fields: 'abc', + }); + }); +} + +function numberBased() { + // prepare data for test cases + beforeEach(async function () { + context = await init(); + project = await createProject(context); + table = await createTable(context, project, { + table_name: 'numberBased', + title: 'numberBased', + columns: customColumns('numberBased'), + }); + + // retrieve column meta + columns = await table.getColumns(); + + // build records + const rowAttributes = []; + for (let i = 0; i < 400; i++) { + const row = { + Number: rowMixedValue(columns[1], i), + Decimal: rowMixedValue(columns[2], i), + Currency: rowMixedValue(columns[3], i), + Percent: rowMixedValue(columns[4], i), + Duration: rowMixedValue(columns[5], i), + Rating: rowMixedValue(columns[6], i), + }; + rowAttributes.push(row); + } + + // insert records + await createBulkRows(context, { + project, + table, + values: rowAttributes, + }); + + // retrieve inserted records + insertedRecords = await listRow({ project, table }); + + // verify length of unfiltered records to be 400 + expect(insertedRecords.length).to.equal(400); + }); +} + +function selectBased() { + // prepare data for test cases + beforeEach(async function () { + context = await init(); + project = await createProject(context); + table = await createTable(context, project, { + table_name: 'selectBased', + title: 'selectBased', + columns: customColumns('selectBased'), + }); + + // retrieve column meta + columns = await table.getColumns(); + + // build records + const rowAttributes = []; + for (let i = 0; i < 400; i++) { + const row = { + SingleSelect: rowMixedValue(columns[1], i), + MultiSelect: rowMixedValue(columns[2], i), + }; + rowAttributes.push(row); + } + + // insert records + await createBulkRows(context, { + project, + table, + values: rowAttributes, + }); + + // retrieve inserted records + insertedRecords = await listRow({ project, table }); + + // verify length of unfiltered records to be 400 + expect(insertedRecords.length).to.equal(400); + }); +} + +function dateBased() { + // prepare data for test cases + beforeEach(async function () { + context = await init(); + project = await createProject(context); + table = await createTable(context, project, { + table_name: 'dateBased', + title: 'dateBased', + columns: customColumns('dateBased'), + }); + + // retrieve column meta + columns = await table.getColumns(); + + // build records + // 800: one year before to one year after + const rowAttributes = []; + for (let i = 0; i < 800; i++) { + const row = { + Date: rowMixedValue(columns[1], i), + }; + rowAttributes.push(row); + } + + // insert records + await createBulkRows(context, { + project, + table, + values: rowAttributes, + }); + + // retrieve inserted records + insertedRecords = await listRow({ project, table }); + + // verify length of unfiltered records to be 800 + expect(insertedRecords.length).to.equal(800); + }); +} + +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// + +export default function () { + // describe('General', generalDb); + describe('Text based', textBased); + // describe('Numerical', numberBased); + // describe('Select based', selectBased); + // describe('Date based', dateBased); +} + +///////////////////////////////////////////////////////////////////////////////