diff --git a/packages/nocodb/tests/unit/factory/column.ts b/packages/nocodb/tests/unit/factory/column.ts index 710c7cff53..a77d945dbb 100644 --- a/packages/nocodb/tests/unit/factory/column.ts +++ b/packages/nocodb/tests/unit/factory/column.ts @@ -157,6 +157,25 @@ const customColumns = function (type: string, options: any = {}) { dtxp: "'jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'", }, ]; + case 'userBased': + return [ + { + column_name: 'Id', + title: 'Id', + uidt: UITypes.ID, + }, + { + column_name: 'userFieldSingle', + title: 'userFieldSingle', + uidt: UITypes.User, + }, + { + column_name: 'userFieldMulti', + title: 'userFieldMulti', + uidt: UITypes.User, + meta: { is_multi: true }, + }, + ]; case 'custom': return [{ title: 'Id', column_name: 'Id', uidt: UITypes.ID }, ...options]; } diff --git a/packages/nocodb/tests/unit/init/index.ts b/packages/nocodb/tests/unit/init/index.ts index 7067bbceba..f467f51d47 100644 --- a/packages/nocodb/tests/unit/init/index.ts +++ b/packages/nocodb/tests/unit/init/index.ts @@ -26,7 +26,7 @@ const serverInit = async () => { const isFirstTimeRun = () => !server; -export default async function (forceReset = false) { +export default async function (forceReset = false, roles = 'editor') { const { default: TestDbMngr } = await import('../TestDbMngr'); if (isFirstTimeRun()) { @@ -39,7 +39,7 @@ export default async function (forceReset = false) { // } await cleanupMeta(); - const { token } = await createUser({ app: server }, { roles: 'editor' }); + const { token } = await createUser({ app: server }, { roles }); const extra: any = {}; diff --git a/packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts b/packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts index 5a05eb24bd..793f8180dc 100644 --- a/packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts +++ b/packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts @@ -82,7 +82,7 @@ */ import 'mocha'; -import { UITypes, ViewTypes } from 'nocodb-sdk'; +import {UITypes, ViewTypes, WorkspaceUserRoles} from 'nocodb-sdk'; import { expect } from 'chai'; import request from 'supertest'; import init from '../../init'; @@ -98,6 +98,7 @@ import { import { createView, updateView } from '../../factory/view'; import { isPg } from '../../init/db'; +import { defaultUserArgs } from '../../factory/user'; import type { ColumnType } from 'nocodb-sdk'; import type Base from '~/models/Base'; import type Model from '../../../../src/models/Model'; @@ -2731,6 +2732,248 @@ function linkBased() { }); } +function userFieldBased() { + // prepare data for test cases + beforeEach(async function () { + context = await init(false, 'creator'); + base = await createProject(context); + table = await createTable(context, base, { + table_name: 'userBased', + title: 'userBased', + columns: customColumns('userBased'), + }); + + // retrieve column meta + columns = await table.getColumns(); + + // add users to workspace + const users = [ + 'a@nocodb.com', + 'b@nocodb.com', + 'c@nocodb.com', + 'd@nocodb.com', + 'e@nocodb.com', + ]; + for (const email of users) { + await addUsers(email); + } + const userList = await getUsers(); + userList[userList.length] = { email: null }; + userList[userList.length] = { email: '' }; + + // build records + const rowAttributes = []; + for (let i = 0; i < 400; i++) { + const row = { + userFieldSingle: [{ email: userList[i % userList.length].email }], + userFieldMulti: [ + { email: userList[i % userList.length].email }, + { email: userList[(i + 1) % userList.length].email }, + ], + }; + rowAttributes.push(row); + } + + // insert records + await createBulkRows(context, { + base, + table, + values: rowAttributes, + }); + + // retrieve inserted records + insertedRecords = await listRow({ base, table }); + + // verify length of unfiltered records to be 400 + expect(insertedRecords.length).to.equal(400); + }); + + async function addUsers(email) { + const response = await request(context.app) + .post('/api/v1/auth/user/signup') + .send({ email, password: defaultUserArgs.password }) + .expect(200); + + const token = response.body.token; + expect(token).to.be.a('string'); + + // invite users to workspace + if (process.env.EE === 'true') { + let rsp = await request(context.app) + .post(`/api/v1/workspaces/${context.fk_workspace_id}/invitations`) + .set('xc-auth', context.token) + .send({ email, roles: WorkspaceUserRoles.VIEWER }); + console.log(rsp); + } + } + + async function getUsers() { + const response = await request(context.app) + .get(`/api/v2/meta/bases/${base.id}/users`) + .set('xc-auth', context.token); + + expect(response.body).to.have.keys(['users']); + expect(response.body.users.list).to.have.length(6); + return response.body.users.list; + } + + it('List records', async function () { + // retrieve inserted records + insertedRecords = await listRow({ base, table }); + + // verify length of unfiltered records to be 400 + expect(insertedRecords.length).to.equal(400); + expect(insertedRecords[0].userFieldSingle[0]).to.have.keys([ + 'email', + 'id', + 'display_name', + ]); + expect(insertedRecords[0].userFieldMulti[0]).to.have.keys([ + 'email', + 'id', + 'display_name', + ]); + }); + + it('List: sort, ascending', async function () { + const sortColumn = columns.find((c) => c.title === 'userFieldSingle'); + const rsp = await ncAxiosGet({ + query: { sort: 'userFieldSingle', 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((a, b) => { + const emailA = a ? a[0]?.email?.toLowerCase() : ''; + const emailB = b ? b[0]?.email?.toLowerCase() : ''; + + if (emailA < emailB) { + return -1; + } + if (emailA > emailB) { + return 1; + } + + // Emails are equal, no change in order + return 0; + }), + ); + }); + + it('List: filter, single', async function () { + const userList = await getUsers(); + const rsp = await ncAxiosGet({ + query: { + where: `(userFieldSingle,eq,${userList[2].id})`, + limit: 400, + }, + }); + + expect(verifyColumnsInRsp(rsp.body.list[0], columns)).to.equal(true); + const filteredArray = rsp.body.list.map((r) => r.userFieldSingle); + expect(filteredArray).to.deep.equal(filteredArray.fill(userList[2])); + }); + + it('List: sort, ascending for user multi field', async function () { + const sortColumn = columns.find((c) => c.title === 'userFieldMulti'); + const rsp = await ncAxiosGet({ + query: { sort: 'userFieldMulti', 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((a, b) => { + const emailA = a ? a[0]?.email?.toLowerCase() : ''; + const emailB = b ? b[0]?.email?.toLowerCase() : ''; + + if (emailA < emailB) { + return -1; + } + if (emailA > emailB) { + return 1; + } + + // Emails are equal, no change in order + return 0; + }), + ); + }); + + it('List: filter, user multi field', async function () { + const userList = await getUsers(); + const rsp = await ncAxiosGet({ + query: { + where: `(userFieldMulti,anyof,${userList[2].id})`, + limit: 400, + }, + }); + + expect(verifyColumnsInRsp(rsp.body.list[0], columns)).to.equal(true); + expect(rsp.body.list.length).to.equal(100); + }); + + it('Create record : using email', async function () { + const newRecord = { + userFieldSingle: 'a@nocodb.com', + userFieldMulti: 'a@nocodb.com,b@nocodb.com', + }; + const rsp = await ncAxiosPost({ body: newRecord }); + expect(rsp.body).to.deep.equal({ Id: 401 }); + + const record = await ncAxiosGet({ + url: `/api/v2/tables/${table.id}/records/401`, + }); + expect(record.body.Id).to.equal(401); + expect(record.body.userFieldSingle[0].email).to.equal('a@nocodb.com'); + expect(record.body.userFieldMulti[0].email).to.equal('a@nocodb.com'); + expect(record.body.userFieldMulti[1].email).to.equal('b@nocodb.com'); + }); + + it('Create record : using ID', async function () { + const userList = await getUsers(); + + const id0 = userList.find((u) => u.email === 'test@example.com').id; + const id1 = userList.find((u) => u.email === 'a@nocodb.com').id; + + const newRecord = { + userFieldSingle: id0, + userFieldMulti: `${id0},${id1}`, + }; + const rsp = await ncAxiosPost({ body: newRecord }); + expect(rsp.body).to.deep.equal({ Id: 401 }); + + const record = await ncAxiosGet({ + url: `/api/v2/tables/${table.id}/records/401`, + }); + expect(record.body.Id).to.equal(401); + expect(record.body.userFieldSingle[0].email).to.equal('test@example.com'); + expect(record.body.userFieldMulti[0].email).to.equal('test@example.com'); + expect(record.body.userFieldMulti[1].email).to.equal('a@nocodb.com'); + }); + + it('Create record : duplicate ID', async function () { + const userList = await getUsers(); + + const newRecord1 = { + userFieldSingle: userList[0].id, + userFieldMulti: `${userList[0].id},${userList[0].id}`, + }; + const rsp = await ncAxiosPost({ body: newRecord1, status: 422 }); + expect(rsp.body.msg).to.equal('Duplicate users not allowed for user field'); + + const newRecord2 = { + userFieldSingle: `${userList[0].id},${userList[1].id}`, + userFieldMulti: `${userList[0].id},${userList[1].id}`, + }; + const rsp2 = await ncAxiosPost({ body: newRecord2, status: 422 }); + expect(rsp2.body.msg).to.equal( + "Multiple users not allowed for 'userFieldSingle'", + ); + }); +} + /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// @@ -2742,8 +2985,9 @@ export default function () { describe('Select based', selectBased); describe('Date based', dateBased); describe('Link based', linkBased); + describe('User field based', userFieldBased); - // based out of sakila db, for link based tests + // based out of Sakila db, for link based tests describe('General', generalDb); } diff --git a/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts b/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts index c2eb295957..547098c028 100644 --- a/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts +++ b/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts @@ -341,7 +341,7 @@ export class ToolbarFilterPage extends BasePage { y: 1, }, }); - // eslint-disable-next-line no-case-declarations + const v = value.split(','); for (let i = 0; i < v.length; i++) { await this.rootPage