diff --git a/packages/cli/src/modules/data-store/__tests__/data-store.controller.test.ts b/packages/cli/src/modules/data-store/__tests__/data-store.controller.test.ts index 782ca92e0f..d650dfe429 100644 --- a/packages/cli/src/modules/data-store/__tests__/data-store.controller.test.ts +++ b/packages/cli/src/modules/data-store/__tests__/data-store.controller.test.ts @@ -1,4 +1,4 @@ -import type { DataStore } from '@n8n/api-types'; +import type { DataStore, DataStoreCreateColumnSchema } from '@n8n/api-types'; import { createTeamProject, getPersonalProject, @@ -6,7 +6,7 @@ import { testDb, } from '@n8n/backend-test-utils'; import type { Project, User } from '@n8n/db'; -import { ProjectRepository } from '@n8n/db'; +import { ProjectRepository, QueryFailedError } from '@n8n/db'; import { Container } from '@n8n/di'; import { createDataStore } from '@test-integration/db/data-stores'; import { createOwner, createMember, createAdmin } from '@test-integration/db/users'; @@ -14,7 +14,10 @@ import type { SuperAgentTest } from '@test-integration/types'; import * as utils from '@test-integration/utils'; import { DateTime } from 'luxon'; +import { DataStoreColumnRepository } from '../data-store-column.repository'; +import { DataStoreRowsRepository } from '../data-store-rows.repository'; import { DataStoreRepository } from '../data-store.repository'; +import { toTableName } from '../utils/sql-utils'; let owner: User; let member: User; @@ -31,6 +34,8 @@ const testServer = utils.setupTestServer({ }); let projectRepository: ProjectRepository; let dataStoreRepository: DataStoreRepository; +let dataStoreColumnRepository: DataStoreColumnRepository; +let dataStoreRowsRepository: DataStoreRowsRepository; beforeAll(async () => { await testDb.init(); @@ -41,6 +46,8 @@ beforeEach(async () => { projectRepository = Container.get(ProjectRepository); dataStoreRepository = Container.get(DataStoreRepository); + dataStoreColumnRepository = Container.get(DataStoreColumnRepository); + dataStoreRowsRepository = Container.get(DataStoreRowsRepository); owner = await createOwner(); member = await createMember(); @@ -110,8 +117,7 @@ describe('POST /projects/:projectId/data-stores', () => { expect(dataStoresInDb).toHaveLength(0); }); - test("should not allow creating data store in another user's personal project", async () => { - const ownerPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(owner.id); + test("should not create data store in another user's personal project", async () => { const payload = { name: 'Test Data Store', columns: [ @@ -123,7 +129,7 @@ describe('POST /projects/:projectId/data-stores', () => { }; await authMemberAgent - .post(`/projects/${ownerPersonalProject.id}/data-stores`) + .post(`/projects/${ownerProject.id}/data-stores`) .send(payload) .expect(403); }); @@ -150,6 +156,26 @@ describe('POST /projects/:projectId/data-stores', () => { test('should create data store if user has project:admin role in team project', async () => { const project = await createTeamProject(undefined, owner); + await linkUserToProject(admin, project, 'project:admin'); + + const payload = { + name: 'Test Data Store', + columns: [ + { + name: 'test-ccolumn', + type: 'string', + }, + ], + }; + + await authAdminAgent.post(`/projects/${project.id}/data-stores`).send(payload).expect(200); + + const dataStoresInDb = await dataStoreRepository.find(); + expect(dataStoresInDb).toHaveLength(1); + }); + + test('should create data store if user is owner in team project', async () => { + const project = await createTeamProject(undefined, owner); const payload = { name: 'Test Data Store', @@ -219,7 +245,7 @@ describe('GET /projects/:projectId/data-stores', () => { await authAdminAgent.get(`/projects/${project.id}/data-stores`).expect(403); }); - test("should not allow listing data stores from another user's personal project", async () => { + test("should not list data stores from another user's personal project", async () => { await authMemberAgent.get(`/projects/${ownerProject.id}/data-stores`).expect(403); }); @@ -483,3 +509,1727 @@ describe('GET /projects/:projectId/data-stores', () => { expect(response.body.data.data[0].columns).toHaveLength(2); }); }); + +describe('PATCH /projects/:projectId/data-stores/:dataStoreId', () => { + test('should not update data store when project does not exist', async () => { + const payload = { + name: 'Updated Data Store Name', + }; + + await authOwnerAgent + .patch('/projects/non-existing-id/data-stores/some-data-store-id') + .send(payload) + .expect(403); + }); + + test('should not update data store when data store does not exist', async () => { + const project = await createTeamProject('test project', owner); + + const payload = { + name: 'Updated Data Store Name', + }; + + await authOwnerAgent + .patch(`/projects/${project.id}/data-stores/non-existing-data-store`) + .send(payload) + .expect(404); + }); + + test('should not update data store when name is empty', async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project, { name: 'Original Name' }); + + const payload = { + name: '', + }; + + await authOwnerAgent + .patch(`/projects/${project.id}/data-stores/${dataStore.id}`) + .send(payload) + .expect(400); + + const dataStoreInDb = await dataStoreRepository.findOneBy({ id: dataStore.id }); + expect(dataStoreInDb?.name).toBe('Original Name'); + }); + + test('should not update data store if user has project:viewer role in team project', async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project, { name: 'Original Name' }); + await linkUserToProject(member, project, 'project:viewer'); + + const payload = { + name: 'Updated Data Store Name', + }; + + await authMemberAgent + .patch(`/projects/${project.id}/data-stores/${dataStore.id}`) + .send(payload) + .expect(403); + + const dataStoreInDb = await dataStoreRepository.findOneBy({ id: dataStore.id }); + expect(dataStoreInDb?.name).toBe('Original Name'); + }); + + test("should not update data store in another user's personal project", async () => { + const dataStore = await createDataStore(ownerProject, { name: 'Original Name' }); + + const payload = { + name: 'Updated Data Store Name', + }; + + await authMemberAgent + .patch(`/projects/${ownerProject.id}/data-stores/${dataStore.id}`) + .send(payload) + .expect(403); + + const dataStoreInDb = await dataStoreRepository.findOneBy({ id: dataStore.id }); + expect(dataStoreInDb?.name).toBe('Original Name'); + }); + + test('should update data store if user has project:editor role in team project', async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project, { name: 'Original Name' }); + await linkUserToProject(member, project, 'project:editor'); + + const payload = { + name: 'Updated Data Store Name', + }; + + await authMemberAgent + .patch(`/projects/${project.id}/data-stores/${dataStore.id}`) + .send(payload) + .expect(200); + + const dataStoreInDb = await dataStoreRepository.findOneBy({ id: dataStore.id }); + expect(dataStoreInDb?.name).toBe('Updated Data Store Name'); + }); + + test('should update data store if user has project:admin role in team project', async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project, { name: 'Original Name' }); + await linkUserToProject(admin, project, 'project:admin'); + + const payload = { + name: 'Updated Data Store Name', + }; + + await authAdminAgent + .patch(`/projects/${project.id}/data-stores/${dataStore.id}`) + .send(payload) + .expect(200); + + const dataStoreInDb = await dataStoreRepository.findOneBy({ id: dataStore.id }); + expect(dataStoreInDb?.name).toBe('Updated Data Store Name'); + }); + + test('should update data store if user is owner in team project', async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project, { name: 'Original Name' }); + + const payload = { + name: 'Updated Data Store Name', + }; + + await authOwnerAgent + .patch(`/projects/${project.id}/data-stores/${dataStore.id}`) + .send(payload) + .expect(200); + + const dataStoreInDb = await dataStoreRepository.findOneBy({ id: dataStore.id }); + expect(dataStoreInDb?.name).toBe('Updated Data Store Name'); + }); + + test('should update data store in personal project', async () => { + const personalProject = await projectRepository.getPersonalProjectForUserOrFail(owner.id); + const dataStore = await createDataStore(personalProject, { name: 'Original Name' }); + + const payload = { + name: 'Updated Data Store Name', + }; + + await authOwnerAgent + .patch(`/projects/${personalProject.id}/data-stores/${dataStore.id}`) + .send(payload) + .expect(200); + + const dataStoreInDb = await dataStoreRepository.findOneBy({ id: dataStore.id }); + expect(dataStoreInDb?.name).toBe('Updated Data Store Name'); + }); +}); + +describe('DELETE /projects/:projectId/data-stores/:dataStoreId', () => { + test('should not delete data store when project does not exist', async () => { + await authOwnerAgent + .delete('/projects/non-existing-id/data-stores/some-data-store-id') + .send({}) + .expect(403); + }); + + test('should not delete data store when data store does not exist', async () => { + const project = await createTeamProject('test project', owner); + + await authOwnerAgent + .delete(`/projects/${project.id}/data-stores/non-existing-data-store`) + .send({}) + .expect(404); + }); + + test('should not delete data store if user has project:viewer role in team project', async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project); + await linkUserToProject(member, project, 'project:viewer'); + + await authMemberAgent + .delete(`/projects/${project.id}/data-stores/${dataStore.id}`) + .send({}) + .expect(403); + + const dataStoreInDb = await dataStoreRepository.findOneBy({ id: dataStore.id }); + expect(dataStoreInDb).toBeDefined(); + }); + + test("should not delete data store in another user's personal project", async () => { + const dataStore = await createDataStore(ownerProject); + + await authMemberAgent + .delete(`/projects/${ownerProject.id}/data-stores/${dataStore.id}`) + .send({}) + .expect(403); + + const dataStoreInDb = await dataStoreRepository.findOneBy({ id: dataStore.id }); + expect(dataStoreInDb).toBeDefined(); + }); + + test('should delete data store if user has project:editor role in team project', async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project); + await linkUserToProject(member, project, 'project:editor'); + + await authMemberAgent + .delete(`/projects/${project.id}/data-stores/${dataStore.id}`) + .send({}) + .expect(200); + + const dataStoreInDb = await dataStoreRepository.findOneBy({ id: dataStore.id }); + expect(dataStoreInDb).toBeNull(); + }); + + test('should delete data store if user has project:admin role in team project', async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project); + await linkUserToProject(admin, project, 'project:admin'); + + await authAdminAgent + .delete(`/projects/${project.id}/data-stores/${dataStore.id}`) + .send({}) + .expect(200); + + const dataStoreInDb = await dataStoreRepository.findOneBy({ id: dataStore.id }); + expect(dataStoreInDb).toBeNull(); + }); + + test('should delete data store if user is owner in team project', async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project); + + await authOwnerAgent + .delete(`/projects/${project.id}/data-stores/${dataStore.id}`) + .send({}) + .expect(200); + + const dataStoreInDb = await dataStoreRepository.findOneBy({ id: dataStore.id }); + expect(dataStoreInDb).toBeNull(); + }); + + test('should delete data store in personal project', async () => { + const personalProject = await projectRepository.getPersonalProjectForUserOrFail(owner.id); + const dataStore = await createDataStore(personalProject); + + await authOwnerAgent + .delete(`/projects/${personalProject.id}/data-stores/${dataStore.id}`) + .send({}) + .expect(200); + + const dataStoreInDb = await dataStoreRepository.findOneBy({ id: dataStore.id }); + expect(dataStoreInDb).toBeNull(); + }); + + test("should delete data from 'data_store', 'data_store_column' tables and drop 'data_store_user_' table", async () => { + const personalProject = await projectRepository.getPersonalProjectForUserOrFail(owner.id); + const dataStore = await createDataStore(personalProject, { + name: 'Test Data Store', + columns: [ + { + name: 'test', + type: 'string', + }, + ], + }); + + await authOwnerAgent + .delete(`/projects/${personalProject.id}/data-stores/${dataStore.id}`) + .send({}) + .expect(200); + + const dataStoreInDb = await dataStoreRepository.findOneBy({ id: dataStore.id }); + expect(dataStoreInDb).toBeNull(); + + const dataStoreColumnInDb = await dataStoreColumnRepository.findOneBy({ + dataStoreId: dataStore.id, + }); + expect(dataStoreColumnInDb).toBeNull(); + + await expect( + dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {}), + ).rejects.toThrow(QueryFailedError); + }); +}); + +describe('GET /projects/:projectId/data-stores/:dataStoreId/columns', () => { + test('should not list columns when project does not exist', async () => { + await authOwnerAgent + .get('/projects/non-existing-id/data-stores/non-existing-id/columns') + .expect(403); + }); + + test('should not list columns if user has no access to project', async () => { + const project = await createTeamProject('test project', owner); + const dataStore = await createDataStore(project); + + await authMemberAgent + .get(`/projects/${project.id}/data-stores/${dataStore.id}/columns`) + .expect(403); + }); + + test("should not list columns from data stores in another user's personal project", async () => { + await authMemberAgent.get(`/projects/${ownerProject.id}/data-stores`).expect(403); + }); + + test('should not list columns when data store does not exist', async () => { + const project = await createTeamProject('test project', owner); + + await authOwnerAgent + .get(`/projects/${project.id}/data-stores/non-existing-id/columns`) + .expect(404); + }); + + test('should list columns if user has project:viewer role in team project', async () => { + const project = await createTeamProject('test project', owner); + await linkUserToProject(member, project, 'project:viewer'); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + { + name: 'another-column', + type: 'boolean', + }, + ], + }); + + const response = await authMemberAgent + .get(`/projects/${project.id}/data-stores/${dataStore.id}/columns`) + .expect(200); + + expect(response.body.data).toHaveLength(2); + expect(response.body.data[0].name).toBe('test-column'); + expect(response.body.data[1].name).toBe('another-column'); + }); + + test('should list columns if user has project:editor role in team project', async () => { + const project = await createTeamProject('test project', owner); + await linkUserToProject(member, project, 'project:editor'); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + ], + }); + + const response = await authMemberAgent + .get(`/projects/${project.id}/data-stores/${dataStore.id}/columns`) + .expect(200); + + expect(response.body.data).toHaveLength(1); + expect(response.body.data[0].name).toBe('test-column'); + }); + + test('should list columns from personal project data store', async () => { + const dataStore = await createDataStore(memberProject, { + name: 'Personal Data Store 1', + columns: [ + { + name: 'test-column', + type: 'string', + }, + ], + }); + + const response = await authMemberAgent + .get(`/projects/${memberProject.id}/data-stores/${dataStore.id}/columns`) + .expect(200); + + expect(response.body.data).toHaveLength(1); + expect(response.body.data[0].name).toBe('test-column'); + }); +}); + +describe('POST /projects/:projectId/data-stores/:dataStoreId/columns', () => { + test('should not create column when project does not exist', async () => { + const payload = { + name: 'Test Column', + type: 'string', + }; + + await authOwnerAgent + .post('/projects/non-existing-id/data-stores/some-data-store-id/columns') + .send(payload) + .expect(403); + }); + + test('should not create column when data store does not exist', async () => { + const project = await createTeamProject('test project', owner); + + const payload = { + name: 'test-column', + type: 'string', + index: 0, + }; + + await authOwnerAgent + .post(`/projects/${project.id}/data-stores/non-existing-data-store/columns`) + .send(payload) + .expect(404); + }); + + test('should not create column when name is empty', async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project); + + const payload = { + name: '', + type: 'string', + }; + + await authOwnerAgent + .post(`/projects/${project.id}/data-stores/${dataStore.id}/columns`) + .send(payload) + .expect(400); + + const columnsInDb = await dataStoreColumnRepository.findBy({ dataStoreId: dataStore.id }); + expect(columnsInDb).toHaveLength(0); + }); + + test("should not create column when name isn't valid", async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project); + + const payload = { + name: 'invalid name', + type: 'string', + }; + + await authOwnerAgent + .post(`/projects/${project.id}/data-stores/${dataStore.id}/columns`) + .send(payload) + .expect(400); + + const columnsInDb = await dataStoreColumnRepository.findBy({ dataStoreId: dataStore.id }); + expect(columnsInDb).toHaveLength(0); + }); + + test("should not create column in another user's personal project data store", async () => { + const dataStore = await createDataStore(ownerProject, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + ], + }); + + await authMemberAgent + .post(`/projects/${ownerProject.id}/data-stores/${dataStore.id}/columns`) + .send({ + name: 'new-column', + type: 'string', + }) + .expect(403); + + const columnsInDb = await dataStoreColumnRepository.findBy({ dataStoreId: dataStore.id }); + expect(columnsInDb).toHaveLength(1); + expect(columnsInDb[0].name).toBe('test-column'); + }); + + test('should not create column if user has project:viewer role in team project', async () => { + const project = await createTeamProject(undefined, owner); + await linkUserToProject(member, project, 'project:viewer'); + const dataStore = await createDataStore(project); + + const payload = { + name: 'test-column', + type: 'string', + }; + + await authMemberAgent + .post(`/projects/${project.id}/data-stores/${dataStore.id}/columns`) + .send(payload) + .expect(403); + + const columnsInDb = await dataStoreColumnRepository.findBy({ dataStoreId: dataStore.id }); + expect(columnsInDb).toHaveLength(0); + }); + + test('should create column if user has project:editor role in team project', async () => { + const project = await createTeamProject(undefined, owner); + await linkUserToProject(member, project, 'project:editor'); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + ], + }); + + const payload = { + name: 'new-column', + type: 'string', + index: 0, + }; + + await authMemberAgent + .post(`/projects/${project.id}/data-stores/${dataStore.id}/columns`) + .send(payload) + .expect(200); + + const columnsInDb = await dataStoreColumnRepository.findBy({ dataStoreId: dataStore.id }); + expect(columnsInDb).toHaveLength(2); + expect(columnsInDb[0].name).toBe('new-column'); + expect(columnsInDb[0].type).toBe('string'); + }); + + test('should create column if user has project:admin role in team project', async () => { + const project = await createTeamProject(undefined, owner); + await linkUserToProject(admin, project, 'project:admin'); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + ], + }); + + const payload = { + name: 'new-column', + type: 'boolean', + index: 0, + }; + + await authAdminAgent + .post(`/projects/${project.id}/data-stores/${dataStore.id}/columns`) + .send(payload) + .expect(200); + + const columnsInDb = await dataStoreColumnRepository.findBy({ dataStoreId: dataStore.id }); + expect(columnsInDb).toHaveLength(2); + expect(columnsInDb[0].name).toBe('new-column'); + expect(columnsInDb[0].type).toBe('boolean'); + expect(columnsInDb[1].name).toBe('test-column'); + expect(columnsInDb[1].type).toBe('string'); + }); + + test('should create column if user has is owner in team project', async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + ], + }); + + const payload = { + name: 'new-column', + type: 'boolean', + index: 0, + }; + + await authOwnerAgent + .post(`/projects/${project.id}/data-stores/${dataStore.id}/columns`) + .send(payload) + .expect(200); + + const columnsInDb = await dataStoreColumnRepository.findBy({ dataStoreId: dataStore.id }); + expect(columnsInDb).toHaveLength(2); + expect(columnsInDb[0].name).toBe('new-column'); + expect(columnsInDb[0].type).toBe('boolean'); + expect(columnsInDb[1].name).toBe('test-column'); + expect(columnsInDb[1].type).toBe('string'); + }); + + test('should place the column in correct index', async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column-1', + type: 'string', + }, + { + name: 'test-column-2', + type: 'string', + }, + ], + }); + + const payload: DataStoreCreateColumnSchema = { + name: 'new-column', + type: 'boolean', + index: 1, + }; + + await authOwnerAgent + .post(`/projects/${project.id}/data-stores/${dataStore.id}/columns`) + .send(payload) + .expect(200); + + const columns = await dataStoreColumnRepository.getColumns(dataStore.id); + + expect(columns).toHaveLength(3); + expect(columns[0].name).toBe('test-column-1'); + expect(columns[1].name).toBe('new-column'); + expect(columns[2].name).toBe('test-column-2'); + }); +}); + +describe('DELETE /projects/:projectId/data-stores/:dataStoreId/columns/:columnId', () => { + test('should not delete column when project does not exist', async () => { + await authOwnerAgent + .delete('/projects/non-existing-id/data-stores/some-data-store-id/columns/some-column-id') + .send({}) + .expect(403); + }); + + test('should not delete column when data store does not exist', async () => { + const project = await createTeamProject('test project', owner); + + await authOwnerAgent + .delete(`/projects/${project.id}/data-stores/non-existing-id/columns/some-column-id`) + .send() + .expect(404); + }); + + test('should not delete column when column does not exist', async () => { + const project = await createTeamProject('test project', owner); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + ], + }); + + await authOwnerAgent + .delete(`/projects/${project.id}/data-stores/${dataStore.id}/columns/non-existing-id`) + .send() + .expect(404); + }); + + test("should not delete column in another user's personal project data store", async () => { + const dataStore = await createDataStore(ownerProject, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + ], + }); + + await authMemberAgent + .delete(`/projects/${ownerProject.id}/data-stores/${dataStore.id}/columns/test-column`) + .send() + .expect(403); + + const columnInDb = await dataStoreColumnRepository.findOneBy({ + dataStoreId: dataStore.id, + name: 'test-column', + }); + expect(columnInDb).toBeDefined(); + }); + + test('should not delete column if user has project:viewer role in team project', async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + ], + }); + await linkUserToProject(member, project, 'project:viewer'); + + await authMemberAgent + .delete(`/projects/${project.id}/data-stores/${dataStore.id}/columns/test-column`) + .send() + .expect(403); + + const columnInDb = await dataStoreColumnRepository.findOneBy({ + dataStoreId: dataStore.id, + name: 'test-column', + }); + expect(columnInDb).toBeDefined(); + }); + + test('should delete column if user has project:editor role in team project', async () => { + const project = await createTeamProject(undefined, owner); + await linkUserToProject(member, project, 'project:editor'); + + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + ], + }); + + await authOwnerAgent + .delete( + `/projects/${project.id}/data-stores/${dataStore.id}/columns/${dataStore.columns[0].id}`, + ) + .send() + .expect(200); + + const columnInDb = await dataStoreColumnRepository.findOneBy({ + dataStoreId: dataStore.id, + name: 'test-column', + }); + expect(columnInDb).toBeNull(); + }); + + test('should delete column if user has project:admin role in team project', async () => { + const project = await createTeamProject(undefined, owner); + await linkUserToProject(admin, project, 'project:admin'); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + ], + }); + + await authAdminAgent + .delete( + `/projects/${project.id}/data-stores/${dataStore.id}/columns/${dataStore.columns[0].id}`, + ) + .send() + .expect(200); + + const columnInDb = await dataStoreColumnRepository.findOneBy({ + dataStoreId: dataStore.id, + name: 'test-column', + }); + expect(columnInDb).toBeNull(); + }); + + test('should delete column if user is owner in team project', async () => { + const project = await createTeamProject(undefined, owner); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + ], + }); + + await authOwnerAgent + .delete( + `/projects/${project.id}/data-stores/${dataStore.id}/columns/${dataStore.columns[0].id}`, + ) + .send() + .expect(200); + + const columnInDb = await dataStoreColumnRepository.findOneBy({ + dataStoreId: dataStore.id, + name: 'test-column', + }); + expect(columnInDb).toBeNull(); + }); + + test('should delete column in personal project', async () => { + const dataStore = await createDataStore(memberProject, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + ], + }); + + await authMemberAgent + .delete( + `/projects/${memberProject.id}/data-stores/${dataStore.id}/columns/${dataStore.columns[0].id}`, + ) + .send() + .expect(200); + + const columnInDb = await dataStoreColumnRepository.findOneBy({ + dataStoreId: dataStore.id, + name: 'test-column', + }); + expect(columnInDb).toBeNull(); + }); +}); + +describe('PATCH /projects/:projectId/data-stores/:dataStoreId/columns/:columnId/move', () => { + test('should not move column when project does not exist', async () => { + const payload = { + index: 1, + }; + + await authOwnerAgent + .patch('/projects/non-existing-id/data-stores/some-data-store-id/columns/some-column-id/move') + .send(payload) + .expect(403); + }); + + test('should not move column when data store does not exist', async () => { + const project = await createTeamProject('test project', owner); + const payload = { + targetIndex: 1, + }; + + await authOwnerAgent + .patch( + `/projects/${project.id}/data-stores/non-existing-data-store/columns/some-column-id/move`, + ) + .send(payload) + .expect(404); + }); + + test('should not move column when column does not exist', async () => { + const project = await createTeamProject('test project', owner); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + ], + }); + const payload = { + targetIndex: 1, + }; + + await authOwnerAgent + .patch(`/projects/${project.id}/data-stores/${dataStore.id}/columns/some-column-id/move`) + .send(payload) + .expect(404); + }); + + test("should not move column in another user's personal project data store", async () => { + const dataStore = await createDataStore(ownerProject, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + { + name: 'another-column', + type: 'string', + }, + ], + }); + + await authMemberAgent + .patch( + `/projects/${ownerProject.id}/data-stores/${dataStore.id}/columns/${dataStore.columns[0].id}/move`, + ) + .send({ targetIndex: 1 }) + .expect(403); + + const columnInDb = await dataStoreColumnRepository.findOneBy({ + dataStoreId: dataStore.id, + name: 'test-column', + index: 0, + }); + expect(columnInDb).toBeDefined(); + }); + + test('should not move column if user has project:viewer role in team project', async () => { + const project = await createTeamProject('test project', owner); + await linkUserToProject(member, project, 'project:viewer'); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + { + name: 'another-column', + type: 'string', + }, + ], + }); + + await authMemberAgent + .patch( + `/projects/${project.id}/data-stores/${dataStore.id}/columns/${dataStore.columns[0].id}/move`, + ) + .send({ targetIndex: 1 }) + .expect(403); + + const columnInDb = await dataStoreColumnRepository.findOneBy({ + dataStoreId: dataStore.id, + name: 'test-column', + index: 0, + }); + expect(columnInDb).toBeDefined(); + }); + + test('should move column if user has project:editor role in team project', async () => { + const project = await createTeamProject('test project', owner); + await linkUserToProject(member, project, 'project:editor'); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + { + name: 'another-column', + type: 'string', + }, + ], + }); + + await authMemberAgent + .patch( + `/projects/${project.id}/data-stores/${dataStore.id}/columns/${dataStore.columns[0].id}/move`, + ) + .send({ targetIndex: 1 }) + .expect(200); + + const columnInDb = await dataStoreColumnRepository.findOneBy({ + dataStoreId: dataStore.id, + name: 'test-column', + index: 1, + }); + expect(columnInDb).toBeDefined(); + }); + + test('should move column if user has project:admin role in team project', async () => { + const project = await createTeamProject('test project', owner); + await linkUserToProject(admin, project, 'project:admin'); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + { + name: 'another-column', + type: 'string', + }, + ], + }); + + await authAdminAgent + .patch( + `/projects/${project.id}/data-stores/${dataStore.id}/columns/${dataStore.columns[0].id}/move`, + ) + .send({ targetIndex: 1 }) + .expect(200); + + const columnInDb = await dataStoreColumnRepository.findOneBy({ + dataStoreId: dataStore.id, + name: 'test-column', + index: 1, + }); + expect(columnInDb).toBeDefined(); + }); + + test('should move column if user is owner in team project', async () => { + const project = await createTeamProject('test project', owner); + + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + { + name: 'another-column', + type: 'string', + }, + ], + }); + + await authOwnerAgent + .patch( + `/projects/${project.id}/data-stores/${dataStore.id}/columns/${dataStore.columns[0].id}/move`, + ) + .send({ targetIndex: 1 }) + .expect(200); + + const columnInDb = await dataStoreColumnRepository.findOneBy({ + dataStoreId: dataStore.id, + name: 'test-column', + index: 1, + }); + expect(columnInDb).toBeDefined(); + }); + + test('should move column in personal project', async () => { + const dataStore = await createDataStore(memberProject, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + { + name: 'another-column', + type: 'string', + }, + ], + }); + + await authMemberAgent + .patch( + `/projects/${memberProject.id}/data-stores/${dataStore.id}/columns/${dataStore.columns[0].id}/move`, + ) + .send({ targetIndex: 1 }) + .expect(200); + + const columnInDb = await dataStoreColumnRepository.findOneBy({ + dataStoreId: dataStore.id, + name: 'test-column', + index: 1, + }); + expect(columnInDb).toBeDefined(); + }); +}); + +describe('GET /projects/:projectId/data-stores/:dataStoreId/rows', () => { + test('should not list rows when project does not exist', async () => { + await authOwnerAgent + .get('/projects/non-existing-id/data-stores/some-data-store-id/rows') + .expect(403); + }); + + test('should not list rows when data store does not exist', async () => { + const project = await createTeamProject('test project', owner); + await authOwnerAgent + .get(`/projects/${project.id}/data-stores/non-existing-id/rows`) + .expect(404); + }); + + test("should not list rows in another user's personal project data store", async () => { + const dataStore = await createDataStore(ownerProject, { + columns: [ + { + name: 'test-column', + type: 'string', + }, + { + name: 'another-column', + type: 'string', + }, + ], + }); + + await authMemberAgent + .get(`/projects/${ownerProject.id}/data-stores/${dataStore.id}/rows`) + .expect(403); + }); + + test('should list rows if user has project:viewer role in team project', async () => { + const project = await createTeamProject('test project', owner); + await linkUserToProject(member, project, 'project:viewer'); + + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + data: [ + { + first: 'test value', + second: 'another value', + }, + ], + }); + + const response = await authMemberAgent + .get(`/projects/${project.id}/data-stores/${dataStore.id}/rows`) + .expect(200); + + expect(response.body.data).toEqual({ + count: 1, + data: [ + { + id: 1, + first: 'test value', + second: 'another value', + }, + ], + }); + }); + + test('should list rows if user has project:editor role in team project', async () => { + const project = await createTeamProject('test project', owner); + await linkUserToProject(member, project, 'project:editor'); + + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + data: [ + { + first: 'test value', + second: 'another value', + }, + ], + }); + + const response = await authMemberAgent + .get(`/projects/${project.id}/data-stores/${dataStore.id}/rows`) + .expect(200); + + expect(response.body.data).toEqual({ + count: 1, + data: [ + { + id: 1, + first: 'test value', + second: 'another value', + }, + ], + }); + }); + + test('should list rows if user has project:admin role in team project', async () => { + const project = await createTeamProject('test project', owner); + await linkUserToProject(admin, project, 'project:admin'); + + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + data: [ + { + first: 'test value', + second: 'another value', + }, + ], + }); + + const response = await authAdminAgent + .get(`/projects/${project.id}/data-stores/${dataStore.id}/rows`) + .expect(200); + + expect(response.body.data).toEqual({ + count: 1, + data: [ + { + id: 1, + first: 'test value', + second: 'another value', + }, + ], + }); + }); + + test('should list rows in personal project', async () => { + const dataStore = await createDataStore(memberProject, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + data: [ + { + first: 'test value', + second: 'another value', + }, + ], + }); + + const response = await authMemberAgent + .get(`/projects/${memberProject.id}/data-stores/${dataStore.id}/rows`) + .expect(200); + + expect(response.body.data).toEqual({ + count: 1, + data: [ + { + id: 1, + first: 'test value', + second: 'another value', + }, + ], + }); + }); +}); + +describe('POST /projects/:projectId/data-stores/:dataStoreId/insert', () => { + test('should not insert rows when project does not exist', async () => { + const payload = { + data: [ + { + first: 'test value', + second: 'another value', + }, + ], + }; + + await authOwnerAgent + .post('/projects/non-existing-id/data-stores/some-data-store-id/insert') + .send(payload) + .expect(403); + }); + + test('should not insert rows when data store does not exist', async () => { + const project = await createTeamProject('test project', owner); + const payload = { + data: [ + { + first: 'test value', + second: 'another value', + }, + ], + }; + + await authOwnerAgent + .post(`/projects/${project.id}/data-stores/non-existing-id/insert`) + .send(payload) + .expect(404); + }); + + test("should not insert rows in another user's personal project data store", async () => { + const dataStore = await createDataStore(ownerProject, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + }); + + const payload = { + data: [ + { + first: 'test value', + second: 'another value', + }, + ], + }; + + await authMemberAgent + .post(`/projects/${ownerProject.id}/data-stores/${dataStore.id}/insert`) + .send(payload) + .expect(403); + }); + + test('should not insert rows if user has project:viewer role in team project', async () => { + const project = await createTeamProject('test project', owner); + await linkUserToProject(member, project, 'project:viewer'); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + }); + + const payload = { + data: [ + { + first: 'test value', + second: 'another value', + }, + ], + }; + + await authMemberAgent + .post(`/projects/${project.id}/data-stores/${dataStore.id}/insert`) + .send(payload) + .expect(403); + }); + + test('should insert rows if user has project:editor role in team project', async () => { + const project = await createTeamProject('test project', owner); + await linkUserToProject(member, project, 'project:editor'); + + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + }); + + const payload = { + data: [ + { + first: 'test value', + second: 'another value', + }, + ], + }; + + await authMemberAgent + .post(`/projects/${project.id}/data-stores/${dataStore.id}/insert`) + .send(payload) + .expect(200); + + const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {}); + expect(rowsInDb.count).toBe(1); + expect(rowsInDb.data[0]).toMatchObject(payload.data[0]); + }); + + test('should insert rows if user has project:admin role in team project', async () => { + const project = await createTeamProject('test project', owner); + await linkUserToProject(admin, project, 'project:admin'); + + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + }); + + const payload = { + data: [ + { + first: 'test value', + second: 'another value', + }, + ], + }; + + await authAdminAgent + .post(`/projects/${project.id}/data-stores/${dataStore.id}/insert`) + .send(payload) + .expect(200); + + const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {}); + expect(rowsInDb.count).toBe(1); + expect(rowsInDb.data[0]).toMatchObject(payload.data[0]); + }); + + test('should insert rows in personal project', async () => { + const dataStore = await createDataStore(memberProject, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + }); + + const payload = { + data: [ + { + first: 'test value', + second: 'another value', + }, + ], + }; + + await authMemberAgent + .post(`/projects/${memberProject.id}/data-stores/${dataStore.id}/insert`) + .send(payload) + .expect(200); + + const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {}); + expect(rowsInDb.count).toBe(1); + expect(rowsInDb.data[0]).toMatchObject(payload.data[0]); + }); + + test('should not insert rows when column does not exist', async () => { + const dataStore = await createDataStore(memberProject, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + }); + + const payload = { + data: [ + { + first: 'test value', + nonexisting: 'this does not exist', + }, + ], + }; + + const response = await authMemberAgent + .post(`/projects/${memberProject.id}/data-stores/${dataStore.id}/insert`) + .send(payload) + .expect(500); + + expect(response.body.message).toContain('unknown column'); + const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {}); + expect(rowsInDb.count).toBe(0); + }); +}); + +describe('POST /projects/:projectId/data-stores/:dataStoreId/upsert', () => { + test('should not upsert rows when project does not exist', async () => { + const payload = { + rows: [ + { + first: 'test value', + second: 'another value', + }, + ], + matchFields: ['first', 'second'], + }; + + await authOwnerAgent + .post('/projects/non-existing-id/data-stores/some-data-store-id/upsert') + .send(payload) + .expect(403); + }); + + test('should not upsert rows when data store does not exist', async () => { + const project = await createTeamProject('test project', owner); + const payload = { + rows: [ + { + first: 'test value', + second: 'another value', + }, + ], + matchFields: ['first', 'second'], + }; + + await authOwnerAgent + .post(`/projects/${project.id}/data-stores/non-existing-id/upsert`) + .send(payload) + .expect(404); + }); + + test("should not upsert rows in another user's personal project data store", async () => { + const dataStore = await createDataStore(ownerProject, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + }); + + const payload = { + rows: [ + { + first: 'test value', + second: 'another value', + }, + ], + matchFields: ['first', 'second'], + }; + + await authMemberAgent + .post(`/projects/${ownerProject.id}/data-stores/${dataStore.id}/upsert`) + .send(payload) + .expect(403); + }); + + test('should not upsert rows if user has project:viewer role in team project', async () => { + const project = await createTeamProject('test project', owner); + await linkUserToProject(member, project, 'project:viewer'); + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + }); + + const payload = { + rows: [ + { + first: 'test value', + second: 'another value', + }, + ], + matchFields: ['first', 'second'], + }; + + await authMemberAgent + .post(`/projects/${project.id}/data-stores/${dataStore.id}/upsert`) + .send(payload) + .expect(403); + }); + + test('should upsert rows if user has project:editor role in team project', async () => { + const project = await createTeamProject('test project', owner); + await linkUserToProject(member, project, 'project:editor'); + + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + }); + + const payload = { + rows: [ + { + first: 'test value', + second: 'another value', + }, + ], + matchFields: ['first', 'second'], + }; + + await authMemberAgent + .post(`/projects/${project.id}/data-stores/${dataStore.id}/upsert`) + .send(payload) + .expect(200); + + const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {}); + expect(rowsInDb.count).toBe(1); + expect(rowsInDb.data[0]).toMatchObject(payload.rows[0]); + }); + + test('should upsert rows if user has project:admin role in team project', async () => { + const project = await createTeamProject('test project', owner); + await linkUserToProject(admin, project, 'project:admin'); + + const dataStore = await createDataStore(project, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + }); + + const payload = { + rows: [ + { + first: 'test value', + second: 'another value', + }, + ], + matchFields: ['first', 'second'], + }; + + await authAdminAgent + .post(`/projects/${project.id}/data-stores/${dataStore.id}/upsert`) + .send(payload) + .expect(200); + + const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {}); + expect(rowsInDb.count).toBe(1); + expect(rowsInDb.data[0]).toMatchObject(payload.rows[0]); + }); + + test('should upsert rows in personal project', async () => { + const dataStore = await createDataStore(memberProject, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + }); + + const payload = { + rows: [ + { + first: 'test value', + second: 'another value', + }, + ], + matchFields: ['first', 'second'], + }; + + await authMemberAgent + .post(`/projects/${memberProject.id}/data-stores/${dataStore.id}/upsert`) + .send(payload) + .expect(200); + + const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {}); + expect(rowsInDb.count).toBe(1); + expect(rowsInDb.data[0]).toMatchObject(payload.rows[0]); + }); + + test('should not upsert rows when column does not exist', async () => { + const dataStore = await createDataStore(memberProject, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + }); + + const payload = { + rows: [ + { + first: 'test value', + nonexisting: 'this does not exist', + }, + ], + matchFields: ['first', 'second'], + }; + + const response = await authMemberAgent + .post(`/projects/${memberProject.id}/data-stores/${dataStore.id}/upsert`) + .send(payload) + .expect(500); + + expect(response.body.message).toContain('unknown column'); + const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {}); + expect(rowsInDb.count).toBe(0); + }); + + test('should update existing matched fields and insert new ones', async () => { + const dataStore = await createDataStore(memberProject, { + columns: [ + { + name: 'first', + type: 'string', + }, + { + name: 'second', + type: 'string', + }, + ], + data: [ + { + first: 'test row', + second: 'test value', + }, + { + first: 'test row', + second: 'another row with same first column', + }, + ], + }); + + const payload = { + rows: [ + { + first: 'test row', + second: 'updated value', + }, + { + first: 'new row', + second: 'new value', + }, + ], + matchFields: ['first'], + }; + + await authMemberAgent + .post(`/projects/${memberProject.id}/data-stores/${dataStore.id}/upsert`) + .send(payload) + .expect(200); + + const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), { + sortBy: ['id', 'ASC'], + }); + expect(rowsInDb.count).toBe(3); + expect(rowsInDb.data[0]).toMatchObject(payload.rows[0]); + expect(rowsInDb.data[1]).toMatchObject(payload.rows[0]); + expect(rowsInDb.data[2]).toMatchObject(payload.rows[1]); + }); +}); diff --git a/packages/cli/src/modules/data-store/data-store-column.repository.ts b/packages/cli/src/modules/data-store/data-store-column.repository.ts index b500aab710..e6aa866839 100644 --- a/packages/cli/src/modules/data-store/data-store-column.repository.ts +++ b/packages/cli/src/modules/data-store/data-store-column.repository.ts @@ -1,10 +1,12 @@ import { DataStoreCreateColumnSchema } from '@n8n/api-types'; import { Service } from '@n8n/di'; import { DataSource, EntityManager, Repository } from '@n8n/typeorm'; -import { UserError } from 'n8n-workflow'; +import { UnexpectedError, UserError } from 'n8n-workflow'; import { DataStoreColumn } from './data-store-column.entity'; import { DataStoreRowsRepository } from './data-store-rows.repository'; +import { NotFoundError } from '@/errors/response-errors/not-found.error'; +import { BadRequestError } from '@/errors/response-errors/bad-request.error'; @Service() export class DataStoreColumnRepository extends Repository { @@ -59,7 +61,7 @@ export class DataStoreColumnRepository extends Repository { const queryRunner = em.queryRunner; if (!queryRunner) { - throw new Error('QueryRunner is not available'); + throw new UnexpectedError('QueryRunner is not available'); } await this.dataStoreRowsRepository.ensureTableAndAddColumn( @@ -88,23 +90,25 @@ export class DataStoreColumnRepository extends Repository { async moveColumn(rawDataStoreId: string, columnId: string, targetIndex: number) { await this.manager.transaction(async (em) => { - const columnCount = await em.countBy(DataStoreColumn, { dataStoreId: rawDataStoreId }); - - if (targetIndex >= columnCount) { - throw new UserError('tried to move column to index larger than column count'); - } - - if (targetIndex < 0) { - throw new UserError('tried to move column to negative index'); - } - const existingColumn = await em.findOneBy(DataStoreColumn, { id: columnId, dataStoreId: rawDataStoreId, }); if (existingColumn === null) { - throw new UserError(`tried to move column not present in data store '${rawDataStoreId}'`); + throw new NotFoundError( + `tried to move column not present in data store '${rawDataStoreId}'`, + ); + } + + const columnCount = await em.countBy(DataStoreColumn, { dataStoreId: rawDataStoreId }); + + if (targetIndex >= columnCount) { + throw new BadRequestError('tried to move column to index larger than column count'); + } + + if (targetIndex < 0) { + throw new BadRequestError('tried to move column to negative index'); } await this.shiftColumns(rawDataStoreId, existingColumn.index, -1, em); diff --git a/packages/cli/src/modules/data-store/data-store-rows.repository.ts b/packages/cli/src/modules/data-store/data-store-rows.repository.ts index 19e11c2c5c..ad26e57b4b 100644 --- a/packages/cli/src/modules/data-store/data-store-rows.repository.ts +++ b/packages/cli/src/modules/data-store/data-store-rows.repository.ts @@ -37,7 +37,7 @@ function getConditionAndParams( dbType: DataSourceOptions['type'], ): [string, Record] { const paramName = `filter_${index}`; - const column = `dataStore.${quoteIdentifier(filter.columnName, dbType)}`; + const column = `${quoteIdentifier('dataStore', dbType)}.${quoteIdentifier(filter.columnName, dbType)}`; switch (filter.condition) { case 'eq': @@ -185,7 +185,6 @@ export class DataStoreRowsRepository { private applySorting(query: QueryBuilder, dto: ListDataStoreContentQueryDto): void { if (!dto.sortBy) { - // query.orderBy('dataStore.', 'DESC'); return; } @@ -195,7 +194,7 @@ export class DataStoreRowsRepository { private applySortingByField(query: QueryBuilder, field: string, direction: 'DESC' | 'ASC'): void { const dbType = this.dataSource.options.type; - const quotedField = `dataStore.${quoteIdentifier(field, dbType)}`; + const quotedField = `${quoteIdentifier('dataStore', dbType)}.${quoteIdentifier(field, dbType)}`; query.orderBy(quotedField, direction); } diff --git a/packages/cli/src/modules/data-store/data-store.service.ts b/packages/cli/src/modules/data-store/data-store.service.ts index b0169f2449..b5e8e219d2 100644 --- a/packages/cli/src/modules/data-store/data-store.service.ts +++ b/packages/cli/src/modules/data-store/data-store.service.ts @@ -110,7 +110,7 @@ export class DataStoreService { }); if (existingColumnMatch === null) { - throw new UserError( + throw new NotFoundError( `Tried to delete column with name not present in data store '${dataStoreId}'`, ); } diff --git a/packages/cli/test/integration/shared/db/data-stores.ts b/packages/cli/test/integration/shared/db/data-stores.ts index e0bb6902bc..a552257a12 100644 --- a/packages/cli/test/integration/shared/db/data-stores.ts +++ b/packages/cli/test/integration/shared/db/data-stores.ts @@ -1,15 +1,19 @@ -import type { CreateDataStoreColumnDto } from '@n8n/api-types'; +import type { CreateDataStoreColumnDto, DataStoreRows } from '@n8n/api-types'; import { randomName } from '@n8n/backend-test-utils'; import type { Project } from '@n8n/db'; import { Container } from '@n8n/di'; +import { DataStoreColumnRepository } from '@/modules/data-store/data-store-column.repository'; +import { DataStoreRowsRepository } from '@/modules/data-store/data-store-rows.repository'; import { DataStoreRepository } from '@/modules/data-store/data-store.repository'; +import { toTableName } from '@/modules/data-store/utils/sql-utils'; export const createDataStore = async ( project: Project, options: { name?: string; columns?: CreateDataStoreColumnDto[]; + data?: DataStoreRows; updatedAt?: Date; } = {}, ) => { @@ -27,5 +31,13 @@ export const createDataStore = async ( dataStore.updatedAt = options.updatedAt; } + if (options.data) { + const dataStoreColumnRepository = Container.get(DataStoreColumnRepository); + const columns = await dataStoreColumnRepository.getColumns(dataStore.id); + + const dataStoreRowsRepository = Container.get(DataStoreRowsRepository); + await dataStoreRowsRepository.insertRows(toTableName(dataStore.id), options.data, columns); + } + return dataStore; };