Skip to content

Commit acfbf7a

Browse files
committed
test(tables): add updateRow partial merge tests
Covers the bug where partial updates wiped unmentioned columns — verifies that fields not in the update payload are preserved, nulling a field works, full-row updates are idempotent, and missing rows throw correctly.
1 parent 7c776a4 commit acfbf7a

1 file changed

Lines changed: 120 additions & 0 deletions

File tree

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { databaseMock } from '@sim/testing'
5+
import { beforeEach, describe, expect, it, vi } from 'vitest'
6+
import { updateRow } from '../service'
7+
import type { TableDefinition } from '../types'
8+
9+
const EXISTING_ROW = {
10+
id: 'row-1',
11+
tableId: 'tbl-1',
12+
workspaceId: 'ws-1',
13+
data: { name: 'Alice', age: 30 },
14+
position: 1,
15+
createdAt: new Date('2024-01-01'),
16+
updatedAt: new Date('2024-01-01'),
17+
}
18+
19+
const TABLE: TableDefinition = {
20+
id: 'tbl-1',
21+
name: 'People',
22+
description: null,
23+
schema: {
24+
columns: [
25+
{ name: 'name', type: 'string' },
26+
{ name: 'age', type: 'number' },
27+
],
28+
},
29+
metadata: null,
30+
rowCount: 0,
31+
maxRows: 1000,
32+
workspaceId: 'ws-1',
33+
createdBy: 'user-1',
34+
archivedAt: null,
35+
createdAt: new Date('2024-01-01'),
36+
updatedAt: new Date('2024-01-01'),
37+
}
38+
39+
describe('updateRow — partial merge', () => {
40+
let mockSet: ReturnType<typeof vi.fn>
41+
42+
beforeEach(() => {
43+
vi.clearAllMocks()
44+
45+
// db.select() → used by getRowById to fetch the existing row
46+
const mockLimit = vi.fn().mockResolvedValue([EXISTING_ROW])
47+
const mockSelectWhere = vi.fn().mockReturnValue({ limit: mockLimit })
48+
const mockFrom = vi.fn().mockReturnValue({ where: mockSelectWhere })
49+
databaseMock.db.select.mockReturnValue({ from: mockFrom })
50+
51+
// db.update() → captures what merged data gets written
52+
const mockUpdateWhere = vi.fn().mockResolvedValue([])
53+
mockSet = vi.fn().mockReturnValue({ where: mockUpdateWhere })
54+
databaseMock.db.update.mockReturnValue({ set: mockSet })
55+
})
56+
57+
it('preserves columns not included in the partial update', async () => {
58+
const result = await updateRow(
59+
{ tableId: 'tbl-1', rowId: 'row-1', data: { age: 31 }, workspaceId: 'ws-1' },
60+
TABLE,
61+
'req-1'
62+
)
63+
64+
expect(result.data).toEqual({ name: 'Alice', age: 31 })
65+
expect(mockSet).toHaveBeenCalledWith(
66+
expect.objectContaining({ data: { name: 'Alice', age: 31 } })
67+
)
68+
})
69+
70+
it('allows updating a single column without affecting others', async () => {
71+
const result = await updateRow(
72+
{ tableId: 'tbl-1', rowId: 'row-1', data: { name: 'Bob' }, workspaceId: 'ws-1' },
73+
TABLE,
74+
'req-1'
75+
)
76+
77+
expect(result.data).toEqual({ name: 'Bob', age: 30 })
78+
expect(mockSet).toHaveBeenCalledWith(
79+
expect.objectContaining({ data: { name: 'Bob', age: 30 } })
80+
)
81+
})
82+
83+
it('allows explicitly nulling a field while preserving others', async () => {
84+
const result = await updateRow(
85+
{ tableId: 'tbl-1', rowId: 'row-1', data: { age: null }, workspaceId: 'ws-1' },
86+
TABLE,
87+
'req-1'
88+
)
89+
90+
expect(result.data).toEqual({ name: 'Alice', age: null })
91+
expect(mockSet).toHaveBeenCalledWith(
92+
expect.objectContaining({ data: { name: 'Alice', age: null } })
93+
)
94+
})
95+
96+
it('handles a full-row update correctly (idempotent merge)', async () => {
97+
const result = await updateRow(
98+
{ tableId: 'tbl-1', rowId: 'row-1', data: { name: 'Bob', age: 25 }, workspaceId: 'ws-1' },
99+
TABLE,
100+
'req-1'
101+
)
102+
103+
expect(result.data).toEqual({ name: 'Bob', age: 25 })
104+
})
105+
106+
it('throws when the row does not exist', async () => {
107+
const mockLimit = vi.fn().mockResolvedValue([])
108+
const mockSelectWhere = vi.fn().mockReturnValue({ limit: mockLimit })
109+
const mockFrom = vi.fn().mockReturnValue({ where: mockSelectWhere })
110+
databaseMock.db.select.mockReturnValue({ from: mockFrom })
111+
112+
await expect(
113+
updateRow(
114+
{ tableId: 'tbl-1', rowId: 'row-missing', data: { age: 31 }, workspaceId: 'ws-1' },
115+
TABLE,
116+
'req-1'
117+
)
118+
).rejects.toThrow('Row not found')
119+
})
120+
})

0 commit comments

Comments
 (0)