@gblikas/querykit 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/.cursor/BUGBOT.md +21 -0
  2. package/.cursor/rules/01-project-structure.mdc +77 -0
  3. package/.cursor/rules/02-typescript-standards.mdc +105 -0
  4. package/.cursor/rules/03-testing-standards.mdc +78 -0
  5. package/.cursor/rules/04-query-language.mdc +79 -0
  6. package/.cursor/rules/05-solid-principles.mdc +118 -0
  7. package/.cursor/rules/liqe-readme-docs.mdc +438 -0
  8. package/.devcontainer/devcontainer.json +25 -0
  9. package/.eslintignore +1 -0
  10. package/.eslintrc.js +39 -0
  11. package/.github/dependabot.yml +12 -0
  12. package/.github/workflows/ci.yml +114 -0
  13. package/.github/workflows/publish.yml +61 -0
  14. package/.husky/pre-commit +30 -0
  15. package/.prettierrc +10 -0
  16. package/CONTRIBUTING.md +187 -0
  17. package/LICENSE +674 -0
  18. package/README.md +237 -0
  19. package/dist/adapters/drizzle/index.d.ts +122 -0
  20. package/dist/adapters/drizzle/index.js +166 -0
  21. package/dist/adapters/index.d.ts +7 -0
  22. package/dist/adapters/index.js +25 -0
  23. package/dist/adapters/types.d.ts +60 -0
  24. package/dist/adapters/types.js +8 -0
  25. package/dist/index.d.ts +75 -0
  26. package/dist/index.js +118 -0
  27. package/dist/parser/index.d.ts +2 -0
  28. package/dist/parser/index.js +18 -0
  29. package/dist/parser/parser.d.ts +51 -0
  30. package/dist/parser/parser.js +201 -0
  31. package/dist/parser/types.d.ts +68 -0
  32. package/dist/parser/types.js +5 -0
  33. package/dist/query/builder.d.ts +61 -0
  34. package/dist/query/builder.js +188 -0
  35. package/dist/query/index.d.ts +2 -0
  36. package/dist/query/index.js +18 -0
  37. package/dist/query/types.d.ts +79 -0
  38. package/dist/query/types.js +2 -0
  39. package/dist/security/index.d.ts +2 -0
  40. package/dist/security/index.js +18 -0
  41. package/dist/security/types.d.ts +181 -0
  42. package/dist/security/types.js +43 -0
  43. package/dist/security/validator.d.ts +191 -0
  44. package/dist/security/validator.js +344 -0
  45. package/dist/translators/drizzle/index.d.ts +73 -0
  46. package/dist/translators/drizzle/index.js +260 -0
  47. package/dist/translators/index.d.ts +8 -0
  48. package/dist/translators/index.js +27 -0
  49. package/dist/translators/sql/index.d.ts +108 -0
  50. package/dist/translators/sql/index.js +252 -0
  51. package/dist/translators/types.d.ts +39 -0
  52. package/dist/translators/types.js +8 -0
  53. package/examples/qk-next/README.md +35 -0
  54. package/examples/qk-next/app/favicon.ico +0 -0
  55. package/examples/qk-next/app/globals.css +122 -0
  56. package/examples/qk-next/app/layout.tsx +121 -0
  57. package/examples/qk-next/app/page.tsx +813 -0
  58. package/examples/qk-next/app/providers.tsx +80 -0
  59. package/examples/qk-next/components/aurora-background.tsx +12 -0
  60. package/examples/qk-next/components/github-stars.tsx +51 -0
  61. package/examples/qk-next/components/mode-toggle.tsx +27 -0
  62. package/examples/qk-next/components/reactbits/blocks/Backgrounds/Aurora/Aurora.tsx +217 -0
  63. package/examples/qk-next/components/reactbits/blocks/Backgrounds/LightRays/LightRays.tsx +474 -0
  64. package/examples/qk-next/components/theme-provider.tsx +11 -0
  65. package/examples/qk-next/components/ui/card.tsx +92 -0
  66. package/examples/qk-next/components/ui/command.tsx +184 -0
  67. package/examples/qk-next/components/ui/dialog.tsx +143 -0
  68. package/examples/qk-next/components/ui/drawer.tsx +135 -0
  69. package/examples/qk-next/components/ui/hover-card.tsx +44 -0
  70. package/examples/qk-next/components/ui/icons.tsx +148 -0
  71. package/examples/qk-next/components/ui/sonner.tsx +26 -0
  72. package/examples/qk-next/components/ui/table.tsx +117 -0
  73. package/examples/qk-next/components.json +21 -0
  74. package/examples/qk-next/eslint.config.mjs +21 -0
  75. package/examples/qk-next/jsrepo.json +13 -0
  76. package/examples/qk-next/lib/utils.ts +6 -0
  77. package/examples/qk-next/next.config.ts +8 -0
  78. package/examples/qk-next/package.json +48 -0
  79. package/examples/qk-next/pnpm-lock.yaml +5558 -0
  80. package/examples/qk-next/postcss.config.mjs +5 -0
  81. package/examples/qk-next/public/file.svg +1 -0
  82. package/examples/qk-next/public/globe.svg +1 -0
  83. package/examples/qk-next/public/next.svg +1 -0
  84. package/examples/qk-next/public/vercel.svg +1 -0
  85. package/examples/qk-next/public/window.svg +1 -0
  86. package/examples/qk-next/tsconfig.json +42 -0
  87. package/examples/qk-next/types/sonner.d.ts +3 -0
  88. package/jest.config.js +26 -0
  89. package/package.json +51 -0
  90. package/src/adapters/drizzle/drizzle-adapter.test.ts +115 -0
  91. package/src/adapters/drizzle/index.ts +299 -0
  92. package/src/adapters/index.ts +11 -0
  93. package/src/adapters/types.ts +72 -0
  94. package/src/index.ts +194 -0
  95. package/src/integration.test.ts +202 -0
  96. package/src/parser/index.ts +2 -0
  97. package/src/parser/parser.test.ts +1056 -0
  98. package/src/parser/parser.ts +268 -0
  99. package/src/parser/types.ts +97 -0
  100. package/src/query/builder.test.ts +272 -0
  101. package/src/query/builder.ts +274 -0
  102. package/src/query/index.ts +2 -0
  103. package/src/query/types.ts +107 -0
  104. package/src/security/index.ts +2 -0
  105. package/src/security/types.ts +210 -0
  106. package/src/security/validator.test.ts +459 -0
  107. package/src/security/validator.ts +395 -0
  108. package/src/security.test.ts +366 -0
  109. package/src/translators/drizzle/drizzle-translator.test.ts +128 -0
  110. package/src/translators/drizzle/index.test.ts +45 -0
  111. package/src/translators/drizzle/index.ts +346 -0
  112. package/src/translators/index.ts +14 -0
  113. package/src/translators/sql/index.test.ts +45 -0
  114. package/src/translators/sql/index.ts +331 -0
  115. package/src/translators/sql/sql-translator.test.ts +419 -0
  116. package/src/translators/types.ts +44 -0
  117. package/src/types/sonner.d.ts +3 -0
  118. package/tsconfig.json +34 -0
