@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.
- package/.cursor/BUGBOT.md +21 -0
- package/.cursor/rules/01-project-structure.mdc +77 -0
- package/.cursor/rules/02-typescript-standards.mdc +105 -0
- package/.cursor/rules/03-testing-standards.mdc +78 -0
- package/.cursor/rules/04-query-language.mdc +79 -0
- package/.cursor/rules/05-solid-principles.mdc +118 -0
- package/.cursor/rules/liqe-readme-docs.mdc +438 -0
- package/.devcontainer/devcontainer.json +25 -0
- package/.eslintignore +1 -0
- package/.eslintrc.js +39 -0
- package/.github/dependabot.yml +12 -0
- package/.github/workflows/ci.yml +114 -0
- package/.github/workflows/publish.yml +61 -0
- package/.husky/pre-commit +30 -0
- package/.prettierrc +10 -0
- package/CONTRIBUTING.md +187 -0
- package/LICENSE +674 -0
- package/README.md +237 -0
- package/dist/adapters/drizzle/index.d.ts +122 -0
- package/dist/adapters/drizzle/index.js +166 -0
- package/dist/adapters/index.d.ts +7 -0
- package/dist/adapters/index.js +25 -0
- package/dist/adapters/types.d.ts +60 -0
- package/dist/adapters/types.js +8 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +118 -0
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.js +18 -0
- package/dist/parser/parser.d.ts +51 -0
- package/dist/parser/parser.js +201 -0
- package/dist/parser/types.d.ts +68 -0
- package/dist/parser/types.js +5 -0
- package/dist/query/builder.d.ts +61 -0
- package/dist/query/builder.js +188 -0
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +18 -0
- package/dist/query/types.d.ts +79 -0
- package/dist/query/types.js +2 -0
- package/dist/security/index.d.ts +2 -0
- package/dist/security/index.js +18 -0
- package/dist/security/types.d.ts +181 -0
- package/dist/security/types.js +43 -0
- package/dist/security/validator.d.ts +191 -0
- package/dist/security/validator.js +344 -0
- package/dist/translators/drizzle/index.d.ts +73 -0
- package/dist/translators/drizzle/index.js +260 -0
- package/dist/translators/index.d.ts +8 -0
- package/dist/translators/index.js +27 -0
- package/dist/translators/sql/index.d.ts +108 -0
- package/dist/translators/sql/index.js +252 -0
- package/dist/translators/types.d.ts +39 -0
- package/dist/translators/types.js +8 -0
- package/examples/qk-next/README.md +35 -0
- package/examples/qk-next/app/favicon.ico +0 -0
- package/examples/qk-next/app/globals.css +122 -0
- package/examples/qk-next/app/layout.tsx +121 -0
- package/examples/qk-next/app/page.tsx +813 -0
- package/examples/qk-next/app/providers.tsx +80 -0
- package/examples/qk-next/components/aurora-background.tsx +12 -0
- package/examples/qk-next/components/github-stars.tsx +51 -0
- package/examples/qk-next/components/mode-toggle.tsx +27 -0
- package/examples/qk-next/components/reactbits/blocks/Backgrounds/Aurora/Aurora.tsx +217 -0
- package/examples/qk-next/components/reactbits/blocks/Backgrounds/LightRays/LightRays.tsx +474 -0
- package/examples/qk-next/components/theme-provider.tsx +11 -0
- package/examples/qk-next/components/ui/card.tsx +92 -0
- package/examples/qk-next/components/ui/command.tsx +184 -0
- package/examples/qk-next/components/ui/dialog.tsx +143 -0
- package/examples/qk-next/components/ui/drawer.tsx +135 -0
- package/examples/qk-next/components/ui/hover-card.tsx +44 -0
- package/examples/qk-next/components/ui/icons.tsx +148 -0
- package/examples/qk-next/components/ui/sonner.tsx +26 -0
- package/examples/qk-next/components/ui/table.tsx +117 -0
- package/examples/qk-next/components.json +21 -0
- package/examples/qk-next/eslint.config.mjs +21 -0
- package/examples/qk-next/jsrepo.json +13 -0
- package/examples/qk-next/lib/utils.ts +6 -0
- package/examples/qk-next/next.config.ts +8 -0
- package/examples/qk-next/package.json +48 -0
- package/examples/qk-next/pnpm-lock.yaml +5558 -0
- package/examples/qk-next/postcss.config.mjs +5 -0
- package/examples/qk-next/public/file.svg +1 -0
- package/examples/qk-next/public/globe.svg +1 -0
- package/examples/qk-next/public/next.svg +1 -0
- package/examples/qk-next/public/vercel.svg +1 -0
- package/examples/qk-next/public/window.svg +1 -0
- package/examples/qk-next/tsconfig.json +42 -0
- package/examples/qk-next/types/sonner.d.ts +3 -0
- package/jest.config.js +26 -0
- package/package.json +51 -0
- package/src/adapters/drizzle/drizzle-adapter.test.ts +115 -0
- package/src/adapters/drizzle/index.ts +299 -0
- package/src/adapters/index.ts +11 -0
- package/src/adapters/types.ts +72 -0
- package/src/index.ts +194 -0
- package/src/integration.test.ts +202 -0
- package/src/parser/index.ts +2 -0
- package/src/parser/parser.test.ts +1056 -0
- package/src/parser/parser.ts +268 -0
- package/src/parser/types.ts +97 -0
- package/src/query/builder.test.ts +272 -0
- package/src/query/builder.ts +274 -0
- package/src/query/index.ts +2 -0
- package/src/query/types.ts +107 -0
- package/src/security/index.ts +2 -0
- package/src/security/types.ts +210 -0
- package/src/security/validator.test.ts +459 -0
- package/src/security/validator.ts +395 -0
- package/src/security.test.ts +366 -0
- package/src/translators/drizzle/drizzle-translator.test.ts +128 -0
- package/src/translators/drizzle/index.test.ts +45 -0
- package/src/translators/drizzle/index.ts +346 -0
- package/src/translators/index.ts +14 -0
- package/src/translators/sql/index.test.ts +45 -0
- package/src/translators/sql/index.ts +331 -0
- package/src/translators/sql/sql-translator.test.ts +419 -0
- package/src/translators/types.ts +44 -0
- package/src/types/sonner.d.ts +3 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
import { QueryParser, QueryParseError } from './parser';
|
|
2
|
+
import { QueryExpression } from './types';
|
|
3
|
+
|
|
4
|
+
// Replace the type definition with this approach
|
|
5
|
+
type QueryParserPrivate = {
|
|
6
|
+
convertLiqeAst(node: unknown): QueryExpression;
|
|
7
|
+
convertLiqeOperator(operator: string): string;
|
|
8
|
+
convertLiqeValue(
|
|
9
|
+
value: unknown
|
|
10
|
+
): string | number | boolean | null | unknown[];
|
|
11
|
+
normalizeFieldName(field: string): string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe('QueryParser', () => {
|
|
15
|
+
let parser: QueryParser;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
parser = new QueryParser();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('parse', () => {
|
|
22
|
+
it('should parse simple comparison expressions', () => {
|
|
23
|
+
const query = 'priority:>2';
|
|
24
|
+
const expected: QueryExpression = {
|
|
25
|
+
type: 'comparison',
|
|
26
|
+
field: 'priority',
|
|
27
|
+
operator: '>',
|
|
28
|
+
value: 2
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should parse equality comparison expressions', () => {
|
|
35
|
+
const query = 'priority:2';
|
|
36
|
+
const expected: QueryExpression = {
|
|
37
|
+
type: 'comparison',
|
|
38
|
+
field: 'priority',
|
|
39
|
+
operator: '==',
|
|
40
|
+
value: 2
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should parse less than comparison expressions', () => {
|
|
47
|
+
const query = 'priority:<2';
|
|
48
|
+
const expected: QueryExpression = {
|
|
49
|
+
type: 'comparison',
|
|
50
|
+
field: 'priority',
|
|
51
|
+
operator: '<',
|
|
52
|
+
value: 2
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should parse less than or equal comparison expressions', () => {
|
|
59
|
+
const query = 'priority:<=2';
|
|
60
|
+
const expected: QueryExpression = {
|
|
61
|
+
type: 'comparison',
|
|
62
|
+
field: 'priority',
|
|
63
|
+
operator: '<=',
|
|
64
|
+
value: 2
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should parse greater than or equal comparison expressions', () => {
|
|
71
|
+
const query = 'priority:>=2';
|
|
72
|
+
const expected: QueryExpression = {
|
|
73
|
+
type: 'comparison',
|
|
74
|
+
field: 'priority',
|
|
75
|
+
operator: '>=',
|
|
76
|
+
value: 2
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should parse IN operator expressions', () => {
|
|
83
|
+
const query = 'status:"active" OR status:"pending"';
|
|
84
|
+
const expected: QueryExpression = {
|
|
85
|
+
type: 'logical',
|
|
86
|
+
operator: 'OR',
|
|
87
|
+
left: {
|
|
88
|
+
type: 'comparison',
|
|
89
|
+
field: 'status',
|
|
90
|
+
operator: '==',
|
|
91
|
+
value: 'active'
|
|
92
|
+
},
|
|
93
|
+
right: {
|
|
94
|
+
type: 'comparison',
|
|
95
|
+
field: 'status',
|
|
96
|
+
operator: '==',
|
|
97
|
+
value: 'pending'
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should parse NOT IN operator expressions', () => {
|
|
105
|
+
const query = 'NOT (status:"inactive" OR status:"closed")';
|
|
106
|
+
const expected: QueryExpression = {
|
|
107
|
+
type: 'logical',
|
|
108
|
+
operator: 'NOT',
|
|
109
|
+
left: {
|
|
110
|
+
type: 'logical',
|
|
111
|
+
operator: 'OR',
|
|
112
|
+
left: {
|
|
113
|
+
type: 'comparison',
|
|
114
|
+
field: 'status',
|
|
115
|
+
operator: '==',
|
|
116
|
+
value: 'inactive'
|
|
117
|
+
},
|
|
118
|
+
right: {
|
|
119
|
+
type: 'comparison',
|
|
120
|
+
field: 'status',
|
|
121
|
+
operator: '==',
|
|
122
|
+
value: 'closed'
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should parse wildcard expressions', () => {
|
|
131
|
+
const query = 'name:"John*"';
|
|
132
|
+
const expected: QueryExpression = {
|
|
133
|
+
type: 'comparison',
|
|
134
|
+
field: 'name',
|
|
135
|
+
operator: 'LIKE',
|
|
136
|
+
value: 'John*'
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should parse logical AND expressions', () => {
|
|
143
|
+
const query = 'priority:>2 AND status:active';
|
|
144
|
+
const expected: QueryExpression = {
|
|
145
|
+
type: 'logical',
|
|
146
|
+
operator: 'AND',
|
|
147
|
+
left: {
|
|
148
|
+
type: 'comparison',
|
|
149
|
+
field: 'priority',
|
|
150
|
+
operator: '>',
|
|
151
|
+
value: 2
|
|
152
|
+
},
|
|
153
|
+
right: {
|
|
154
|
+
type: 'comparison',
|
|
155
|
+
field: 'status',
|
|
156
|
+
operator: '==',
|
|
157
|
+
value: 'active'
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should parse logical OR expressions', () => {
|
|
165
|
+
const query = 'status:active OR status:pending';
|
|
166
|
+
const expected: QueryExpression = {
|
|
167
|
+
type: 'logical',
|
|
168
|
+
operator: 'OR',
|
|
169
|
+
left: {
|
|
170
|
+
type: 'comparison',
|
|
171
|
+
field: 'status',
|
|
172
|
+
operator: '==',
|
|
173
|
+
value: 'active'
|
|
174
|
+
},
|
|
175
|
+
right: {
|
|
176
|
+
type: 'comparison',
|
|
177
|
+
field: 'status',
|
|
178
|
+
operator: '==',
|
|
179
|
+
value: 'pending'
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should parse NOT expressions', () => {
|
|
187
|
+
const query = 'NOT status:inactive';
|
|
188
|
+
const expected: QueryExpression = {
|
|
189
|
+
type: 'logical',
|
|
190
|
+
operator: 'NOT',
|
|
191
|
+
left: {
|
|
192
|
+
type: 'comparison',
|
|
193
|
+
field: 'status',
|
|
194
|
+
operator: '==',
|
|
195
|
+
value: 'inactive'
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should parse parenthesized expressions', () => {
|
|
203
|
+
const query = '(status:active OR status:pending) AND priority:>2';
|
|
204
|
+
const expected: QueryExpression = {
|
|
205
|
+
type: 'logical',
|
|
206
|
+
operator: 'AND',
|
|
207
|
+
left: {
|
|
208
|
+
type: 'logical',
|
|
209
|
+
operator: 'OR',
|
|
210
|
+
left: {
|
|
211
|
+
type: 'comparison',
|
|
212
|
+
field: 'status',
|
|
213
|
+
operator: '==',
|
|
214
|
+
value: 'active'
|
|
215
|
+
},
|
|
216
|
+
right: {
|
|
217
|
+
type: 'comparison',
|
|
218
|
+
field: 'status',
|
|
219
|
+
operator: '==',
|
|
220
|
+
value: 'pending'
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
right: {
|
|
224
|
+
type: 'comparison',
|
|
225
|
+
field: 'priority',
|
|
226
|
+
operator: '>',
|
|
227
|
+
value: 2
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should handle case-insensitive fields when enabled', () => {
|
|
235
|
+
const parser = new QueryParser({ caseInsensitiveFields: true });
|
|
236
|
+
const query = 'STATUS:active';
|
|
237
|
+
const expected: QueryExpression = {
|
|
238
|
+
type: 'comparison',
|
|
239
|
+
field: 'status',
|
|
240
|
+
operator: '==',
|
|
241
|
+
value: 'active'
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should apply field mappings when provided', () => {
|
|
248
|
+
const parser = new QueryParser({
|
|
249
|
+
fieldMappings: { user_name: 'username' }
|
|
250
|
+
});
|
|
251
|
+
const query = 'user_name:john';
|
|
252
|
+
const expected: QueryExpression = {
|
|
253
|
+
type: 'comparison',
|
|
254
|
+
field: 'username',
|
|
255
|
+
operator: '==',
|
|
256
|
+
value: 'john'
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should throw QueryParseError for invalid queries', () => {
|
|
263
|
+
const invalidQueries = ['', 'invalid:query:format', 'field:', ':value'];
|
|
264
|
+
|
|
265
|
+
invalidQueries.forEach(query => {
|
|
266
|
+
expect(() => parser.parse(query)).toThrow(QueryParseError);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should throw QueryParseError for unsupported operators', () => {
|
|
271
|
+
const query = 'priority:^2';
|
|
272
|
+
expect(() => parser.parse(query)).toThrow(QueryParseError);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should throw QueryParseError for invalid AST nodes', () => {
|
|
276
|
+
// This test simulates an internal error in the parser
|
|
277
|
+
const mockLiqeAst = {} as unknown;
|
|
278
|
+
expect(() =>
|
|
279
|
+
parser['convertLiqeAst'](mockLiqeAst as import('liqe').LiqeQuery)
|
|
280
|
+
).toThrow(QueryParseError);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// New test cases start here
|
|
284
|
+
it('should parse quoted string values', () => {
|
|
285
|
+
const query = 'name:"John Doe"';
|
|
286
|
+
const expected: QueryExpression = {
|
|
287
|
+
type: 'comparison',
|
|
288
|
+
field: 'name',
|
|
289
|
+
operator: '==',
|
|
290
|
+
value: 'John Doe'
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should parse boolean values', () => {
|
|
297
|
+
const query = 'isActive:true';
|
|
298
|
+
const expected: QueryExpression = {
|
|
299
|
+
type: 'comparison',
|
|
300
|
+
field: 'isActive',
|
|
301
|
+
operator: '==',
|
|
302
|
+
value: true
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should parse null values', () => {
|
|
309
|
+
const query = 'lastName:null';
|
|
310
|
+
const expected: QueryExpression = {
|
|
311
|
+
type: 'comparison',
|
|
312
|
+
field: 'lastName',
|
|
313
|
+
operator: '==',
|
|
314
|
+
value: null
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should parse complex nested expressions with multiple operators', () => {
|
|
321
|
+
const query =
|
|
322
|
+
'(name:"John" OR name:"Jane") AND (priority:>2 AND (isActive:true OR role:"admin"))';
|
|
323
|
+
const expected: QueryExpression = {
|
|
324
|
+
type: 'logical',
|
|
325
|
+
operator: 'AND',
|
|
326
|
+
left: {
|
|
327
|
+
type: 'logical',
|
|
328
|
+
operator: 'OR',
|
|
329
|
+
left: {
|
|
330
|
+
type: 'comparison',
|
|
331
|
+
field: 'name',
|
|
332
|
+
operator: '==',
|
|
333
|
+
value: 'John'
|
|
334
|
+
},
|
|
335
|
+
right: {
|
|
336
|
+
type: 'comparison',
|
|
337
|
+
field: 'name',
|
|
338
|
+
operator: '==',
|
|
339
|
+
value: 'Jane'
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
right: {
|
|
343
|
+
type: 'logical',
|
|
344
|
+
operator: 'AND',
|
|
345
|
+
left: {
|
|
346
|
+
type: 'comparison',
|
|
347
|
+
field: 'priority',
|
|
348
|
+
operator: '>',
|
|
349
|
+
value: 2
|
|
350
|
+
},
|
|
351
|
+
right: {
|
|
352
|
+
type: 'logical',
|
|
353
|
+
operator: 'OR',
|
|
354
|
+
left: {
|
|
355
|
+
type: 'comparison',
|
|
356
|
+
field: 'isActive',
|
|
357
|
+
operator: '==',
|
|
358
|
+
value: true
|
|
359
|
+
},
|
|
360
|
+
right: {
|
|
361
|
+
type: 'comparison',
|
|
362
|
+
field: 'role',
|
|
363
|
+
operator: '==',
|
|
364
|
+
value: 'admin'
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('should apply complex field mappings', () => {
|
|
374
|
+
const parser = new QueryParser({
|
|
375
|
+
fieldMappings: {
|
|
376
|
+
user_name: 'username',
|
|
377
|
+
user_priority: 'priority',
|
|
378
|
+
user_active: 'isActive'
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const query = 'user_name:john AND user_priority:>2 AND user_active:true';
|
|
383
|
+
const expected: QueryExpression = {
|
|
384
|
+
type: 'logical',
|
|
385
|
+
operator: 'AND',
|
|
386
|
+
left: {
|
|
387
|
+
type: 'logical',
|
|
388
|
+
operator: 'AND',
|
|
389
|
+
left: {
|
|
390
|
+
type: 'comparison',
|
|
391
|
+
field: 'username',
|
|
392
|
+
operator: '==',
|
|
393
|
+
value: 'john'
|
|
394
|
+
},
|
|
395
|
+
right: {
|
|
396
|
+
type: 'comparison',
|
|
397
|
+
field: 'priority',
|
|
398
|
+
operator: '>',
|
|
399
|
+
value: 2
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
right: {
|
|
403
|
+
type: 'comparison',
|
|
404
|
+
field: 'isActive',
|
|
405
|
+
operator: '==',
|
|
406
|
+
value: true
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should throw QueryParseError for invalid logical operator', () => {
|
|
414
|
+
// Simulating an internal error when converting logical operators
|
|
415
|
+
const mockLiqeAst = {
|
|
416
|
+
type: 'LogicalExpression',
|
|
417
|
+
operator: { operator: 'INVALID' },
|
|
418
|
+
left: {
|
|
419
|
+
type: 'Tag',
|
|
420
|
+
field: { name: 'field' },
|
|
421
|
+
expression: { value: 'value' },
|
|
422
|
+
operator: { operator: ':' }
|
|
423
|
+
}
|
|
424
|
+
} as unknown;
|
|
425
|
+
|
|
426
|
+
expect(() =>
|
|
427
|
+
parser['convertLiqeAst'](
|
|
428
|
+
mockLiqeAst as unknown as import('liqe').LiqeQuery
|
|
429
|
+
)
|
|
430
|
+
).toThrow(QueryParseError);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('should throw QueryParseError for invalid field or expression in Tag node', () => {
|
|
434
|
+
// Simulating an internal error where field or expression is missing in a Tag node
|
|
435
|
+
const mockLiqeAst = {
|
|
436
|
+
type: 'Tag',
|
|
437
|
+
operator: { operator: ':' }
|
|
438
|
+
} as unknown;
|
|
439
|
+
|
|
440
|
+
expect(() =>
|
|
441
|
+
parser['convertLiqeAst'](
|
|
442
|
+
mockLiqeAst as unknown as import('liqe').LiqeQuery
|
|
443
|
+
)
|
|
444
|
+
).toThrow(QueryParseError);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('should throw QueryParseError for invalid empty expression', () => {
|
|
448
|
+
// Simulating an internal error for an invalid empty expression
|
|
449
|
+
const mockLiqeAst = {
|
|
450
|
+
type: 'EmptyExpression'
|
|
451
|
+
// Missing 'left' property
|
|
452
|
+
} as unknown;
|
|
453
|
+
|
|
454
|
+
expect(() =>
|
|
455
|
+
parser['convertLiqeAst'](
|
|
456
|
+
mockLiqeAst as unknown as import('liqe').LiqeQuery
|
|
457
|
+
)
|
|
458
|
+
).toThrow(QueryParseError);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should throw QueryParseError for invalid parenthesized expression', () => {
|
|
462
|
+
// Simulating an internal error for an invalid parenthesized expression
|
|
463
|
+
const mockLiqeAst = {
|
|
464
|
+
type: 'ParenthesizedExpression'
|
|
465
|
+
// Missing 'expression' property
|
|
466
|
+
} as unknown;
|
|
467
|
+
|
|
468
|
+
expect(() =>
|
|
469
|
+
parser['convertLiqeAst'](
|
|
470
|
+
mockLiqeAst as unknown as import('liqe').LiqeQuery
|
|
471
|
+
)
|
|
472
|
+
).toThrow(QueryParseError);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('should throw QueryParseError for unsupported value type', () => {
|
|
476
|
+
// Create a mock Tag node with an unsupported value type (object)
|
|
477
|
+
const mockLiqeAst = {
|
|
478
|
+
type: 'Tag',
|
|
479
|
+
field: { name: 'field' },
|
|
480
|
+
operator: { operator: ':' },
|
|
481
|
+
expression: { value: { unsupported: 'object' } }
|
|
482
|
+
} as unknown;
|
|
483
|
+
|
|
484
|
+
expect(() =>
|
|
485
|
+
parser['convertLiqeAst'](mockLiqeAst as import('liqe').LiqeQuery)
|
|
486
|
+
).toThrow(QueryParseError);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it('should parse multiple field mappings in a single query', () => {
|
|
490
|
+
const parser = new QueryParser({
|
|
491
|
+
fieldMappings: {
|
|
492
|
+
first_name: 'firstName',
|
|
493
|
+
last_name: 'lastName'
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
const query = 'first_name:John AND last_name:Doe';
|
|
498
|
+
const expected: QueryExpression = {
|
|
499
|
+
type: 'logical',
|
|
500
|
+
operator: 'AND',
|
|
501
|
+
left: {
|
|
502
|
+
type: 'comparison',
|
|
503
|
+
field: 'firstName',
|
|
504
|
+
operator: '==',
|
|
505
|
+
value: 'John'
|
|
506
|
+
},
|
|
507
|
+
right: {
|
|
508
|
+
type: 'comparison',
|
|
509
|
+
field: 'lastName',
|
|
510
|
+
operator: '==',
|
|
511
|
+
value: 'Doe'
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// New test cases to improve coverage
|
|
519
|
+
it('should handle IN operator expressions with array values', () => {
|
|
520
|
+
// This test is to handle the IN operator cases in convertLiqeOperator
|
|
521
|
+
const mockLiqeAst = {
|
|
522
|
+
type: 'Tag',
|
|
523
|
+
field: { name: 'status' },
|
|
524
|
+
operator: { operator: 'in' },
|
|
525
|
+
expression: { value: ['active', 'pending'] }
|
|
526
|
+
} as unknown;
|
|
527
|
+
|
|
528
|
+
const result = parser['convertLiqeAst'](
|
|
529
|
+
mockLiqeAst as import('liqe').LiqeQuery
|
|
530
|
+
);
|
|
531
|
+
expect(result).toEqual({
|
|
532
|
+
type: 'comparison',
|
|
533
|
+
field: 'status',
|
|
534
|
+
operator: 'IN',
|
|
535
|
+
value: ['active', 'pending']
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('should handle NOT IN operator expressions with array values', () => {
|
|
540
|
+
// This test is to handle the NOT IN operator cases in convertLiqeOperator
|
|
541
|
+
const mockLiqeAst = {
|
|
542
|
+
type: 'Tag',
|
|
543
|
+
field: { name: 'status' },
|
|
544
|
+
operator: { operator: 'not in' },
|
|
545
|
+
expression: { value: ['inactive', 'deleted'] }
|
|
546
|
+
} as unknown;
|
|
547
|
+
|
|
548
|
+
const result = parser['convertLiqeAst'](
|
|
549
|
+
mockLiqeAst as import('liqe').LiqeQuery
|
|
550
|
+
);
|
|
551
|
+
expect(result).toEqual({
|
|
552
|
+
type: 'comparison',
|
|
553
|
+
field: 'status',
|
|
554
|
+
operator: 'NOT IN',
|
|
555
|
+
value: ['inactive', 'deleted']
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('should handle equality operator (=) expressions', () => {
|
|
560
|
+
// Test for the '=' case in convertLiqeOperator
|
|
561
|
+
const mockLiqeAst = {
|
|
562
|
+
type: 'Tag',
|
|
563
|
+
field: { name: 'priority' },
|
|
564
|
+
operator: { operator: '=' },
|
|
565
|
+
expression: { value: 2 }
|
|
566
|
+
} as unknown;
|
|
567
|
+
|
|
568
|
+
const result = parser['convertLiqeAst'](
|
|
569
|
+
mockLiqeAst as import('liqe').LiqeQuery
|
|
570
|
+
);
|
|
571
|
+
expect(result).toEqual({
|
|
572
|
+
type: 'comparison',
|
|
573
|
+
field: 'priority',
|
|
574
|
+
operator: '==',
|
|
575
|
+
value: 2
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('should handle array values in queries', () => {
|
|
580
|
+
// Testing array handling in convertLiqeValue
|
|
581
|
+
const mockLiqeAst = {
|
|
582
|
+
type: 'Tag',
|
|
583
|
+
field: { name: 'tags' },
|
|
584
|
+
operator: { operator: ':' },
|
|
585
|
+
expression: { value: ['important', 'urgent', 'critical'] }
|
|
586
|
+
} as unknown;
|
|
587
|
+
|
|
588
|
+
const result = parser['convertLiqeAst'](
|
|
589
|
+
mockLiqeAst as import('liqe').LiqeQuery
|
|
590
|
+
);
|
|
591
|
+
expect(result).toEqual({
|
|
592
|
+
type: 'comparison',
|
|
593
|
+
field: 'tags',
|
|
594
|
+
operator: '==',
|
|
595
|
+
value: ['important', 'urgent', 'critical']
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it('should handle EmptyExpression with left property', () => {
|
|
600
|
+
// Testing the EmptyExpression branch with a left property
|
|
601
|
+
const mockLiqeAst = {
|
|
602
|
+
type: 'EmptyExpression',
|
|
603
|
+
left: {
|
|
604
|
+
type: 'Tag',
|
|
605
|
+
field: { name: 'status' },
|
|
606
|
+
operator: { operator: ':' },
|
|
607
|
+
expression: { value: 'active' }
|
|
608
|
+
}
|
|
609
|
+
} as unknown;
|
|
610
|
+
|
|
611
|
+
const result = parser['convertLiqeAst'](
|
|
612
|
+
mockLiqeAst as import('liqe').LiqeQuery
|
|
613
|
+
);
|
|
614
|
+
expect(result).toEqual({
|
|
615
|
+
type: 'comparison',
|
|
616
|
+
field: 'status',
|
|
617
|
+
operator: '==',
|
|
618
|
+
value: 'active'
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
it('should handle error details in QueryParseError', () => {
|
|
623
|
+
try {
|
|
624
|
+
// Force a specific error to test error handling in parse method
|
|
625
|
+
parser['convertLiqeAst'] = (): QueryExpression => {
|
|
626
|
+
const error = new Error('Test specific error message');
|
|
627
|
+
throw error;
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
parser.parse('status:active');
|
|
631
|
+
fail('Should have thrown an error');
|
|
632
|
+
} catch (error) {
|
|
633
|
+
expect(error).toBeInstanceOf(QueryParseError);
|
|
634
|
+
expect((error as Error).message).toContain(
|
|
635
|
+
'Test specific error message'
|
|
636
|
+
);
|
|
637
|
+
} finally {
|
|
638
|
+
// Restore original method
|
|
639
|
+
parser['convertLiqeAst'] = jest.fn();
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
it('should throw QueryParseError for non-Error exceptions', () => {
|
|
644
|
+
try {
|
|
645
|
+
// Force a non-Error exception to test that branch
|
|
646
|
+
parser['convertLiqeAst'] = (): QueryExpression => {
|
|
647
|
+
throw 'String exception'; // Not an Error object
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
parser.parse('status:active');
|
|
651
|
+
fail('Should have thrown an error');
|
|
652
|
+
} catch (error) {
|
|
653
|
+
expect(error).toBeInstanceOf(QueryParseError);
|
|
654
|
+
expect((error as Error).message).toContain('String exception');
|
|
655
|
+
} finally {
|
|
656
|
+
// Restore original method
|
|
657
|
+
parser['convertLiqeAst'] = jest.fn();
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
// Additional tests to improve coverage further
|
|
662
|
+
it('should handle explicit case when node type is not recognized', () => {
|
|
663
|
+
// Testing line 78: unsupported node type branch
|
|
664
|
+
const mockLiqeAst = {
|
|
665
|
+
type: 'UnsupportedNodeType' // This should trigger the default case
|
|
666
|
+
} as unknown;
|
|
667
|
+
|
|
668
|
+
expect(() =>
|
|
669
|
+
parser['convertLiqeAst'](mockLiqeAst as import('liqe').LiqeQuery)
|
|
670
|
+
).toThrow(QueryParseError);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it('should handle more complex field names in normalizeFieldName', () => {
|
|
674
|
+
// Create a parser with both case insensitivity and field mappings
|
|
675
|
+
// to exercise line 207 in normalizeFieldName
|
|
676
|
+
const parser = new QueryParser({
|
|
677
|
+
caseInsensitiveFields: true,
|
|
678
|
+
fieldMappings: {
|
|
679
|
+
'user_profile.name': 'userName',
|
|
680
|
+
'user_profile.priority': 'userPriority'
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
const mockLiqeAst = {
|
|
685
|
+
type: 'Tag',
|
|
686
|
+
field: { name: 'USER_PROFILE.NAME' }, // Upper case to test case insensitivity
|
|
687
|
+
operator: { operator: ':' },
|
|
688
|
+
expression: { value: 'John' }
|
|
689
|
+
} as unknown;
|
|
690
|
+
|
|
691
|
+
const result = parser['convertLiqeAst'](
|
|
692
|
+
mockLiqeAst as import('liqe').LiqeQuery
|
|
693
|
+
);
|
|
694
|
+
expect(result).toEqual({
|
|
695
|
+
type: 'comparison',
|
|
696
|
+
field: 'userName', // Should be mapped and case-normalized
|
|
697
|
+
operator: '==',
|
|
698
|
+
value: 'John'
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it('should handle invalid parenthesized expression with undefined expression', () => {
|
|
703
|
+
// This tests line 142 - when a parenthesized expression has an undefined expression
|
|
704
|
+
const mockLiqeAst = {
|
|
705
|
+
type: 'ParenthesizedExpression',
|
|
706
|
+
expression: undefined
|
|
707
|
+
} as unknown;
|
|
708
|
+
|
|
709
|
+
expect(() =>
|
|
710
|
+
parser['convertLiqeAst'](mockLiqeAst as import('liqe').LiqeQuery)
|
|
711
|
+
).toThrow(QueryParseError);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
// Target line 78 specifically with correct typing
|
|
715
|
+
it('should throw QueryParseError for unknown node types', () => {
|
|
716
|
+
// We need to make sure line 78 executes by creating an object with a type property but not a known type
|
|
717
|
+
const mockUnknownNode = {
|
|
718
|
+
type: 'UnknownType'
|
|
719
|
+
} as unknown as import('liqe').LiqeQuery;
|
|
720
|
+
|
|
721
|
+
expect(() => parser['convertLiqeAst'](mockUnknownNode)).toThrowError(
|
|
722
|
+
'Unsupported node type: UnknownType'
|
|
723
|
+
);
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
// Target line 142 specifically - ParenthesizedExpression with undefined expression
|
|
727
|
+
it('should throw QueryParseError for ParenthesizedExpression with undefined expression', () => {
|
|
728
|
+
const mockParenNode = {
|
|
729
|
+
type: 'ParenthesizedExpression',
|
|
730
|
+
// Explicitly define expression as undefined to target line 142
|
|
731
|
+
expression: undefined
|
|
732
|
+
} as unknown as import('liqe').LiqeQuery;
|
|
733
|
+
|
|
734
|
+
expect(() => parser['convertLiqeAst'](mockParenNode)).toThrowError(
|
|
735
|
+
'Invalid parenthesized expression'
|
|
736
|
+
);
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
// Target line 207 - the fallback case in normalizeFieldName
|
|
740
|
+
it('should return the original field name when no mapping exists', () => {
|
|
741
|
+
// Create a parser with field mappings that don't include the field we're testing
|
|
742
|
+
const parser = new QueryParser({
|
|
743
|
+
fieldMappings: {
|
|
744
|
+
known_field: 'mappedField'
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// This should test line 207 by using an unmapped field
|
|
749
|
+
const unmappedField = 'unmapped_field';
|
|
750
|
+
const result = parser['normalizeFieldName'](unmappedField);
|
|
751
|
+
|
|
752
|
+
// Should return the original field since there is no mapping
|
|
753
|
+
expect(result).toBe(unmappedField);
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
it('should handle all cases in the switch statement of convertLiqeAst', () => {
|
|
757
|
+
// Testing line 78: the last case in the switch statement
|
|
758
|
+
// Create a direct mock of a node with an unknown type
|
|
759
|
+
const mockNode = { type: 'CompletelyUnknownType' };
|
|
760
|
+
|
|
761
|
+
try {
|
|
762
|
+
// Need to call the method directly
|
|
763
|
+
(parser as unknown as QueryParserPrivate).convertLiqeAst(mockNode);
|
|
764
|
+
fail('Should have thrown an error');
|
|
765
|
+
} catch (error) {
|
|
766
|
+
// Verify we hit the default case
|
|
767
|
+
expect(error).toBeInstanceOf(QueryParseError);
|
|
768
|
+
expect((error as QueryParseError).message).toBe(
|
|
769
|
+
'Unsupported node type: CompletelyUnknownType'
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
it('should handle invalid ParenthesizedExpression node', () => {
|
|
775
|
+
// Testing line 142: the throw in the ParenthesizedExpression case
|
|
776
|
+
// Create a direct mock of a ParenthesizedExpression without an expression property
|
|
777
|
+
const mockNode = {
|
|
778
|
+
type: 'ParenthesizedExpression'
|
|
779
|
+
// Intentionally not adding an expression property
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
try {
|
|
783
|
+
// Need to call the method directly
|
|
784
|
+
(parser as unknown as QueryParserPrivate).convertLiqeAst(mockNode);
|
|
785
|
+
fail('Should have thrown an error');
|
|
786
|
+
} catch (error) {
|
|
787
|
+
// Verify we hit the right case
|
|
788
|
+
expect(error).toBeInstanceOf(QueryParseError);
|
|
789
|
+
expect((error as QueryParseError).message).toBe(
|
|
790
|
+
'Invalid parenthesized expression'
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it('should handle multiple wildcards in a pattern', () => {
|
|
796
|
+
const query = 'title:"*product*"';
|
|
797
|
+
const expected: QueryExpression = {
|
|
798
|
+
type: 'comparison',
|
|
799
|
+
field: 'title',
|
|
800
|
+
operator: 'LIKE',
|
|
801
|
+
value: '*product*'
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
it('should parse prefix wildcard pattern (foo*)', () => {
|
|
808
|
+
const query = 'name:foo*';
|
|
809
|
+
const expected: QueryExpression = {
|
|
810
|
+
type: 'comparison',
|
|
811
|
+
field: 'name',
|
|
812
|
+
operator: 'LIKE',
|
|
813
|
+
value: 'foo*'
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
it('should parse combined wildcard pattern (foo*bar)', () => {
|
|
820
|
+
const query = 'name:foo*bar';
|
|
821
|
+
const expected: QueryExpression = {
|
|
822
|
+
type: 'comparison',
|
|
823
|
+
field: 'name',
|
|
824
|
+
operator: 'LIKE',
|
|
825
|
+
value: 'foo*bar'
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
it('should parse single character wildcard (foo?)', () => {
|
|
832
|
+
const query = 'name:foo?';
|
|
833
|
+
const expected: QueryExpression = {
|
|
834
|
+
type: 'comparison',
|
|
835
|
+
field: 'name',
|
|
836
|
+
operator: 'LIKE',
|
|
837
|
+
value: 'foo?'
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
it('should parse combined single character wildcard (foo?bar)', () => {
|
|
844
|
+
const query = 'name:foo?bar';
|
|
845
|
+
const expected: QueryExpression = {
|
|
846
|
+
type: 'comparison',
|
|
847
|
+
field: 'name',
|
|
848
|
+
operator: 'LIKE',
|
|
849
|
+
value: 'foo?bar'
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
it('should parse mixed wildcard patterns (f*o?bar*)', () => {
|
|
856
|
+
const query = 'name:f*o?bar*';
|
|
857
|
+
const expected: QueryExpression = {
|
|
858
|
+
type: 'comparison',
|
|
859
|
+
field: 'name',
|
|
860
|
+
operator: 'LIKE',
|
|
861
|
+
value: 'f*o?bar*'
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
expect(parser.parse(query)).toEqual(expected);
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
describe('validate', () => {
|
|
869
|
+
it('should return true for valid queries', () => {
|
|
870
|
+
const validQueries = [
|
|
871
|
+
'priority:>2',
|
|
872
|
+
'status:active',
|
|
873
|
+
'priority:>2 AND status:active',
|
|
874
|
+
'status:active OR status:pending',
|
|
875
|
+
'NOT status:inactive'
|
|
876
|
+
];
|
|
877
|
+
|
|
878
|
+
validQueries.forEach(query => {
|
|
879
|
+
expect(parser.validate(query)).toBe(true);
|
|
880
|
+
});
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
it('should return false for invalid queries', () => {
|
|
884
|
+
const invalidQueries = ['', 'invalid:query:format', 'field:', ':value'];
|
|
885
|
+
|
|
886
|
+
invalidQueries.forEach(query => {
|
|
887
|
+
expect(parser.validate(query)).toBe(false);
|
|
888
|
+
});
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
// New validation tests
|
|
892
|
+
it('should return true for complex valid queries', () => {
|
|
893
|
+
const validComplexQueries = [
|
|
894
|
+
'name:"John Doe" AND (priority:>2 OR role:"admin")',
|
|
895
|
+
'(status:active OR status:pending) AND NOT isDeleted:true',
|
|
896
|
+
'tags:"important*" OR (priority:>3 AND assignee:"john@example.com")'
|
|
897
|
+
];
|
|
898
|
+
|
|
899
|
+
validComplexQueries.forEach(query => {
|
|
900
|
+
expect(parser.validate(query)).toBe(true);
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
it('should return false for additional invalid queries', () => {
|
|
905
|
+
const additionalInvalidQueries = [
|
|
906
|
+
'field:value:extra', // Too many colons
|
|
907
|
+
'AND field:value', // Starting with operator
|
|
908
|
+
'field:value AND', // Ending with operator
|
|
909
|
+
'(field:value', // Unclosed parenthesis
|
|
910
|
+
'field:value)', // Extra closing parenthesis
|
|
911
|
+
'OR' // Lone operator
|
|
912
|
+
];
|
|
913
|
+
|
|
914
|
+
additionalInvalidQueries.forEach(query => {
|
|
915
|
+
expect(parser.validate(query)).toBe(false);
|
|
916
|
+
});
|
|
917
|
+
});
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
// Test additional constructor options
|
|
921
|
+
describe('parser options', () => {
|
|
922
|
+
it('should apply default options when none provided', () => {
|
|
923
|
+
const parser = new QueryParser();
|
|
924
|
+
|
|
925
|
+
// Case sensitive by default
|
|
926
|
+
const query = 'STATUS:active';
|
|
927
|
+
const parsedQuery = parser.parse(query);
|
|
928
|
+
expect(parsedQuery).toEqual({
|
|
929
|
+
type: 'comparison',
|
|
930
|
+
field: 'STATUS', // Not lowercased
|
|
931
|
+
operator: '==',
|
|
932
|
+
value: 'active'
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
it('should apply multiple options when provided', () => {
|
|
937
|
+
const parser = new QueryParser({
|
|
938
|
+
caseInsensitiveFields: true,
|
|
939
|
+
fieldMappings: { user_name: 'username' }
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
// Test both options working together
|
|
943
|
+
const query = 'USER_NAME:john';
|
|
944
|
+
const parsedQuery = parser.parse(query);
|
|
945
|
+
expect(parsedQuery).toEqual({
|
|
946
|
+
type: 'comparison',
|
|
947
|
+
field: 'username', // Lowercased and mapped
|
|
948
|
+
operator: '==',
|
|
949
|
+
value: 'john'
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
// Add a specific test suite for direct private method testing
|
|
955
|
+
describe('Private method testing for coverage', () => {
|
|
956
|
+
let parser: QueryParser;
|
|
957
|
+
|
|
958
|
+
beforeEach(() => {
|
|
959
|
+
parser = new QueryParser();
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
it('should directly test convertLiqeOperator with various inputs', () => {
|
|
963
|
+
// Testing convertLiqeOperator with various inputs for coverage
|
|
964
|
+
// Access private method using type assertion
|
|
965
|
+
const convertLiqeOperator = (
|
|
966
|
+
parser as unknown as QueryParserPrivate
|
|
967
|
+
).convertLiqeOperator.bind(parser);
|
|
968
|
+
|
|
969
|
+
// Test basic cases
|
|
970
|
+
expect(convertLiqeOperator(':')).toBe('==');
|
|
971
|
+
expect(convertLiqeOperator('=')).toBe('==');
|
|
972
|
+
expect(convertLiqeOperator('!=')).toBe('!=');
|
|
973
|
+
expect(convertLiqeOperator('>')).toBe('>');
|
|
974
|
+
expect(convertLiqeOperator('>=')).toBe('>=');
|
|
975
|
+
expect(convertLiqeOperator('<')).toBe('<');
|
|
976
|
+
expect(convertLiqeOperator('<=')).toBe('<=');
|
|
977
|
+
|
|
978
|
+
// Test specific cases for coverage
|
|
979
|
+
expect(convertLiqeOperator('in')).toBe('IN');
|
|
980
|
+
expect(convertLiqeOperator('not in')).toBe('NOT IN');
|
|
981
|
+
|
|
982
|
+
// Test with colon prefix
|
|
983
|
+
expect(convertLiqeOperator(':>')).toBe('>');
|
|
984
|
+
|
|
985
|
+
// Test error case
|
|
986
|
+
expect(() => convertLiqeOperator('unknown')).toThrow(QueryParseError);
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
it('should directly test convertLiqeValue with various inputs', () => {
|
|
990
|
+
// Access private method using type assertion
|
|
991
|
+
const convertLiqeValue = (
|
|
992
|
+
parser as unknown as QueryParserPrivate
|
|
993
|
+
).convertLiqeValue.bind(parser);
|
|
994
|
+
|
|
995
|
+
// Test various value types
|
|
996
|
+
expect(convertLiqeValue('string')).toBe('string');
|
|
997
|
+
expect(convertLiqeValue(123)).toBe(123);
|
|
998
|
+
expect(convertLiqeValue(true)).toBe(true);
|
|
999
|
+
expect(convertLiqeValue(null)).toBe(null);
|
|
1000
|
+
expect(convertLiqeValue([1, 2, 3])).toEqual([1, 2, 3]);
|
|
1001
|
+
|
|
1002
|
+
// Test error case
|
|
1003
|
+
expect(() => convertLiqeValue({ key: 'value' })).toThrow(QueryParseError);
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
it('should directly test normalizeFieldName', () => {
|
|
1007
|
+
// Test with default options
|
|
1008
|
+
expect(
|
|
1009
|
+
(parser as unknown as QueryParserPrivate).normalizeFieldName(
|
|
1010
|
+
'fieldName'
|
|
1011
|
+
)
|
|
1012
|
+
).toBe('fieldName');
|
|
1013
|
+
|
|
1014
|
+
// Test with case insensitivity
|
|
1015
|
+
const caseInsensitiveParser = new QueryParser({
|
|
1016
|
+
caseInsensitiveFields: true
|
|
1017
|
+
});
|
|
1018
|
+
expect(
|
|
1019
|
+
(
|
|
1020
|
+
caseInsensitiveParser as unknown as QueryParserPrivate
|
|
1021
|
+
).normalizeFieldName('FieldName')
|
|
1022
|
+
).toBe('fieldname');
|
|
1023
|
+
|
|
1024
|
+
// Test with field mappings
|
|
1025
|
+
const mappingParser = new QueryParser({
|
|
1026
|
+
fieldMappings: { old_name: 'newName' }
|
|
1027
|
+
});
|
|
1028
|
+
expect(
|
|
1029
|
+
(mappingParser as unknown as QueryParserPrivate).normalizeFieldName(
|
|
1030
|
+
'old_name'
|
|
1031
|
+
)
|
|
1032
|
+
).toBe('newName');
|
|
1033
|
+
expect(
|
|
1034
|
+
(mappingParser as unknown as QueryParserPrivate).normalizeFieldName(
|
|
1035
|
+
'unmapped'
|
|
1036
|
+
)
|
|
1037
|
+
).toBe('unmapped');
|
|
1038
|
+
|
|
1039
|
+
// Test with both options
|
|
1040
|
+
const combinedParser = new QueryParser({
|
|
1041
|
+
caseInsensitiveFields: true,
|
|
1042
|
+
fieldMappings: { old_name: 'newName' }
|
|
1043
|
+
});
|
|
1044
|
+
expect(
|
|
1045
|
+
(combinedParser as unknown as QueryParserPrivate).normalizeFieldName(
|
|
1046
|
+
'OLD_NAME'
|
|
1047
|
+
)
|
|
1048
|
+
).toBe('newName');
|
|
1049
|
+
expect(
|
|
1050
|
+
(combinedParser as unknown as QueryParserPrivate).normalizeFieldName(
|
|
1051
|
+
'Unmapped'
|
|
1052
|
+
)
|
|
1053
|
+
).toBe('unmapped');
|
|
1054
|
+
});
|
|
1055
|
+
});
|
|
1056
|
+
});
|