Skip to content

Commit 1a193ba

Browse files
Add unit tests for sql sanitization
1 parent 09753eb commit 1a193ba

File tree

3 files changed

+62
-10
lines changed

3 files changed

+62
-10
lines changed

packages/common/src/client/triggers/sanitizeSQL.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ export function sanitizeSQL(strings: TemplateStringsArray, ...values: any[]): st
4949
if (i < values.length) {
5050
// For SQL, escape single quotes in string values
5151
const value = values[i];
52-
if (typeof value === 'string') {
52+
if (typeof value == 'string') {
5353
result += sanitizeString(value);
54-
} else if (value === null || value === undefined) {
54+
} else if (value == null) {
5555
result += 'NULL';
5656
} else if (typeof value == 'object') {
5757
// Stringify the object and escape single quotes in the result

packages/common/tests/sql.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { sanitizeSQL } from '../src/client/triggers/sanitizeSQL.js';
3+
describe('SQL', () => {
4+
describe('sanitization', () => {
5+
it('should sanitize quoted strings', () => {
6+
expect(sanitizeSQL`New.id = ${"O'Reilly"}`).toBe("New.id = 'O''Reilly'");
7+
});
8+
9+
it('should handle null and undefined', () => {
10+
expect(sanitizeSQL`val = ${null}`).toBe('val = NULL');
11+
expect(sanitizeSQL`val = ${undefined}`).toBe('val = NULL');
12+
});
13+
14+
it('should handle numbers', () => {
15+
expect(sanitizeSQL`age = ${42}`).toBe('age = 42');
16+
expect(sanitizeSQL`price = ${3.14}`).toBe('price = 3.14');
17+
});
18+
19+
it('should handle objects', () => {
20+
expect(sanitizeSQL`data = ${{ foo: 'bar' }}`).toBe(`data = '{"foo":"bar"}'`);
21+
});
22+
23+
it('should escape single quotes in stringified objects', () => {
24+
const obj = { foo: "O'Reilly" };
25+
const clause = sanitizeSQL`data = ${obj}`;
26+
expect(clause).toBe(`data = '{"foo":"O''Reilly"}'`);
27+
});
28+
29+
it('should interpolate multiple values', () => {
30+
const name = 'Alice';
31+
const age = 30;
32+
const clause = sanitizeSQL`name = ${name} AND age = ${age}`;
33+
expect(clause).toBe("name = 'Alice' AND age = 30");
34+
});
35+
36+
it('should stringify arrays', () => {
37+
expect(sanitizeSQL`arr = ${[1, 2, 3]}`).toBe(`arr = '[1,2,3]'`);
38+
expect(sanitizeSQL`arr = ${['a', "O'Reilly", null]}`).toBe(`arr = '["a","O''Reilly",null]'`);
39+
});
40+
});
41+
});

packages/node/tests/trigger.test.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ describe('Triggers', () => {
2424
source: 'todos',
2525
destination: tempTable,
2626
columns: filteredColumns,
27-
operations: [DiffTriggerOperation.INSERT, DiffTriggerOperation.UPDATE, DiffTriggerOperation.DELETE]
27+
when: {
28+
[DiffTriggerOperation.INSERT]: 'TRUE',
29+
[DiffTriggerOperation.UPDATE]: 'TRUE',
30+
[DiffTriggerOperation.DELETE]: 'TRUE'
31+
}
2832
});
2933

3034
const results = [] as TriggerDiffRecord[];
@@ -116,7 +120,6 @@ describe('Triggers', () => {
116120
when: {
117121
[DiffTriggerOperation.INSERT]: sanitizeSQL`json_extract(NEW.data, '$.list_id') = ${sanitizeUUID(firstList.id)}`
118122
},
119-
operations: [DiffTriggerOperation.INSERT],
120123
onChange: async (context) => {
121124
// Fetches the current state of todo records that were inserted during this diff window.
122125
const newTodos = await context.withDiff<Database['todos']>(/* sql */ `
@@ -193,8 +196,10 @@ describe('Triggers', () => {
193196
*/
194197
await database.triggers.trackTableDiff({
195198
source: 'lists',
196-
when: { [DiffTriggerOperation.UPDATE]: sanitizeSQL`NEW.id = ${sanitizeUUID(list.id)}` },
197-
operations: [DiffTriggerOperation.UPDATE, DiffTriggerOperation.DELETE],
199+
when: {
200+
[DiffTriggerOperation.UPDATE]: sanitizeSQL`NEW.id = ${sanitizeUUID(list.id)}`,
201+
[DiffTriggerOperation.DELETE]: 'TRUE'
202+
},
198203
onChange: async (context) => {
199204
// Fetches the todo records that were inserted during this diff
200205
const diffs = await context.withExtractedDiff<ExtractedTriggerDiffRecord<Database['lists']>>(/* sql */ `
@@ -305,7 +310,6 @@ describe('Triggers', () => {
305310
when: {
306311
[DiffTriggerOperation.INSERT]: sanitizeSQL`json_extract(NEW.data, '$.list_id') = ${sanitizeUUID(firstList.id)}`
307312
},
308-
operations: [DiffTriggerOperation.INSERT],
309313
onChange: async (context) => {
310314
// Fetches the todo records that were inserted during this diff
311315
const newTodos = await context.withDiff<Database['todos']>(/* sql */ `
@@ -395,7 +399,6 @@ describe('Triggers', () => {
395399
when: {
396400
[DiffTriggerOperation.INSERT]: sanitizeSQL`json_extract(NEW.data, '$.list_id') = ${sanitizeUUID(firstList.id)}`
397401
},
398-
operations: [DiffTriggerOperation.INSERT],
399402
onChange: async (context) => {
400403
// Fetches the content of the records at the time of the operation
401404
const extractedDiff = await context.withExtractedDiff<{ content: string; operation: DiffTriggerOperation }>(
@@ -456,7 +459,11 @@ describe('Triggers', () => {
456459

457460
await database.triggers.trackTableDiff({
458461
source: 'todos',
459-
operations: [DiffTriggerOperation.INSERT, DiffTriggerOperation.UPDATE, DiffTriggerOperation.DELETE],
462+
when: {
463+
[DiffTriggerOperation.INSERT]: 'TRUE',
464+
[DiffTriggerOperation.UPDATE]: 'TRUE',
465+
[DiffTriggerOperation.DELETE]: 'TRUE'
466+
},
460467
// Only track the row ids
461468
columns: [],
462469
onChange: async (context) => {
@@ -535,7 +542,11 @@ describe('Triggers', () => {
535542

536543
await database.triggers.trackTableDiff({
537544
source: 'todos',
538-
operations: [DiffTriggerOperation.INSERT, DiffTriggerOperation.UPDATE, DiffTriggerOperation.DELETE],
545+
when: {
546+
[DiffTriggerOperation.INSERT]: 'TRUE',
547+
[DiffTriggerOperation.UPDATE]: 'TRUE',
548+
[DiffTriggerOperation.DELETE]: 'TRUE'
549+
},
539550
columns: ['columnA'],
540551
onChange: async (context) => {
541552
// Fetches the content of the records at the time of the operation

0 commit comments

Comments
 (0)