fix(classes.doc (convertFilterForMongoDb)): Improve filter conversion: handle logical operators, merge operator objects, add nested filter tests and docs, and fix test script
This commit is contained in:
		| @@ -213,6 +213,221 @@ tap.test('should filter by nested object fields', async () => { | ||||
|   expect(users[0].name).toEqual('John Doe'); | ||||
| }); | ||||
|  | ||||
| // ============= COMPREHENSIVE NESTED FILTER TESTS ============= | ||||
| tap.test('should filter by nested object with direct object syntax', async () => { | ||||
|   // Direct nested object matching (exact match) | ||||
|   const users = await TestUser.getInstances({ | ||||
|     metadata: {  | ||||
|       loginCount: 5, | ||||
|       lastLogin: (await TestUser.getInstances({}))[0].metadata.lastLogin // Get the exact date | ||||
|     } | ||||
|   }); | ||||
|   expect(users.length).toEqual(1); | ||||
|   expect(users[0].name).toEqual('John Doe'); | ||||
| }); | ||||
|  | ||||
| tap.test('should filter by partial nested object match', async () => { | ||||
|   // When using object syntax, only specified fields must match | ||||
|   const users = await TestUser.getInstances({ | ||||
|     metadata: { loginCount: 5 }  // Only checks loginCount, ignores other fields | ||||
|   }); | ||||
|   expect(users.length).toEqual(1); | ||||
|   expect(users[0].name).toEqual('John Doe'); | ||||
| }); | ||||
|  | ||||
| tap.test('should combine nested object and dot notation', async () => { | ||||
|   const users = await TestUser.getInstances({ | ||||
|     metadata: { loginCount: { $gte: 3 } },  // Object syntax with operator | ||||
|     'metadata.loginCount': { $lte: 10 }     // Dot notation with operator | ||||
|   }); | ||||
|   expect(users.length).toEqual(3);  // Jane (3), John (5), and Alice (10) have loginCount between 3-10 | ||||
| }); | ||||
|  | ||||
| tap.test('should filter nested fields with operators using dot notation', async () => { | ||||
|   const users = await TestUser.getInstances({ | ||||
|     'metadata.loginCount': { $gte: 5 } | ||||
|   }); | ||||
|   expect(users.length).toEqual(2);  // John (5) and Alice (10) | ||||
|   const names = users.map(u => u.name).sort(); | ||||
|   expect(names).toEqual(['Alice Brown', 'John Doe']); | ||||
| }); | ||||
|  | ||||
| tap.test('should filter nested fields with multiple operators', async () => { | ||||
|   const users = await TestUser.getInstances({ | ||||
|     'metadata.loginCount': { $gte: 3, $lt: 10 } | ||||
|   }); | ||||
|   expect(users.length).toEqual(2);  // Jane (3) and John (5) | ||||
|   const names = users.map(u => u.name).sort(); | ||||
|   expect(names).toEqual(['Jane Smith', 'John Doe']); | ||||
| }); | ||||
|  | ||||
| tap.test('should handle deeply nested object structures', async () => { | ||||
|   // First, create a user with deep nesting in preferences | ||||
|   const deepUser = new TestUser({ | ||||
|     name: 'Deep Nester', | ||||
|     age: 40, | ||||
|     email: 'deep@example.com', | ||||
|     roles: ['admin'], | ||||
|     tags: [], | ||||
|     status: 'active', | ||||
|     metadata: { | ||||
|       loginCount: 1, | ||||
|       preferences: { | ||||
|         theme: { | ||||
|           colors: { | ||||
|             primary: '#000000', | ||||
|             secondary: '#ffffff' | ||||
|           }, | ||||
|           fonts: { | ||||
|             heading: 'Arial', | ||||
|             body: 'Helvetica' | ||||
|           } | ||||
|         }, | ||||
|         notifications: { | ||||
|           email: true, | ||||
|           push: false | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     scores: [] | ||||
|   }); | ||||
|   await deepUser.save(); | ||||
|  | ||||
|   // Test deep nesting with dot notation | ||||
|   const deepResults = await TestUser.getInstances({ | ||||
|     'metadata.preferences.theme.colors.primary': '#000000' | ||||
|   }); | ||||
|   expect(deepResults.length).toEqual(1); | ||||
|   expect(deepResults[0].name).toEqual('Deep Nester'); | ||||
|  | ||||
|   // Test deep nesting with operators | ||||
|   const boolResults = await TestUser.getInstances({ | ||||
|     'metadata.preferences.notifications.email': { $eq: true } | ||||
|   }); | ||||
|   expect(boolResults.length).toEqual(1); | ||||
|   expect(boolResults[0].name).toEqual('Deep Nester'); | ||||
|  | ||||
|   // Clean up | ||||
|   await deepUser.delete(); | ||||
| }); | ||||
|  | ||||
| tap.test('should filter arrays of nested objects using $elemMatch', async () => { | ||||
|   const orders = await TestOrder.getInstances({ | ||||
|     items: { | ||||
|       $elemMatch: { | ||||
|         product: 'laptop', | ||||
|         price: { $gte: 1000 } | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|   expect(orders.length).toEqual(2);  // Both laptop orders have price >= 1000 | ||||
| }); | ||||
|  | ||||
| tap.test('should filter nested arrays with dot notation', async () => { | ||||
|   // Query for any order that has an item with specific product | ||||
|   const orders = await TestOrder.getInstances({ | ||||
|     'items.product': 'laptop' | ||||
|   }); | ||||
|   expect(orders.length).toEqual(2);  // Two orders contain laptops | ||||
| }); | ||||
|  | ||||
| tap.test('should combine nested object filters with logical operators', async () => { | ||||
|   const users = await TestUser.getInstances({ | ||||
|     $or: [ | ||||
|       { 'metadata.loginCount': { $gte: 10 } },  // Alice has 10 | ||||
|       {  | ||||
|         $and: [ | ||||
|           { 'metadata.loginCount': { $lt: 5 } },  // Jane has 3, Bob has 0, Charlie has 1 | ||||
|           { status: 'active' }  // Jane is active, Bob is inactive, Charlie is pending | ||||
|         ] | ||||
|       } | ||||
|     ] | ||||
|   }); | ||||
|   expect(users.length).toEqual(2);  // Alice (loginCount >= 10), Jane (loginCount < 5 AND active) | ||||
|   const names = users.map(u => u.name).sort(); | ||||
|   expect(names).toEqual(['Alice Brown', 'Jane Smith']); | ||||
| }); | ||||
|  | ||||
| tap.test('should handle null and undefined in nested fields', async () => { | ||||
|   // Users without lastLogin | ||||
|   const noLastLogin = await TestUser.getInstances({ | ||||
|     'metadata.lastLogin': { $exists: false } | ||||
|   }); | ||||
|   expect(noLastLogin.length).toEqual(4);  // Everyone except John | ||||
|  | ||||
|   // Users with preferences (none have it set) | ||||
|   const withPreferences = await TestUser.getInstances({ | ||||
|     'metadata.preferences': { $exists: true } | ||||
|   }); | ||||
|   expect(withPreferences.length).toEqual(0); | ||||
| }); | ||||
|  | ||||
| tap.test('should filter nested arrays by size', async () => { | ||||
|   // Create an order with specific number of items | ||||
|   const multiItemOrder = new TestOrder({ | ||||
|     userId: 'test-user', | ||||
|     items: [ | ||||
|       { product: 'item1', quantity: 1, price: 10 }, | ||||
|       { product: 'item2', quantity: 2, price: 20 }, | ||||
|       { product: 'item3', quantity: 3, price: 30 }, | ||||
|       { product: 'item4', quantity: 4, price: 40 } | ||||
|     ], | ||||
|     totalAmount: 100, | ||||
|     status: 'pending', | ||||
|     tags: ['test'] | ||||
|   }); | ||||
|   await multiItemOrder.save(); | ||||
|  | ||||
|   const fourItemOrders = await TestOrder.getInstances({ | ||||
|     items: { $size: 4 } | ||||
|   }); | ||||
|   expect(fourItemOrders.length).toEqual(1); | ||||
|  | ||||
|   // Clean up | ||||
|   await multiItemOrder.delete(); | ||||
| }); | ||||
|  | ||||
| tap.test('should handle nested field comparison between documents', async () => { | ||||
|   // Find users where loginCount equals their age divided by 6 (John: 30/6=5) | ||||
|   const users = await TestUser.getInstances({ | ||||
|     $and: [ | ||||
|       { 'metadata.loginCount': 5 }, | ||||
|       { age: 30 } | ||||
|     ] | ||||
|   }); | ||||
|   expect(users.length).toEqual(1); | ||||
|   expect(users[0].name).toEqual('John Doe'); | ||||
| }); | ||||
|  | ||||
| tap.test('should filter using $in on nested fields', async () => { | ||||
|   const users = await TestUser.getInstances({ | ||||
|     'metadata.loginCount': { $in: [0, 1, 5] } | ||||
|   }); | ||||
|   expect(users.length).toEqual(3);  // Bob (0), Charlie (1), John (5) | ||||
|   const names = users.map(u => u.name).sort(); | ||||
|   expect(names).toEqual(['Bob Johnson', 'Charlie Wilson', 'John Doe']); | ||||
| }); | ||||
|  | ||||
| tap.test('should filter nested arrays with $all', async () => { | ||||
|   // Create an order with multiple tags | ||||
|   const taggedOrder = new TestOrder({ | ||||
|     userId: 'test-user', | ||||
|     items: [{ product: 'test', quantity: 1, price: 10 }], | ||||
|     totalAmount: 10, | ||||
|     status: 'completed', | ||||
|     tags: ['urgent', 'priority', 'electronics'] | ||||
|   }); | ||||
|   await taggedOrder.save(); | ||||
|  | ||||
|   const priorityElectronics = await TestOrder.getInstances({ | ||||
|     tags: { $all: ['priority', 'electronics'] } | ||||
|   }); | ||||
|   expect(priorityElectronics.length).toEqual(2);  // Original order and new one | ||||
|  | ||||
|   // Clean up | ||||
|   await taggedOrder.delete(); | ||||
| }); | ||||
|  | ||||
| // ============= COMPARISON OPERATOR TESTS ============= | ||||
| tap.test('should filter using $gt operator', async () => { | ||||
|   const users = await TestUser.getInstances({ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user