feat(core): Add createdAt and updatedAt to data store user tables (no-changelog) (#18723)

This commit is contained in:
Daria
2025-08-25 16:23:34 +03:00
committed by GitHub
parent 67352ce2f8
commit 3c8f40007e
4 changed files with 409 additions and 155 deletions

View File

@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import type { DataStore, DataStoreCreateColumnSchema } from '@n8n/api-types';
import {
createTeamProject,
@@ -271,7 +273,7 @@ describe('GET /projects/:projectId/data-stores', () => {
expect(response.body.data.count).toBe(2);
expect(response.body.data.data).toHaveLength(2);
expect(response.body.data.data.map((f: any) => f.name).sort()).toEqual(
expect((response.body.data.data as DataStore[]).map((f) => f.name).sort()).toEqual(
['Personal Data Store 1', 'Personal Data Store 2'].sort(),
);
});
@@ -288,7 +290,7 @@ describe('GET /projects/:projectId/data-stores', () => {
expect(response.body.data.count).toBe(2);
expect(response.body.data.data).toHaveLength(2);
expect(response.body.data.data.map((f: any) => f.name).sort()).toEqual(
expect((response.body.data.data as DataStore[]).map((f) => f.name).sort()).toEqual(
['Test Data Store 1', 'Test Data Store 2'].sort(),
);
});
@@ -305,7 +307,7 @@ describe('GET /projects/:projectId/data-stores', () => {
expect(response.body.data.count).toBe(2);
expect(response.body.data.data).toHaveLength(2);
expect(response.body.data.data.map((f: any) => f.name).sort()).toEqual(
expect((response.body.data.data as DataStore[]).map((f) => f.name).sort()).toEqual(
['Test Data Store', 'Test Something Else'].sort(),
);
});
@@ -357,7 +359,7 @@ describe('GET /projects/:projectId/data-stores', () => {
expect(response.body.data.count).toBe(5); // Total count should be 5
expect(response.body.data.data).toHaveLength(3); // But only 3 returned
expect(response.body.data.data.map((store: DataStore) => store.name)).toEqual([
expect((response.body.data.data as DataStore[]).map((store) => store.name)).toEqual([
'Data Store 5',
'Data Store 4',
'Data Store 3',
@@ -381,7 +383,7 @@ describe('GET /projects/:projectId/data-stores', () => {
expect(response.body.data.count).toBe(5);
expect(response.body.data.data).toHaveLength(3);
expect(response.body.data.data.map((store: DataStore) => store.name)).toEqual([
expect((response.body.data.data as DataStore[]).map((store) => store.name)).toEqual([
'Data Store 3',
'Data Store 2',
'Data Store 1',
@@ -405,7 +407,7 @@ describe('GET /projects/:projectId/data-stores', () => {
expect(response.body.data.count).toBe(5);
expect(response.body.data.data).toHaveLength(2);
expect(response.body.data.data.map((store: DataStore) => store.name)).toEqual([
expect((response.body.data.data as DataStore[]).map((store) => store.name)).toEqual([
'Data Store 4',
'Data Store 3',
]);
@@ -421,7 +423,7 @@ describe('GET /projects/:projectId/data-stores', () => {
.query({ sortBy: 'name:asc' })
.expect(200);
expect(response.body.data.data.map((store: DataStore) => store.name)).toEqual([
expect((response.body.data.data as DataStore[]).map((store) => store.name)).toEqual([
'A Data Store',
'M Data Store',
'Z Data Store',
@@ -438,7 +440,7 @@ describe('GET /projects/:projectId/data-stores', () => {
.query({ sortBy: 'name:desc' })
.expect(200);
expect(response.body.data.data.map((f: DataStore) => f.name)).toEqual([
expect((response.body.data.data as DataStore[]).map((f) => f.name)).toEqual([
'Z Data Store',
'M Data Store',
'A Data Store',
@@ -464,7 +466,7 @@ describe('GET /projects/:projectId/data-stores', () => {
.query({ sortBy: 'updatedAt:desc' })
.expect(200);
expect(response.body.data.data.map((f: DataStore) => f.name)).toEqual([
expect((response.body.data.data as DataStore[]).map((f) => f.name)).toEqual([
'Newest Data Store',
'Middle Data Store',
'Older Data Store',
@@ -1582,7 +1584,7 @@ describe('GET /projects/:projectId/data-stores/:dataStoreId/rows', () => {
.get(`/projects/${project.id}/data-stores/${dataStore.id}/rows`)
.expect(200);
expect(response.body.data).toEqual({
expect(response.body.data).toMatchObject({
count: 1,
data: [
{
@@ -1621,7 +1623,7 @@ describe('GET /projects/:projectId/data-stores/:dataStoreId/rows', () => {
.get(`/projects/${project.id}/data-stores/${dataStore.id}/rows`)
.expect(200);
expect(response.body.data).toEqual({
expect(response.body.data).toMatchObject({
count: 1,
data: [
{
@@ -1660,7 +1662,7 @@ describe('GET /projects/:projectId/data-stores/:dataStoreId/rows', () => {
.get(`/projects/${project.id}/data-stores/${dataStore.id}/rows`)
.expect(200);
expect(response.body.data).toEqual({
expect(response.body.data).toMatchObject({
count: 1,
data: [
{
@@ -1696,7 +1698,7 @@ describe('GET /projects/:projectId/data-stores/:dataStoreId/rows', () => {
.get(`/projects/${memberProject.id}/data-stores/${dataStore.id}/rows`)
.expect(200);
expect(response.body.data).toEqual({
expect(response.body.data).toMatchObject({
count: 1,
data: [
{
@@ -3053,7 +3055,11 @@ describe('PATCH /projects/:projectId/data-stores/:dataStoreId/rows', () => {
.expect(200);
expect(readResponse.body.data.count).toBe(1);
expect(readResponse.body.data.data[0]).toMatchObject({ id: 1, name: 'Alice', age: 31 });
expect(readResponse.body.data.data[0]).toMatchObject({
id: 1,
name: 'Alice',
age: 31,
});
});
test('should update row in personal project', async () => {
@@ -3080,7 +3086,11 @@ describe('PATCH /projects/:projectId/data-stores/:dataStoreId/rows', () => {
.expect(200);
expect(readResponse.body.data.count).toBe(1);
expect(readResponse.body.data.data[0]).toMatchObject({ id: 1, name: 'Alice', age: 31 });
expect(readResponse.body.data.data[0]).toMatchObject({
id: 1,
name: 'Alice',
age: 31,
});
});
test('should update row by id filter', async () => {
@@ -3112,8 +3122,16 @@ describe('PATCH /projects/:projectId/data-stores/:dataStoreId/rows', () => {
expect(readResponse.body.data.count).toBe(2);
expect(readResponse.body.data.data).toEqual(
expect.arrayContaining([
{ id: 1, name: 'Alice', age: 31 },
{ id: 2, name: 'Bob', age: 25 },
expect.objectContaining({
id: 1,
name: 'Alice',
age: 31,
}),
expect.objectContaining({
id: 2,
name: 'Bob',
age: 25,
}),
]),
);
});
@@ -3149,9 +3167,24 @@ describe('PATCH /projects/:projectId/data-stores/:dataStoreId/rows', () => {
expect(readResponse.body.data.count).toBe(3);
expect(readResponse.body.data.data).toEqual(
expect.arrayContaining([
{ id: 1, name: 'Alice', age: 30, department: 'Management' },
{ id: 2, name: 'Alice', age: 25, department: 'Marketing' },
{ id: 3, name: 'Bob', age: 30, department: 'Engineering' },
expect.objectContaining({
id: 1,
name: 'Alice',
age: 30,
department: 'Management',
}),
expect.objectContaining({
id: 2,
name: 'Alice',
age: 25,
department: 'Marketing',
}),
expect.objectContaining({
id: 3,
name: 'Bob',
age: 30,
department: 'Engineering',
}),
]),
);
});
@@ -3182,7 +3215,10 @@ describe('PATCH /projects/:projectId/data-stores/:dataStoreId/rows', () => {
.expect(200);
expect(readResponse.body.data.count).toBe(1);
expect(readResponse.body.data.data[0]).toMatchObject({ name: 'Alice', age: 30 });
expect(readResponse.body.data.data[0]).toMatchObject({
name: 'Alice',
age: 30,
});
});
test('should fail when filter is empty', async () => {

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import type { AddDataStoreColumnDto, CreateDataStoreColumnDto } from '@n8n/api-types';
import { createTeamProject, testDb, testModules } from '@n8n/backend-test-utils';
import { Project } from '@n8n/db';
@@ -62,11 +63,8 @@ describe('dataStore', () => {
type: 'string',
index: 0,
dataStoreId,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
id: expect.any(String),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
createdAt: expect.any(Date),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
updatedAt: expect.any(Date),
},
]);
@@ -101,7 +99,7 @@ describe('dataStore', () => {
const table = await queryRunner.getTable(userTableName);
const columnNames = table?.columns.map((col) => col.name);
expect(columnNames).toEqual(['id']);
expect(columnNames).toEqual(expect.arrayContaining(['id', 'createdAt', 'updatedAt']));
} finally {
await queryRunner.release();
}
@@ -272,66 +270,36 @@ describe('dataStore', () => {
}
const columnResult = await dataStoreService.getColumns(dataStoreId, project1.id);
expect(columnResult).toEqual([
{
expect.objectContaining({
index: 0,
dataStoreId,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
id: expect.any(String),
name: 'myColumn0',
type: 'string',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
createdAt: expect.any(Date),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
updatedAt: expect.any(Date),
},
{
}),
expect.objectContaining({
index: 1,
dataStoreId,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
id: expect.any(String),
name: 'myColumn1',
type: 'string',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
createdAt: expect.any(Date),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
updatedAt: expect.any(Date),
},
{
}),
expect.objectContaining({
index: 2,
dataStoreId,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
id: expect.any(String),
name: 'myColumn2',
type: 'number',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
createdAt: expect.any(Date),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
updatedAt: expect.any(Date),
},
{
}),
expect.objectContaining({
index: 3,
dataStoreId,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
id: expect.any(String),
name: 'myColumn3',
type: 'number',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
createdAt: expect.any(Date),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
updatedAt: expect.any(Date),
},
{
}),
expect.objectContaining({
index: 4,
dataStoreId,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
id: expect.any(String),
name: 'myColumn4',
type: 'date',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
createdAt: expect.any(Date),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
updatedAt: expect.any(Date),
},
}),
]);
const userTableName = dataStoreRowsRepository.toTableName(dataStoreId);
@@ -343,6 +311,8 @@ describe('dataStore', () => {
expect(columnNames).toEqual(
expect.arrayContaining([
'id',
'createdAt',
'updatedAt',
'myColumn0',
'myColumn1',
'myColumn2',
@@ -375,30 +345,18 @@ describe('dataStore', () => {
expect(columnResult.length).toBe(2);
expect(columnResult).toEqual([
{
expect.objectContaining({
index: 0,
dataStoreId,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
id: expect.any(String),
name: 'myColumn0',
type: 'string',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
createdAt: expect.any(Date),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
updatedAt: expect.any(Date),
},
{
}),
expect.objectContaining({
index: 1,
dataStoreId,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
id: expect.any(String),
name: 'myColumn1',
type: 'number',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
createdAt: expect.any(Date),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
updatedAt: expect.any(Date),
},
}),
]);
const userTableName = dataStoreRowsRepository.toTableName(dataStoreId);
@@ -407,7 +365,9 @@ describe('dataStore', () => {
const table = await queryRunner.getTable(userTableName);
const columnNames = table?.columns.map((col) => col.name);
expect(columnNames).toEqual(expect.arrayContaining(['id', 'myColumn0', 'myColumn1']));
expect(columnNames).toEqual(
expect.arrayContaining(['id', 'createdAt', 'updatedAt', 'myColumn0', 'myColumn1']),
);
} finally {
await queryRunner.release();
}
@@ -492,9 +452,24 @@ describe('dataStore', () => {
const updatedData = await dataStoreService.getManyRowsAndCount(dataStoreId, project1.id, {});
expect(updatedData.count).toBe(3);
expect(updatedData.data).toEqual([
{ id: 1, name: 'Alice', age: 30, email: null },
{ id: 2, name: 'Bob', age: 25, email: null },
{ id: 3, name: 'Charlie', age: 35, email: null },
expect.objectContaining({
id: 1,
name: 'Alice',
age: 30,
email: null,
}),
expect.objectContaining({
id: 2,
name: 'Bob',
age: 25,
email: null,
}),
expect.objectContaining({
id: 3,
name: 'Charlie',
age: 35,
email: null,
}),
]);
// Verify we can insert new rows with the new column
@@ -510,10 +485,30 @@ describe('dataStore', () => {
const finalData = await dataStoreService.getManyRowsAndCount(dataStoreId, project1.id, {});
expect(finalData.count).toBe(4);
expect(finalData.data).toEqual([
{ id: 1, name: 'Alice', age: 30, email: null },
{ id: 2, name: 'Bob', age: 25, email: null },
{ id: 3, name: 'Charlie', age: 35, email: null },
{ id: 4, name: 'David', age: 28, email: 'david@example.com' },
expect.objectContaining({
id: 1,
name: 'Alice',
age: 30,
email: null,
}),
expect.objectContaining({
id: 2,
name: 'Bob',
age: 25,
email: null,
}),
expect.objectContaining({
id: 3,
name: 'Charlie',
age: 35,
email: null,
}),
expect.objectContaining({
id: 4,
name: 'David',
age: 28,
email: 'david@example.com',
}),
]);
});
});
@@ -550,7 +545,6 @@ describe('dataStore', () => {
name: 'myColumn2',
type: 'number',
createdAt: c2.createdAt,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
updatedAt: expect.any(Date),
},
]);
@@ -656,7 +650,6 @@ describe('dataStore', () => {
expect(result.data).toHaveLength(1);
expect(result.data[0]).toEqual({
...dataStore,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
project: expect.any(Project),
});
expect(result.data[0].project).toEqual({
@@ -689,12 +682,10 @@ describe('dataStore', () => {
expect(result.data).toHaveLength(2);
expect(result.data).toContainEqual({
...dataStore2,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
project: expect.any(Project),
});
expect(result.data).toContainEqual({
...dataStore1,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
project: expect.any(Project),
});
expect(result.count).toEqual(2);
@@ -803,46 +794,34 @@ describe('dataStore', () => {
expect(resultColumns).toEqual(
expect.arrayContaining([
{
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
id: expect.any(String),
name: 'myColumn1',
type: 'string',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
createdAt: expect.any(Date),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
updatedAt: expect.any(Date),
index: 0,
},
{
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
id: expect.any(String),
name: 'myColumn2',
type: 'number',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
createdAt: expect.any(Date),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
updatedAt: expect.any(Date),
index: 1,
},
{
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
id: expect.any(String),
name: 'myColumn3',
type: 'number',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
createdAt: expect.any(Date),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
updatedAt: expect.any(Date),
index: 2,
},
{
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
id: expect.any(String),
name: 'myColumn4',
type: 'date',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
createdAt: expect.any(Date),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
updatedAt: expect.any(Date),
index: 3,
},
@@ -998,14 +977,16 @@ describe('dataStore', () => {
);
expect(count).toEqual(4);
expect(data).toEqual(
rows.map((row, i) => ({
...row,
id: i + 1,
c1: row.c1,
c2: row.c2,
c3: row.c3 instanceof Date ? row.c3.toISOString() : row.c3,
c4: row.c4,
})),
rows.map(
(row) =>
expect.objectContaining({
...row,
c1: row.c1,
c2: row.c2,
c3: row.c3 instanceof Date ? row.c3.toISOString() : row.c3,
c4: row.c4,
}) as Record<string, unknown>,
),
);
});
@@ -1037,8 +1018,16 @@ describe('dataStore', () => {
expect(count).toEqual(2);
expect(data).toEqual([
{ c1: 1, c2: 'foo', id: 1 },
{ c1: 1, c2: 'foo', id: 2 },
expect.objectContaining({
c1: 1,
c2: 'foo',
id: 1,
}),
expect.objectContaining({
c1: 1,
c2: 'foo',
id: 2,
}),
]);
});
@@ -1074,9 +1063,21 @@ describe('dataStore', () => {
expect(count).toEqual(3);
expect(data).toEqual([
{ c1: 2, c2: 'bar', id: 2 },
{ c1: 1, c2: 'baz', id: 3 },
{ c1: 2, c2: 'faz', id: 4 },
expect.objectContaining({
c1: 2,
c2: 'bar',
id: 2,
}),
expect.objectContaining({
c1: 1,
c2: 'baz',
id: 3,
}),
expect.objectContaining({
c1: 2,
c2: 'faz',
id: 4,
}),
]);
});
@@ -1271,7 +1272,23 @@ describe('dataStore', () => {
{},
);
expect(count).toEqual(3);
expect(data).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]);
expect(data).toEqual([
{
id: 1,
createdAt: expect.any(String),
updatedAt: expect.any(String),
},
{
id: 2,
createdAt: expect.any(String),
updatedAt: expect.any(String),
},
{
id: 3,
createdAt: expect.any(String),
updatedAt: expect.any(String),
},
]);
});
});
@@ -1316,9 +1333,21 @@ describe('dataStore', () => {
expect(count).toEqual(3);
expect(data).toEqual(
expect.arrayContaining([
{ pid: '1995-111a', name: 'Alicia', age: 31, id: 1 },
{ pid: '1994-222a', name: 'John', age: 32, id: 2 },
{ pid: '1993-333a', name: 'Paul', age: 32, id: 3 }, // unchanged
expect.objectContaining({
pid: '1995-111a',
name: 'Alicia',
age: 31,
}),
expect.objectContaining({
pid: '1994-222a',
name: 'John',
age: 32,
}),
expect.objectContaining({
pid: '1993-333a',
name: 'Paul',
age: 32,
}), // unchanged
]),
);
});
@@ -1361,9 +1390,21 @@ describe('dataStore', () => {
expect(count).toEqual(3);
expect(data).toEqual(
expect.arrayContaining([
{ city: 'Berlin', age: 30, isEligible: true, id: 1 },
{ city: 'Amsterdam', age: 32, isEligible: true, id: 2 },
{ city: 'Oslo', age: 28, isEligible: false, id: 3 },
expect.objectContaining({
city: 'Berlin',
age: 30,
isEligible: true,
}),
expect.objectContaining({
city: 'Amsterdam',
age: 32,
isEligible: true,
}),
expect.objectContaining({
city: 'Oslo',
age: 28,
isEligible: false,
}),
]),
);
});
@@ -1402,8 +1443,16 @@ describe('dataStore', () => {
expect(count).toEqual(2);
expect(data).toEqual([
{ name: 'Alice', age: 30, id: 1, pid: '1995-111a' },
{ name: 'Alice', age: 30, id: 2, pid: '1992-222b' },
expect.objectContaining({
name: 'Alice',
age: 30,
pid: '1995-111a',
}),
expect.objectContaining({
name: 'Alice',
age: 30,
pid: '1992-222b',
}),
]);
});
});
@@ -1428,10 +1477,6 @@ describe('dataStore', () => {
]);
expect(ids).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]);
// Get initial data to find row IDs
const initialData = await dataStoreService.getManyRowsAndCount(dataStoreId, project1.id, {});
expect(initialData.count).toBe(3);
// ACT - Delete first and third rows
const result = await dataStoreService.deleteRows(dataStoreId, project1.id, [1, 3]);
@@ -1440,7 +1485,12 @@ describe('dataStore', () => {
const rows = await dataStoreService.getManyRowsAndCount(dataStoreId, project1.id, {});
expect(rows.count).toBe(1);
expect(rows.data).toEqual([{ name: 'Bob', age: 25, id: 2 }]);
expect(rows.data).toEqual([
expect.objectContaining({
name: 'Bob',
age: 25,
}),
]);
});
it('returns true when deleting empty list of IDs', async () => {
@@ -1517,8 +1567,18 @@ describe('dataStore', () => {
const { data } = await dataStoreService.getManyRowsAndCount(dataStoreId, project1.id, {});
expect(data).toEqual(
expect.arrayContaining([
{ id: 1, name: 'Alice', age: 31, active: false },
{ id: 2, name: 'Bob', age: 25, active: false },
expect.objectContaining({
id: 1,
name: 'Alice',
age: 31,
active: false,
}),
expect.objectContaining({
id: 2,
name: 'Bob',
age: 25,
active: false,
}),
]),
);
});
@@ -1535,8 +1595,16 @@ describe('dataStore', () => {
});
await dataStoreService.insertRows(dataStoreId, project1.id, [
{ name: 'Alice', age: 30, active: true },
{ name: 'Bob', age: 25, active: false },
{
name: 'Alice',
age: 30,
active: true,
},
{
name: 'Bob',
age: 25,
active: false,
},
]);
// ACT
@@ -1551,12 +1619,72 @@ describe('dataStore', () => {
const { data } = await dataStoreService.getManyRowsAndCount(dataStoreId, project1.id, {});
expect(data).toEqual(
expect.arrayContaining([
{ id: 1, name: 'Alicia', age: 31, active: false },
{ id: 2, name: 'Bob', age: 25, active: false },
expect.objectContaining({
id: 1,
name: 'Alicia',
age: 31,
active: false,
}),
expect.objectContaining({
id: 2,
name: 'Bob',
age: 25,
active: false,
}),
]),
);
});
it('should update the updatedAt', async () => {
// ARRANGE
const { id: dataStoreId } = await dataStoreService.createDataStore(project1.id, {
name: 'dataStore',
columns: [
{ name: 'name', type: 'string' },
{ name: 'age', type: 'number' },
{ name: 'active', type: 'boolean' },
],
});
await dataStoreService.insertRows(dataStoreId, project1.id, [
{ name: 'Alice', age: 30, active: true },
]);
const { data: initialRows } = await dataStoreService.getManyRowsAndCount(
dataStoreId,
project1.id,
{},
);
// Wait to ensure different timestamps
await new Promise((resolve) => setTimeout(resolve, 10));
// ACT
const result = await dataStoreService.updateRow(dataStoreId, project1.id, {
filter: { name: 'Alice' },
data: { age: 31, active: false },
});
// ASSERT
expect(result).toBe(true);
const { data: updatedRows } = await dataStoreService.getManyRowsAndCount(
dataStoreId,
project1.id,
{},
);
expect(updatedRows[0].createdAt).not.toBeNull();
expect(updatedRows[0].updatedAt).not.toBeNull();
expect(initialRows[0].updatedAt).not.toBeNull();
expect(new Date(updatedRows[0].updatedAt as string).getTime()).toBeGreaterThan(
new Date(initialRows[0].updatedAt as string).getTime(),
);
expect(new Date(updatedRows[0].updatedAt as string).getTime()).toBeGreaterThan(
new Date(updatedRows[0].createdAt as string).getTime(),
);
});
it('should update row with multiple filter conditions', async () => {
// ARRANGE
const { id: dataStoreId } = await dataStoreService.createDataStore(project1.id, {
@@ -1586,9 +1714,24 @@ describe('dataStore', () => {
const { data } = await dataStoreService.getManyRowsAndCount(dataStoreId, project1.id, {});
expect(data).toEqual(
expect.arrayContaining([
{ id: 1, name: 'Alice', age: 30, department: 'Management' },
{ id: 2, name: 'Alice', age: 25, department: 'Marketing' },
{ id: 3, name: 'Bob', age: 30, department: 'Engineering' },
expect.objectContaining({
id: 1,
name: 'Alice',
age: 30,
department: 'Management',
}),
expect.objectContaining({
id: 2,
name: 'Alice',
age: 25,
department: 'Marketing',
}),
expect.objectContaining({
id: 3,
name: 'Bob',
age: 30,
department: 'Engineering',
}),
]),
);
});
@@ -1615,7 +1758,12 @@ describe('dataStore', () => {
expect(result).toBe(true);
const { data } = await dataStoreService.getManyRowsAndCount(dataStoreId, project1.id, {});
expect(data).toEqual([{ id: 1, name: 'Alice', age: 30 }]);
expect(data).toEqual([
expect.objectContaining({
name: 'Alice',
age: 30,
}),
]);
});
it('should throw validation error when filters are empty', async () => {
@@ -1642,7 +1790,12 @@ describe('dataStore', () => {
);
const { data } = await dataStoreService.getManyRowsAndCount(dataStoreId, project1.id, {});
expect(data).toEqual([{ id: 1, name: 'Alice', age: 30 }]);
expect(data).toEqual([
expect.objectContaining({
name: 'Alice',
age: 30,
}),
]);
});
it('should throw validation error when data is empty', async () => {
@@ -1669,7 +1822,12 @@ describe('dataStore', () => {
);
const { data } = await dataStoreService.getManyRowsAndCount(dataStoreId, project1.id, {});
expect(data).toEqual([{ id: 1, name: 'Alice', age: 30 }]);
expect(data).toEqual([
expect.objectContaining({
name: 'Alice',
age: 30,
}),
]);
});
it('should fail when data store does not exist', async () => {
@@ -1764,7 +1922,13 @@ describe('dataStore', () => {
expect(result).toBe(true);
const { data } = await dataStoreService.getManyRowsAndCount(dataStoreId, project1.id, {});
expect(data).toEqual([{ id: 1, name: 'Alice', age: 31, active: true }]);
expect(data).toEqual([
expect.objectContaining({
name: 'Alice',
age: 31,
active: true,
}),
]);
});
it('should handle date column updates correctly', async () => {
@@ -1793,7 +1957,12 @@ describe('dataStore', () => {
expect(result).toBe(true);
const { data } = await dataStoreService.getManyRowsAndCount(dataStoreId, project1.id, {});
expect(data).toEqual([{ id: 1, name: 'Alice', birthDate: newDate.toISOString() }]);
expect(data).toEqual([
expect.objectContaining({
name: 'Alice',
birthDate: newDate.toISOString(),
}),
]);
});
});
@@ -1811,9 +1980,24 @@ describe('dataStore', () => {
});
const rows = [
{ c1: 3, c2: true, c3: new Date(0), c4: 'hello?' },
{ c1: 4, c2: false, c3: new Date(1), c4: 'hello!' },
{ c1: 5, c2: true, c3: new Date(2), c4: 'hello.' },
{
c1: 3,
c2: true,
c3: new Date(0),
c4: 'hello?',
},
{
c1: 4,
c2: false,
c3: new Date(1),
c4: 'hello!',
},
{
c1: 5,
c2: true,
c3: new Date(2),
c4: 'hello.',
},
];
const ids = await dataStoreService.insertRows(dataStoreId, project1.id, rows);
@@ -1826,9 +2010,33 @@ describe('dataStore', () => {
expect(result.count).toEqual(3);
// Assuming IDs are auto-incremented starting from 1
expect(result.data).toEqual([
{ c1: rows[0].c1, c2: rows[0].c2, c3: '1970-01-01T00:00:00.000Z', c4: rows[0].c4, id: 1 },
{ c1: rows[1].c1, c2: rows[1].c2, c3: '1970-01-01T00:00:00.001Z', c4: rows[1].c4, id: 2 },
{ c1: rows[2].c1, c2: rows[2].c2, c3: '1970-01-01T00:00:00.002Z', c4: rows[2].c4, id: 3 },
{
c1: rows[0].c1,
c2: rows[0].c2,
c3: '1970-01-01T00:00:00.000Z',
c4: rows[0].c4,
id: 1,
createdAt: expect.any(String),
updatedAt: expect.any(String),
},
{
c1: rows[1].c1,
c2: rows[1].c2,
c3: '1970-01-01T00:00:00.001Z',
c4: rows[1].c4,
id: 2,
createdAt: expect.any(String),
updatedAt: expect.any(String),
},
{
c1: rows[2].c1,
c2: rows[2].c2,
c3: '1970-01-01T00:00:00.002Z',
c4: rows[2].c4,
id: 3,
createdAt: expect.any(String),
updatedAt: expect.any(String),
},
]);
});
});

View File

@@ -177,6 +177,9 @@ export class DataStoreRowsRepository {
setValues[key] = normalizeValue(value, columnTypeMap[key], dbType);
}
// Always update the updatedAt timestamp
setValues.updatedAt = normalizeValue(new Date(), 'date', dbType);
queryBuilder.set(setValues);
const normalizedWhereData: Record<string, DataStoreColumnJsType | null> = {};
@@ -208,8 +211,9 @@ export class DataStoreRowsRepository {
queryRunner: QueryRunner,
) {
const dslColumns = [new DslColumn('id').int.autoGenerate2.primary, ...toDslColumns(columns)];
const createTable = new CreateTable(this.toTableName(dataStoreId), '', queryRunner);
createTable.withColumns.apply(createTable, dslColumns);
const createTable = new CreateTable(this.toTableName(dataStoreId), '', queryRunner).withColumns(
...dslColumns,
).withTimestamps;
await createTable.execute(queryRunner);
}

View File

@@ -193,7 +193,13 @@ export function extractInsertedIds(raw: unknown, dbType: DataSourceOptions['type
}
export function normalizeRows(rows: DataStoreRows, columns: DataStoreColumn[]) {
const typeMap = new Map(columns.map((col) => [col.name, col.type]));
// we need to normalize system dates as well
const systemColumns = [
{ name: 'createdAt', type: 'date' },
{ name: 'updatedAt', type: 'date' },
];
const typeMap = new Map([...columns, ...systemColumns].map((col) => [col.name, col.type]));
return rows.map((row) => {
const normalized = { ...row };
for (const [key, value] of Object.entries(row)) {