@@ -0,0 +1,366 @@
1
+ /**
2
+ * Security tests validating fixes for critical vulnerabilities:
3
+ * - SQL injection via field names
4
+ * - Field enumeration attacks
5
+ * - ReDoS via wildcard patterns
6
+ * - Type confusion bypasses
7
+ * - NoSQL injection via objects
8
+ */
9
+ import { QueryParser, QueryParseError } from './parser/parser';
10
+ import {
11
+ QuerySecurityValidator,
12
+ QuerySecurityError
13
+ } from './security/validator';
14
+ import {
15
+ DrizzleTranslator,
16
+ DrizzleTranslationError
17
+ } from './translators/drizzle';
18
+
19
+ describe('Security Audit Tests', () => {
20
+ let parser: QueryParser;
21
+ let validator: QuerySecurityValidator;
22
+ let translator: DrizzleTranslator;
23
+
24
+ beforeEach(() => {
25
+ parser = new QueryParser();
26
+ validator = new QuerySecurityValidator({
27
+ allowedFields: [
28
+ 'name',
29
+ 'email',
30
+ 'priority',
31
+ 'status',
32
+ 'field',
33
+ 'active',
34
+ 'count',
35
+ 'percentage',
36
+ 'a',
37
+ 'b',
38
+ 'c',
39
+ 'd',
40
+ 'e',
41
+ 'f',
42
+ 'g'
43
+ ],
44
+ denyFields: ['password', 'secret'],
45
+ maxValueLength: 50,
46
+ maxQueryDepth: 3,
47
+ maxClauseCount: 5
48
+ });
49
+ translator = new DrizzleTranslator();
50
+ });
51
+
52
+ describe('VULN-001: SQL Injection via Raw SQL Construction', () => {
53
+ it('should handle malicious field names safely', () => {
54
+ const maliciousQueries = [
55
+ 'user.name; DROP TABLE users; --:"test"',
56
+ 'id\'; DELETE FROM users; --:"1"',
57
+ 'name` OR 1=1; --:"admin"',
58
+ 'field); DROP TABLE users;--:"value"'
59
+ ];
60
+
61
+ maliciousQueries.forEach(query => {
62
+ try {
63
+ const parsed = parser.parse(query);
64
+ expect(() => validator.validate(parsed)).toThrow(QuerySecurityError);
65
+ } catch (error) {
66
+ // Parser should either reject or validator should catch
67
+ expect(error).toBeInstanceOf(QueryParseError);
68
+ }
69
+ });
70
+ });
71
+
72
+ it('should prevent SQL injection through field names in translator', () => {
73
+ // Create a malicious expression that bypasses parser validation
74
+ const maliciousExpression = {
75
+ type: 'comparison' as const,
76
+ field: 'user.id; DROP TABLE users; --',
77
+ operator: '==' as const,
78
+ value: 'test'
79
+ };
80
+
81
+ expect(() => translator.translate(maliciousExpression)).toThrow(
82
+ DrizzleTranslationError
83
+ );
84
+ expect(() => translator.translate(maliciousExpression)).toThrow(
85
+ 'Invalid field name'
86
+ );
87
+ });
88
+ });
89
+
90
+ describe('VULN-002: Field Enumeration via Error Messages', () => {
91
+ it('should not reveal field existence through error messages', () => {
92
+ const validatorStrict = new QuerySecurityValidator({
93
+ allowedFields: ['name', 'email'],
94
+ denyFields: ['password']
95
+ });
96
+
97
+ const unauthorizedQueries = [
98
+ 'password:"secret"',
99
+ 'nonexistent:"value"',
100
+ 'hidden_field:"data"'
101
+ ];
102
+
103
+ unauthorizedQueries.forEach(query => {
104
+ const parsed = parser.parse(query);
105
+ expect(() => validatorStrict.validate(parsed)).toThrow(
106
+ 'Invalid query parameters'
107
+ );
108
+ });
109
+ });
110
+
111
+ it('should use generic error messages for denied fields', () => {
112
+ const deniedQuery = parser.parse('password:"secret"');
113
+
114
+ expect(() => validator.validate(deniedQuery)).toThrow(QuerySecurityError);
115
+ expect(() => validator.validate(deniedQuery)).toThrow(
116
+ 'Invalid query parameters'
117
+ );
118
+ });
119
+ });
120
+
121
+ describe('VULN-003: ReDoS via Wildcard Patterns', () => {
122
+ it('should prevent catastrophic backtracking patterns', () => {
123
+ const redosPatterns = [
124
+ 'name:"*a*a*a*a*a*a*a*a*a*a*b"',
125
+ 'name:"?x?x?x?x?x?x?x?x?x?x?y"'
126
+ ];
127
+
128
+ redosPatterns.forEach(pattern => {
129
+ const parsed = parser.parse(pattern);
130
+ expect(() => validator.validate(parsed)).toThrow(QuerySecurityError);
131
+ });
132
+ });
133
+
134
+ it('should limit wildcard usage', () => {
135
+ const excessiveWildcards = 'name:"' + '*'.repeat(15) + '"';
136
+ const parsed = parser.parse(excessiveWildcards);
137
+
138
+ expect(() => validator.validate(parsed)).toThrow(
139
+ 'Excessive wildcard usage'
140
+ );
141
+ });
142
+
143
+ it('should sanitize consecutive wildcards', () => {
144
+ const multiWildcard = parser.parse('name:"***test***"');
145
+
146
+ // Should either sanitize or reject based on pattern complexity
147
+ try {
148
+ validator.validate(multiWildcard);
149
+ if (multiWildcard.type === 'comparison') {
150
+ expect(multiWildcard.value).toBe('*test*');
151
+ }
152
+ } catch (error) {
153
+ expect(error).toBeInstanceOf(QuerySecurityError);
154
+ }
155
+ });
156
+ });
157
+
158
+ describe('VULN-004: Logic Bypass via Type Confusion', () => {
159
+ it('should validate array value lengths', () => {
160
+ const longStringArray = {
161
+ type: 'comparison' as const,
162
+ field: 'status',
163
+ operator: 'IN' as const,
164
+ value: ['a'.repeat(100), 'b'.repeat(100)]
165
+ };
166
+
167
+ expect(() => validator.validate(longStringArray)).toThrow(
168
+ QuerySecurityError
169
+ );
170
+ });
171
+
172
+ it('should limit array sizes', () => {
173
+ const largeArray = {
174
+ type: 'comparison' as const,
175
+ field: 'status',
176
+ operator: 'IN' as const,
177
+ value: Array(150).fill('test')
178
+ };
179
+
180
+ expect(() => validator.validate(largeArray)).toThrow(
181
+ 'Array values cannot exceed 100 items'
182
+ );
183
+ });
184
+
185
+ it('should prevent object values in arrays', () => {
186
+ const objectInArray = {
187
+ type: 'comparison' as const,
188
+ field: 'status',
189
+ operator: 'IN' as const,
190
+ value: ['test', { malicious: 'object' }] as unknown as Array<
191
+ string | number | boolean | null
192
+ >
193
+ };
194
+
195
+ expect(() => validator.validate(objectInArray)).toThrow(
196
+ 'Object values are not allowed'
197
+ );
198
+ });
199
+ });
200
+
201
+ describe('VULN-005: NoSQL Injection via Object Values', () => {
202
+ it('should reject object values in parser', () => {
203
+ // Simulate object injection attempt
204
+ const objectValue = { $ne: null };
205
+
206
+ expect(() => parser['convertLiqeValue'](objectValue)).toThrow(
207
+ QueryParseError
208
+ );
209
+ });
210
+
211
+ it('should prevent object injection through complex values', () => {
212
+ const maliciousExpression = {
213
+ type: 'comparison' as const,
214
+ field: 'user',
215
+ operator: '==' as const,
216
+ value: { $where: 'this.password.length > 0' } as unknown as
217
+ | string
218
+ | number
219
+ | boolean
220
+ | null
221
+ };
222
+
223
+ expect(() => validator.validate(maliciousExpression)).toThrow(
224
+ QuerySecurityError
225
+ );
226
+ });
227
+ });
228
+
229
+ describe('Query Complexity Limits', () => {
230
+ it('should enforce maximum query depth', () => {
231
+ // Create a query with nested logical operations that exceeds depth limit of 3
232
+ // Each AND/OR/NOT adds to depth, not parentheses
233
+ const deepQuery = parser.parse(
234
+ 'a:1 AND (b:2 AND (c:3 AND (d:4 AND e:5)))'
235
+ );
236
+
237
+ expect(() => validator.validate(deepQuery)).toThrow(
238
+ 'Query exceeds maximum depth'
239
+ );
240
+ });
241
+
242
+ it('should enforce maximum clause count', () => {
243
+ // Create validator with lower clause count for this test
244
+ const strictValidator = new QuerySecurityValidator({
245
+ allowedFields: ['a', 'b', 'c', 'd', 'e', 'f', 'g'],
246
+ maxClauseCount: 3,
247
+ maxQueryDepth: 10 // Higher depth so clause count limit hits first
248
+ });
249
+
250
+ const complexQuery = parser.parse('a:1 AND b:2 AND c:3 AND d:4');
251
+
252
+ expect(() => strictValidator.validate(complexQuery)).toThrow(
253
+ 'Query exceeds maximum clause count'
254
+ );
255
+ });
256
+ });
257
+
258
+ describe('Input Sanitization', () => {
259
+ it('should handle Unicode and special characters safely', () => {
260
+ const unicodeQueries = [
261
+ 'name:"\\u0000"', // Null byte
262
+ 'name:"\\u001F"', // Control character
263
+ 'name:"𝐇𝐞𝐥𝐥𝐨"', // Unicode mathematical bold
264
+ 'name:"<script>alert(1)</script>"' // XSS attempt
265
+ ];
266
+
267
+ unicodeQueries.forEach(query => {
268
+ const parsed = parser.parse(query);
269
+ expect(() => validator.validate(parsed)).not.toThrow();
270
+
271
+ // Should be handled safely by translator
272
+ expect(() => translator.translate(parsed)).not.toThrow();
273
+ });
274
+ });
275
+
276
+ it('should handle extremely long values', () => {
277
+ const longValue = 'a'.repeat(2000);
278
+ const longQuery = parser.parse(`name:"${longValue}"`);
279
+
280
+ expect(() => validator.validate(longQuery)).toThrow(
281
+ 'exceeds maximum length'
282
+ );
283
+ });
284
+ });
285
+
286
+ describe('Edge Cases and Boundary Conditions', () => {
287
+ it('should handle empty queries safely', () => {
288
+ expect(() => parser.parse('')).toThrow(QueryParseError);
289
+ });
290
+
291
+ it('should handle null and undefined values', () => {
292
+ const nullQuery = parser.parse('field:null');
293
+ expect(() => validator.validate(nullQuery)).not.toThrow();
294
+ expect(() => translator.translate(nullQuery)).not.toThrow();
295
+ });
296
+
297
+ it('should handle boolean values correctly', () => {
298
+ const boolQuery = parser.parse('active:true');
299
+ expect(() => validator.validate(boolQuery)).not.toThrow();
300
+ expect(() => translator.translate(boolQuery)).not.toThrow();
301
+ });
302
+
303
+ it('should handle numeric edge cases', () => {
304
+ const numericQueries = [
305
+ 'count:0',
306
+ 'count:-1',
307
+ 'count:999999999999999',
308
+ 'percentage:0.0001'
309
+ ];
310
+
311
+ numericQueries.forEach(query => {
312
+ const parsed = parser.parse(query);
313
+ expect(() => validator.validate(parsed)).not.toThrow();
314
+ expect(() => translator.translate(parsed)).not.toThrow();
315
+ });
316
+ });
317
+ });
318
+
319
+ describe('Performance and DoS Protection', () => {
320
+ it('should handle deeply nested parentheses', () => {
321
+ const nested = '(' + 'name:"test"' + ')'.repeat(20);
322
+
323
+ expect(() => parser.parse(nested)).toThrow();
324
+ });
325
+
326
+ it('should prevent memory exhaustion via large arrays', () => {
327
+ const hugeArray = {
328
+ type: 'comparison' as const,
329
+ field: 'status',
330
+ operator: 'IN' as const,
331
+ value: Array(10000).fill('test')
332
+ };
333
+
334
+ expect(() => validator.validate(hugeArray)).toThrow();
335
+ });
336
+ });
337
+
338
+ describe('Integration Security Tests', () => {
339
+ it('should maintain security through full parsing pipeline', () => {
340
+ const suspiciousQueries = [
341
+ 'status:"active" OR 1=1',
342
+ 'name:"admin\'--"',
343
+ 'id:1 UNION SELECT password FROM users',
344
+ 'field:"value"; INSERT INTO logs VALUES("hacked")'
345
+ ];
346
+
347
+ suspiciousQueries.forEach(query => {
348
+ try {
349
+ const parsed = parser.parse(query);
350
+ validator.validate(parsed);
351
+ translator.translate(parsed);
352
+
353
+ // If no exception, ensure the translated query is safe
354
+ expect(true).toBe(true); // Placeholder for additional safety checks
355
+ } catch (error) {
356
+ // Should throw security or parse errors
357
+ expect(
358
+ error instanceof QueryParseError ||
359
+ error instanceof QuerySecurityError ||
360
+ error instanceof DrizzleTranslationError
361
+ ).toBe(true);
362
+ }
363
+ });
364
+ });
365
+ });
366
+ });
@@ -0,0 +1,128 @@
1
+ import { QueryParser } from '../../parser';
2
+ import { QueryExpression } from '../../parser/types';
3
+ import { DrizzleTranslator } from './';
4
+ import { SQL, sql } from 'drizzle-orm';
5
+
6
+ // Helper function to safely get SQL string value for testing
7
+ function getSqlString(sqlObj: SQL): string {
8
+ // For testing purposes only - extract a string representation
9
+ // of the SQL query that we can use in our assertions
10
+ try {
11
+ return JSON.stringify(sqlObj);
12
+ } catch (e) {
13
+ return String(sqlObj);
14
+ }
15
+ }
16
+
17
+ describe('DrizzleTranslator', () => {
18
+ const translator = new DrizzleTranslator();
19
+ const parser = new QueryParser();
20
+
21
+ describe('translate', () => {
22
+ it('should translate a simple comparison expression', () => {
23
+ const expression = parser.parse('priority:>2');
24
+ const result = translator.translate(expression);
25
+
26
+ const sqlString = getSqlString(result);
27
+ expect(sqlString).toContain('priority');
28
+ expect(sqlString).toContain('>');
29
+ expect(sqlString).toContain('2');
30
+ });
31
+
32
+ it('should translate a logical AND expression', () => {
33
+ const expression = parser.parse('priority:>2 AND status:"active"');
34
+ const result = translator.translate(expression);
35
+
36
+ const sqlString = getSqlString(result);
37
+ expect(sqlString).toContain('AND');
38
+ expect(sqlString).toContain('priority');
39
+ expect(sqlString).toContain('status');
40
+ });
41
+
42
+ it('should translate a logical OR expression', () => {
43
+ const expression = parser.parse('status:"active" OR status:"pending"');
44
+ const result = translator.translate(expression);
45
+
46
+ const sqlString = getSqlString(result);
47
+ expect(sqlString).toContain('OR');
48
+ });
49
+
50
+ it('should translate a NOT expression', () => {
51
+ const expression = parser.parse('NOT status:"inactive"');
52
+ const result = translator.translate(expression);
53
+
54
+ const sqlString = getSqlString(result);
55
+ expect(sqlString).toContain('NOT');
56
+ });
57
+
58
+ it('should translate multiple values as OR conditions', () => {
59
+ // Create a logical OR expression that checks for multiple values
60
+ const expression = parser.parse('status:"active" OR status:"pending"');
61
+ const result = translator.translate(expression);
62
+
63
+ const sqlString = getSqlString(result);
64
+ expect(sqlString).toContain('OR');
65
+ });
66
+
67
+ it('should translate a complex nested expression', () => {
68
+ const expression = parser.parse(
69
+ '(priority:>2 AND status:"active") OR (role:"admin")'
70
+ );
71
+ const result = translator.translate(expression);
72
+
73
+ const sqlString = getSqlString(result);
74
+ expect(sqlString).toContain('AND');
75
+ expect(sqlString).toContain('OR');
76
+ expect(sqlString).toContain('priority');
77
+ });
78
+ });
79
+
80
+ describe('with schema', () => {
81
+ // Mock schema with some table fields
82
+ const mockSchema = {
83
+ todos: {
84
+ id: sql.raw('todos.id') as unknown as SQL,
85
+ title: sql.raw('todos.title') as unknown as SQL,
86
+ priority: sql.raw('todos.priority') as unknown as SQL,
87
+ status: sql.raw('todos.status') as unknown as SQL
88
+ }
89
+ };
90
+
91
+ const schemaTranslator = new DrizzleTranslator({
92
+ schema: mockSchema
93
+ });
94
+
95
+ it('should use schema fields when available', () => {
96
+ const expression = parser.parse('todos.priority:>2');
97
+ const result = schemaTranslator.translate(expression);
98
+
99
+ const sqlString = getSqlString(result);
100
+ expect(sqlString).toContain('>');
101
+ });
102
+ });
103
+
104
+ describe('canTranslate', () => {
105
+ it('should return true for valid expressions', () => {
106
+ const expression = parser.parse('status:"active"');
107
+ expect(translator.canTranslate(expression)).toBe(true);
108
+ });
109
+
110
+ it('should handle unsupported expressions gracefully', () => {
111
+ // Create a malformed expression to test error handling
112
+ const invalidExpression = {
113
+ type: 'unsupported'
114
+ } as unknown as QueryExpression;
115
+ expect(translator.canTranslate(invalidExpression)).toBe(false);
116
+ });
117
+ });
118
+
119
+ describe('table qualification', () => {
120
+ it('should preserve table qualifiers in fields', () => {
121
+ const expression = parser.parse('todos.priority:>2');
122
+ const result = translator.translate(expression);
123
+ const sqlString = getSqlString(result);
124
+
125
+ expect(sqlString).toContain('todos.priority');
126
+ });
127
+ });
128
+ });
@@ -0,0 +1,45 @@
1
+ import { DrizzleTranslator } from './index';
2
+
3
+ describe('DrizzleTranslator', () => {
4
+ let translator: DrizzleTranslator;
5
+
6
+ beforeEach(() => {
7
+ translator = new DrizzleTranslator();
8
+ });
9
+
10
+ // Helper function to access private wildcardToSqlPattern method
11
+ function testWildcardPattern(pattern: string): string {
12
+ // We need to access a private method for testing - using type assertion
13
+ return (translator as unknown as {
14
+ wildcardToSqlPattern: (p: string) => string
15
+ }).wildcardToSqlPattern(pattern);
16
+ }
17
+
18
+ // Other tests...
19
+
20
+ describe('wildcardToSqlPattern', () => {
21
+ it('should convert * wildcard to % SQL pattern', () => {
22
+ expect(testWildcardPattern('foo*')).toBe('foo%');
23
+ expect(testWildcardPattern('*bar')).toBe('%bar');
24
+ expect(testWildcardPattern('foo*bar')).toBe('foo%bar');
25
+ expect(testWildcardPattern('*foo*')).toBe('%foo%');
26
+ });
27
+
28
+ it('should convert ? wildcard to _ SQL pattern', () => {
29
+ expect(testWildcardPattern('foo?')).toBe('foo_');
30
+ expect(testWildcardPattern('?bar')).toBe('_bar');
31
+ expect(testWildcardPattern('foo?bar')).toBe('foo_bar');
32
+ });
33
+
34
+ it('should handle mixed wildcards', () => {
35
+ expect(testWildcardPattern('f*o?bar*')).toBe('f%o_bar%');
36
+ expect(testWildcardPattern('*test?')).toBe('%test_');
37
+ });
38
+
39
+ it('should escape existing SQL special characters', () => {
40
+ expect(testWildcardPattern('foo%bar')).toBe('foo\\%bar');
41
+ expect(testWildcardPattern('foo_bar')).toBe('foo\\_bar');
42
+ expect(testWildcardPattern('foo_%bar*')).toBe('foo\\_\\%bar%');
43
+ });
44
+ });
45
+ });