Skip to content

Commit bc41e1e

Browse files
committed
test: add delimiters and unicode query patterns
1 parent c6e327d commit bc41e1e

3 files changed

Lines changed: 947 additions & 529 deletions

File tree

test/delimiters.test.ts

Lines changed: 391 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
import { assert, describe, test } from 'poku';
2+
import { format, raw } from '../src/index.ts';
3+
4+
describe('URLs and connection strings', () => {
5+
test('JDBC connection string', () => {
6+
const connStr =
7+
'jdbc:mysql://localhost:3306/db?useSSL=false;serverTimezone=UTC';
8+
const sql = format('INSERT INTO configs (name, value) VALUES (?, ?)', [
9+
'database_url',
10+
connStr,
11+
]);
12+
assert.equal(
13+
sql,
14+
"INSERT INTO configs (name, value) VALUES ('database_url', 'jdbc:mysql://localhost:3306/db?useSSL=false;serverTimezone=UTC')"
15+
);
16+
});
17+
18+
test('legacy URL with semicolon params', () => {
19+
const url = 'https://example.com/search?q=test;page=2;sort=date';
20+
const sql = format('INSERT INTO bookmarks (url) VALUES (?)', [url]);
21+
assert.equal(
22+
sql,
23+
"INSERT INTO bookmarks (url) VALUES ('https://example.com/search?q=test;page=2;sort=date')"
24+
);
25+
});
26+
27+
test('Redis connection string', () => {
28+
const redis = 'redis://user:pass@localhost:6379/0;timeout=5000';
29+
const sql = format('UPDATE settings SET ? WHERE key = ?', [
30+
{ value: redis },
31+
'redis_url',
32+
]);
33+
assert.equal(
34+
sql,
35+
"UPDATE settings SET `value` = 'redis://user:pass@localhost:6379/0;timeout=5000' WHERE key = 'redis_url'"
36+
);
37+
});
38+
});
39+
40+
describe('Windows paths and environment variables', () => {
41+
test('Windows PATH variable', () => {
42+
const path = 'C:\\Program Files\\App;D:\\Tools;C:\\Windows\\System32';
43+
const sql = format('INSERT INTO env_vars (name, value) VALUES (?, ?)', [
44+
'PATH',
45+
path,
46+
]);
47+
assert.equal(
48+
sql,
49+
"INSERT INTO env_vars (name, value) VALUES ('PATH', 'C:\\\\Program Files\\\\App;D:\\\\Tools;C:\\\\Windows\\\\System32')"
50+
);
51+
});
52+
53+
test('classpath-style configuration', () => {
54+
const classpath = 'lib/app.jar;lib/utils.jar;lib/deps/*';
55+
const sql = format('INSERT INTO java_configs (classpath) VALUES (?)', [
56+
classpath,
57+
]);
58+
assert.equal(
59+
sql,
60+
"INSERT INTO java_configs (classpath) VALUES ('lib/app.jar;lib/utils.jar;lib/deps/*')"
61+
);
62+
});
63+
});
64+
65+
describe('CSV and delimited data', () => {
66+
test('semicolon-delimited CSV row', () => {
67+
const csvRow = 'John;Doe;john@example.com;Active';
68+
const sql = format('INSERT INTO imports (raw_data) VALUES (?)', [csvRow]);
69+
assert.equal(
70+
sql,
71+
"INSERT INTO imports (raw_data) VALUES ('John;Doe;john@example.com;Active')"
72+
);
73+
});
74+
75+
test('European locale CSV (uses ; as separator)', () => {
76+
const data = 'Name;Price;Quantity\nCoffee;4,50;100\nBread;2,00;50';
77+
const sql = format(
78+
'INSERT INTO csv_imports (content, locale) VALUES (?, ?)',
79+
[data, 'de-DE']
80+
);
81+
assert.equal(
82+
sql,
83+
"INSERT INTO csv_imports (content, locale) VALUES ('Name;Price;Quantity\\nCoffee;4,50;100\\nBread;2,00;50', 'de-DE')"
84+
);
85+
});
86+
});
87+
88+
describe('Code snippets and scripts stored in database', () => {
89+
test('JavaScript code snippet', () => {
90+
const code = 'const x = 1; const y = 2; return x + y;';
91+
const sql = format('INSERT INTO snippets (language, code) VALUES (?, ?)', [
92+
'javascript',
93+
code,
94+
]);
95+
assert.equal(
96+
sql,
97+
"INSERT INTO snippets (language, code) VALUES ('javascript', 'const x = 1; const y = 2; return x + y;')"
98+
);
99+
});
100+
101+
test('CSS rules', () => {
102+
const css = 'body { margin: 0; padding: 0; } .btn { color: red; }';
103+
const sql = format('INSERT INTO themes (name, css) VALUES (?, ?)', [
104+
'dark',
105+
css,
106+
]);
107+
assert.equal(
108+
sql,
109+
"INSERT INTO themes (name, css) VALUES ('dark', 'body { margin: 0; padding: 0; } .btn { color: red; }')"
110+
);
111+
});
112+
113+
test('shell command sequence', () => {
114+
const script = 'cd /app; npm install; npm run build';
115+
const sql = format('INSERT INTO deploy_scripts (commands) VALUES (?)', [
116+
script,
117+
]);
118+
assert.equal(
119+
sql,
120+
"INSERT INTO deploy_scripts (commands) VALUES ('cd /app; npm install; npm run build')"
121+
);
122+
});
123+
});
124+
125+
describe('Configuration strings', () => {
126+
test('INI-style config', () => {
127+
const config = 'debug=true;log_level=info;max_connections=100';
128+
const sql = format('UPDATE apps SET config = ? WHERE id = ?', [config, 1]);
129+
assert.equal(
130+
sql,
131+
"UPDATE apps SET config = 'debug=true;log_level=info;max_connections=100' WHERE id = 1"
132+
);
133+
});
134+
135+
test('feature flags string', () => {
136+
const flags =
137+
'dark_mode:enabled;new_checkout:disabled;beta_features:enabled';
138+
const sql = format(
139+
'INSERT INTO user_settings (user_id, flags) VALUES (?, ?)',
140+
[42, flags]
141+
);
142+
assert.equal(
143+
sql,
144+
"INSERT INTO user_settings (user_id, flags) VALUES (42, 'dark_mode:enabled;new_checkout:disabled;beta_features:enabled')"
145+
);
146+
});
147+
});
148+
149+
describe('SQL injection with semicolons', () => {
150+
test('classic SQL injection attempt', () => {
151+
const malicious = "'; DROP TABLE users; --";
152+
const sql = format('SELECT * FROM users WHERE username = ?', [malicious]);
153+
assert.equal(
154+
sql,
155+
"SELECT * FROM users WHERE username = '\\'; DROP TABLE users; --'"
156+
);
157+
});
158+
159+
test('injection in login form', () => {
160+
const username = "admin'; DELETE FROM users WHERE '1'='1";
161+
const sql = format(
162+
'SELECT * FROM users WHERE username = ? AND password = ?',
163+
[username, 'password123']
164+
);
165+
assert.equal(
166+
sql,
167+
"SELECT * FROM users WHERE username = 'admin\\'; DELETE FROM users WHERE \\'1\\'=\\'1' AND password = 'password123'"
168+
);
169+
});
170+
171+
test('stacked queries attempt', () => {
172+
const payload = "1; INSERT INTO admins VALUES ('hacker', 'pass'); --";
173+
const sql = format('SELECT * FROM products WHERE id = ?', [payload]);
174+
assert.equal(
175+
sql,
176+
"SELECT * FROM products WHERE id = '1; INSERT INTO admins VALUES (\\'hacker\\', \\'pass\\'); --'"
177+
);
178+
});
179+
});
180+
181+
describe('CASE/WHEN for status mapping', () => {
182+
test('order status labels', () => {
183+
const sql = format(
184+
'SELECT id, CASE status WHEN ? THEN ? WHEN ? THEN ? WHEN ? THEN ? ELSE ? END AS status_label FROM orders',
185+
[
186+
'pending',
187+
'Pending',
188+
'shipped',
189+
'Shipped',
190+
'delivered',
191+
'Delivered',
192+
'Unknown',
193+
]
194+
);
195+
assert.equal(
196+
sql,
197+
"SELECT id, CASE status WHEN 'pending' THEN 'Pending' WHEN 'shipped' THEN 'Shipped' WHEN 'delivered' THEN 'Delivered' ELSE 'Unknown' END AS status_label FROM orders"
198+
);
199+
});
200+
201+
test('pricing tiers', () => {
202+
const sql = format(
203+
'SELECT product_id, CASE WHEN quantity >= ? THEN price * ? WHEN quantity >= ? THEN price * ? ELSE price END AS final_price FROM cart',
204+
[100, 0.8, 50, 0.9]
205+
);
206+
assert.equal(
207+
sql,
208+
'SELECT product_id, CASE WHEN quantity >= 100 THEN price * 0.8 WHEN quantity >= 50 THEN price * 0.9 ELSE price END AS final_price FROM cart'
209+
);
210+
});
211+
212+
test('user role permissions', () => {
213+
const sql = format(
214+
'SELECT user_id, CASE role WHEN ? THEN ? WHEN ? THEN ? ELSE ? END AS can_delete FROM users WHERE id = ?',
215+
['admin', true, 'moderator', true, false, 42]
216+
);
217+
assert.equal(
218+
sql,
219+
"SELECT user_id, CASE role WHEN 'admin' THEN true WHEN 'moderator' THEN true ELSE false END AS can_delete FROM users WHERE id = 42"
220+
);
221+
});
222+
});
223+
224+
describe('IFNULL and COALESCE for fallbacks', () => {
225+
test('display name fallback chain', () => {
226+
const sql = format(
227+
'SELECT COALESCE(display_name, username, ?) AS name FROM users WHERE id = ?',
228+
['Anonymous User', 123]
229+
);
230+
assert.equal(
231+
sql,
232+
"SELECT COALESCE(display_name, username, 'Anonymous User') AS name FROM users WHERE id = 123"
233+
);
234+
});
235+
236+
test('price with promotional fallback', () => {
237+
const sql = format(
238+
'SELECT id, name, IFNULL(promo_price, regular_price) AS price FROM products WHERE category = ?',
239+
['electronics']
240+
);
241+
assert.equal(
242+
sql,
243+
"SELECT id, name, IFNULL(promo_price, regular_price) AS price FROM products WHERE category = 'electronics'"
244+
);
245+
});
246+
247+
test('shipping address fallback to billing', () => {
248+
const sql = format(
249+
'SELECT COALESCE(shipping_address, billing_address, ?) AS address FROM orders WHERE id = ?',
250+
['No address provided', 456]
251+
);
252+
assert.equal(
253+
sql,
254+
"SELECT COALESCE(shipping_address, billing_address, 'No address provided') AS address FROM orders WHERE id = 456"
255+
);
256+
});
257+
});
258+
259+
describe('IF() function for conditional values', () => {
260+
test('stock availability label', () => {
261+
const sql = format(
262+
'SELECT name, IF(stock > ?, ?, ?) AS availability FROM products',
263+
[0, 'In Stock', 'Out of Stock']
264+
);
265+
assert.equal(
266+
sql,
267+
"SELECT name, IF(stock > 0, 'In Stock', 'Out of Stock') AS availability FROM products"
268+
);
269+
});
270+
271+
test('subscription status', () => {
272+
const sql = format(
273+
'SELECT email, IF(expires_at > ?, ?, ?) AS status FROM subscriptions',
274+
[raw('NOW()'), 'active', 'expired']
275+
);
276+
assert.equal(
277+
sql,
278+
"SELECT email, IF(expires_at > NOW(), 'active', 'expired') AS status FROM subscriptions"
279+
);
280+
});
281+
});
282+
283+
describe('Session variables for auditing', () => {
284+
test('set user context for audit triggers', () => {
285+
const sql = format('SET @current_user_id = ?, @current_user_ip = ?', [
286+
42,
287+
'192.168.1.100',
288+
]);
289+
assert.equal(
290+
sql,
291+
"SET @current_user_id = 42, @current_user_ip = '192.168.1.100'"
292+
);
293+
});
294+
295+
test('set timezone for session', () => {
296+
const sql = format('SET time_zone = ?', ['-03:00']);
297+
assert.equal(sql, "SET time_zone = '-03:00'");
298+
});
299+
300+
test('capture last insert id pattern', () => {
301+
const sql = format(
302+
'INSERT INTO orders (customer_id, total) VALUES (?, ?)',
303+
[100, 250.5]
304+
);
305+
assert.equal(
306+
sql,
307+
'INSERT INTO orders (customer_id, total) VALUES (100, 250.5)'
308+
);
309+
});
310+
});
311+
312+
describe('Query comments for debugging', () => {
313+
test('placeholder inside block comment is NOT replaced (correct behavior)', () => {
314+
const sql = format(
315+
'/* request_id: ? */ SELECT * FROM users WHERE id = ?',
316+
[42]
317+
);
318+
assert.equal(sql, '/* request_id: ? */ SELECT * FROM users WHERE id = 42');
319+
});
320+
321+
test('comment before placeholder does not affect substitution', () => {
322+
const sql = format('SELECT * FROM products /* filter */ WHERE active = ?', [
323+
true,
324+
]);
325+
assert.equal(
326+
sql,
327+
'SELECT * FROM products /* filter */ WHERE active = true'
328+
);
329+
});
330+
331+
test('single-line comment does not consume placeholders', () => {
332+
const sql = format(
333+
'SELECT * FROM users -- get all users\nWHERE status = ?',
334+
['active']
335+
);
336+
assert.equal(
337+
sql,
338+
"SELECT * FROM users -- get all users\nWHERE status = 'active'"
339+
);
340+
});
341+
});
342+
343+
describe('Real edge cases', () => {
344+
test('email with plus addressing contains no semicolons but tests escaping', () => {
345+
const sql = format('SELECT * FROM users WHERE email = ?', [
346+
'user+tag@example.com',
347+
]);
348+
assert.equal(
349+
sql,
350+
"SELECT * FROM users WHERE email = 'user+tag@example.com'"
351+
);
352+
});
353+
354+
test('JSON string stored in column', () => {
355+
const json = '{"items":[{"id":1},{"id":2}],"meta":{"page":1}}';
356+
const sql = format('UPDATE carts SET data = ? WHERE id = ?', [json, 1]);
357+
assert.equal(
358+
sql,
359+
'UPDATE carts SET data = \'{\\"items\\":[{\\"id\\":1},{\\"id\\":2}],\\"meta\\":{\\"page\\":1}}\' WHERE id = 1'
360+
);
361+
});
362+
363+
test('HTML content with inline styles', () => {
364+
const html = '<div style="color: red; font-size: 14px;">Hello</div>';
365+
const sql = format('INSERT INTO templates (content) VALUES (?)', [html]);
366+
assert.equal(
367+
sql,
368+
'INSERT INTO templates (content) VALUES (\'<div style=\\"color: red; font-size: 14px;\\">Hello</div>\')'
369+
);
370+
});
371+
372+
test('regex pattern stored in database', () => {
373+
const regex = '^[a-z]+;[0-9]+$';
374+
const sql = format('INSERT INTO validation_rules (pattern) VALUES (?)', [
375+
regex,
376+
]);
377+
assert.equal(
378+
sql,
379+
"INSERT INTO validation_rules (pattern) VALUES ('^[a-z]+;[0-9]+$')"
380+
);
381+
});
382+
383+
test('mathematical expression', () => {
384+
const expr = 'x = y + 1; y = z * 2; return x + y;';
385+
const sql = format('INSERT INTO formulas (expression) VALUES (?)', [expr]);
386+
assert.equal(
387+
sql,
388+
"INSERT INTO formulas (expression) VALUES ('x = y + 1; y = z * 2; return x + y;')"
389+
);
390+
});
391+
});

0 commit comments

Comments
 (0)