| 
									
										
										
										
											2024-05-08 23:59:11 +02:00
										 |  |  | // @ts-nocheck
 | 
					
						
							|  |  |  | // There are many issues with the types of the parser e.g. "parse" function returns "Expression"
 | 
					
						
							|  |  |  | // but we access properties like "subExpressions" which is not defined in the "Expression" class.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-18 21:35:17 +03:00
										 |  |  | import Expression from "../../src/services/search/expressions/expression.js"; | 
					
						
							|  |  |  | import SearchContext from "../../src/services/search/search_context.js"; | 
					
						
							|  |  |  | import parse from "../../src/services/search/services/parse.js"; | 
					
						
							| 
									
										
										
										
											2024-05-08 23:59:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | function tokens(toks: Array<string>, cur = 0): Array<any> { | 
					
						
							|  |  |  |     return toks.map((arg) => { | 
					
						
							|  |  |  |         if (Array.isArray(arg)) { | 
					
						
							|  |  |  |             return tokens(arg, cur); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             cur += arg.length; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return { | 
					
						
							|  |  |  |                 token: arg, | 
					
						
							|  |  |  |                 inQuotes: false, | 
					
						
							|  |  |  |                 startIndex: cur - arg.length, | 
					
						
							|  |  |  |                 endIndex: cur - 1, | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function assertIsArchived(exp: Expression) { | 
					
						
							|  |  |  |     expect(exp.constructor.name).toEqual('PropertyComparisonExp'); | 
					
						
							|  |  |  |     expect(exp.propertyName).toEqual('isArchived'); | 
					
						
							|  |  |  |     expect(exp.operator).toEqual('='); | 
					
						
							|  |  |  |     expect(exp.comparedValue).toEqual('false'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | describe('Parser', () => { | 
					
						
							|  |  |  |     it('fulltext parser without content', () => { | 
					
						
							|  |  |  |         const rootExp = parse({ | 
					
						
							|  |  |  |             fulltextTokens: tokens(['hello', 'hi']), | 
					
						
							|  |  |  |             expressionTokens: [], | 
					
						
							|  |  |  |             searchContext: new SearchContext({ excludeArchived: true }), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[0].constructor.name).toEqual('PropertyComparisonExp'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual('OrExp'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].subExpressions[0].constructor.name).toEqual('NoteFlatTextExp'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].subExpressions[0].tokens).toEqual(['hello', 'hi']); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('fulltext parser with content', () => { | 
					
						
							|  |  |  |         const rootExp = parse({ | 
					
						
							|  |  |  |             fulltextTokens: tokens(['hello', 'hi']), | 
					
						
							|  |  |  |             expressionTokens: [], | 
					
						
							|  |  |  |             searchContext: new SearchContext(), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual('OrExp'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const subs = rootExp.subExpressions[2].subExpressions; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(subs[0].constructor.name).toEqual('NoteFlatTextExp'); | 
					
						
							|  |  |  |         expect(subs[0].tokens).toEqual(['hello', 'hi']); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(subs[1].constructor.name).toEqual('NoteContentFulltextExp'); | 
					
						
							|  |  |  |         expect(subs[1].tokens).toEqual(['hello', 'hi']); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('simple label comparison', () => { | 
					
						
							|  |  |  |         const rootExp = parse({ | 
					
						
							|  |  |  |             fulltextTokens: [], | 
					
						
							|  |  |  |             expressionTokens: tokens(['#mylabel', '=', 'text']), | 
					
						
							|  |  |  |             searchContext: new SearchContext(), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual('LabelComparisonExp'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].attributeType).toEqual('label'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].attributeName).toEqual('mylabel'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].comparator).toBeTruthy(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('simple attribute negation', () => { | 
					
						
							|  |  |  |         let rootExp = parse({ | 
					
						
							|  |  |  |             fulltextTokens: [], | 
					
						
							|  |  |  |             expressionTokens: tokens(['#!mylabel']), | 
					
						
							|  |  |  |             searchContext: new SearchContext(), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual('NotExp'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].subExpression.constructor.name).toEqual('AttributeExistsExp'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].subExpression.attributeType).toEqual('label'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].subExpression.attributeName).toEqual('mylabel'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         rootExp = parse({ | 
					
						
							|  |  |  |             fulltextTokens: [], | 
					
						
							|  |  |  |             expressionTokens: tokens(['~!myrelation']), | 
					
						
							|  |  |  |             searchContext: new SearchContext(), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual('NotExp'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].subExpression.constructor.name).toEqual('AttributeExistsExp'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].subExpression.attributeType).toEqual('relation'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].subExpression.attributeName).toEqual('myrelation'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('simple label AND', () => { | 
					
						
							|  |  |  |         const rootExp = parse({ | 
					
						
							|  |  |  |             fulltextTokens: [], | 
					
						
							|  |  |  |             expressionTokens: tokens(['#first', '=', 'text', 'and', '#second', '=', 'text']), | 
					
						
							|  |  |  |             searchContext: new SearchContext(true), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(firstSub.constructor.name).toEqual('LabelComparisonExp'); | 
					
						
							|  |  |  |         expect(firstSub.attributeName).toEqual('first'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(secondSub.constructor.name).toEqual('LabelComparisonExp'); | 
					
						
							|  |  |  |         expect(secondSub.attributeName).toEqual('second'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('simple label AND without explicit AND', () => { | 
					
						
							|  |  |  |         const rootExp = parse({ | 
					
						
							|  |  |  |             fulltextTokens: [], | 
					
						
							|  |  |  |             expressionTokens: tokens(['#first', '=', 'text', '#second', '=', 'text']), | 
					
						
							|  |  |  |             searchContext: new SearchContext(), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(firstSub.constructor.name).toEqual('LabelComparisonExp'); | 
					
						
							|  |  |  |         expect(firstSub.attributeName).toEqual('first'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(secondSub.constructor.name).toEqual('LabelComparisonExp'); | 
					
						
							|  |  |  |         expect(secondSub.attributeName).toEqual('second'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('simple label OR', () => { | 
					
						
							|  |  |  |         const rootExp = parse({ | 
					
						
							|  |  |  |             fulltextTokens: [], | 
					
						
							|  |  |  |             expressionTokens: tokens(['#first', '=', 'text', 'or', '#second', '=', 'text']), | 
					
						
							|  |  |  |             searchContext: new SearchContext(), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual('OrExp'); | 
					
						
							|  |  |  |         const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(firstSub.constructor.name).toEqual('LabelComparisonExp'); | 
					
						
							|  |  |  |         expect(firstSub.attributeName).toEqual('first'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(secondSub.constructor.name).toEqual('LabelComparisonExp'); | 
					
						
							|  |  |  |         expect(secondSub.attributeName).toEqual('second'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('fulltext and simple label', () => { | 
					
						
							|  |  |  |         const rootExp = parse({ | 
					
						
							|  |  |  |             fulltextTokens: tokens(['hello']), | 
					
						
							|  |  |  |             expressionTokens: tokens(['#mylabel', '=', 'text']), | 
					
						
							|  |  |  |             searchContext: new SearchContext({ excludeArchived: true }), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         const [firstSub, secondSub, thirdSub, fourth] = rootExp.subExpressions; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(firstSub.constructor.name).toEqual('PropertyComparisonExp'); | 
					
						
							|  |  |  |         expect(firstSub.propertyName).toEqual('isArchived'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(thirdSub.constructor.name).toEqual('OrExp'); | 
					
						
							|  |  |  |         expect(thirdSub.subExpressions[0].constructor.name).toEqual('NoteFlatTextExp'); | 
					
						
							|  |  |  |         expect(thirdSub.subExpressions[0].tokens).toEqual(['hello']); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(fourth.constructor.name).toEqual('LabelComparisonExp'); | 
					
						
							|  |  |  |         expect(fourth.attributeName).toEqual('mylabel'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('label sub-expression', () => { | 
					
						
							|  |  |  |         const rootExp = parse({ | 
					
						
							|  |  |  |             fulltextTokens: [], | 
					
						
							|  |  |  |             expressionTokens: tokens(['#first', '=', 'text', 'or', ['#second', '=', 'text', 'and', '#third', '=', 'text']]), | 
					
						
							|  |  |  |             searchContext: new SearchContext(), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual('OrExp'); | 
					
						
							|  |  |  |         const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(firstSub.constructor.name).toEqual('LabelComparisonExp'); | 
					
						
							|  |  |  |         expect(firstSub.attributeName).toEqual('first'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(secondSub.constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         const [firstSubSub, secondSubSub] = secondSub.subExpressions; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(firstSubSub.constructor.name).toEqual('LabelComparisonExp'); | 
					
						
							|  |  |  |         expect(firstSubSub.attributeName).toEqual('second'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(secondSubSub.constructor.name).toEqual('LabelComparisonExp'); | 
					
						
							|  |  |  |         expect(secondSubSub.attributeName).toEqual('third'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('label sub-expression without explicit operator', () => { | 
					
						
							|  |  |  |         const rootExp = parse({ | 
					
						
							|  |  |  |             fulltextTokens: [], | 
					
						
							|  |  |  |             expressionTokens: tokens(['#first', ['#second', 'or', '#third'], '#fourth']), | 
					
						
							|  |  |  |             searchContext: new SearchContext(), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         const [firstSub, secondSub, thirdSub] = rootExp.subExpressions[2].subExpressions; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(firstSub.constructor.name).toEqual('AttributeExistsExp'); | 
					
						
							|  |  |  |         expect(firstSub.attributeName).toEqual('first'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(secondSub.constructor.name).toEqual('OrExp'); | 
					
						
							|  |  |  |         const [firstSubSub, secondSubSub] = secondSub.subExpressions; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(firstSubSub.constructor.name).toEqual('AttributeExistsExp'); | 
					
						
							|  |  |  |         expect(firstSubSub.attributeName).toEqual('second'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(secondSubSub.constructor.name).toEqual('AttributeExistsExp'); | 
					
						
							|  |  |  |         expect(secondSubSub.attributeName).toEqual('third'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(thirdSub.constructor.name).toEqual('AttributeExistsExp'); | 
					
						
							|  |  |  |         expect(thirdSub.attributeName).toEqual('fourth'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | describe('Invalid expressions', () => { | 
					
						
							|  |  |  |     it('incomplete comparison', () => { | 
					
						
							|  |  |  |         const searchContext = new SearchContext(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         parse({ | 
					
						
							|  |  |  |             fulltextTokens: [], | 
					
						
							|  |  |  |             expressionTokens: tokens(['#first', '=']), | 
					
						
							|  |  |  |             searchContext, | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(searchContext.error).toEqual('Misplaced or incomplete expression "="'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('comparison between labels is impossible', () => { | 
					
						
							|  |  |  |         let searchContext = new SearchContext(); | 
					
						
							|  |  |  |         searchContext.originalQuery = '#first = #second'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         parse({ | 
					
						
							|  |  |  |             fulltextTokens: [], | 
					
						
							|  |  |  |             expressionTokens: tokens(['#first', '=', '#second']), | 
					
						
							|  |  |  |             searchContext, | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(searchContext.error).toEqual( | 
					
						
							|  |  |  |             `Error near token "#second" in "#first = #second", it's possible to compare with constant only.` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         searchContext = new SearchContext(); | 
					
						
							|  |  |  |         searchContext.originalQuery = '#first = note.relations.second'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         parse({ | 
					
						
							|  |  |  |             fulltextTokens: [], | 
					
						
							|  |  |  |             expressionTokens: tokens(['#first', '=', 'note', '.', 'relations', 'second']), | 
					
						
							|  |  |  |             searchContext, | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(searchContext.error).toEqual( | 
					
						
							|  |  |  |             `Error near token "note" in "#first = note.relations.second", it's possible to compare with constant only.` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const rootExp = parse({ | 
					
						
							|  |  |  |             fulltextTokens: [], | 
					
						
							|  |  |  |             expressionTokens: [ | 
					
						
							|  |  |  |                 { token: '#first', inQuotes: false }, | 
					
						
							|  |  |  |                 { token: '=', inQuotes: false }, | 
					
						
							|  |  |  |                 { token: '#second', inQuotes: true }, | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |             searchContext: new SearchContext(), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.constructor.name).toEqual('AndExp'); | 
					
						
							|  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual('LabelComparisonExp'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].attributeType).toEqual('label'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].attributeName).toEqual('first'); | 
					
						
							|  |  |  |         expect(rootExp.subExpressions[2].comparator).toBeTruthy(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('searching by relation without note property', () => { | 
					
						
							|  |  |  |         const searchContext = new SearchContext(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         parse({ | 
					
						
							|  |  |  |             fulltextTokens: [], | 
					
						
							|  |  |  |             expressionTokens: tokens(['~first', '=', 'text', '-', 'abc']), | 
					
						
							|  |  |  |             searchContext, | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(searchContext.error).toEqual('Relation can be compared only with property, e.g. ~relation.title=hello in ""'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | }); |