diff --git a/packages/core/src/__tests__/operator.test.ts b/packages/core/src/__tests__/operator.test.ts index 0492c6e..a3b6d7a 100644 --- a/packages/core/src/__tests__/operator.test.ts +++ b/packages/core/src/__tests__/operator.test.ts @@ -395,4 +395,141 @@ describe('operator', () => { expect(() => _getOperator('$invalid')).toThrow('unknown operator: $invalid') }) }) + + describe('operator error handling', () => { + describe('$pull with non-array values', () => { + it('should handle string value instead of array', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, field: 'not-an-array' } as unknown as Doc + const operator = _getOperator('$pull') + operator(doc, { field: 'value' }) + expect((doc as any).field).toEqual([]) + }) + + it('should handle object value instead of array', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, field: { key: 'value' } } as unknown as Doc + const operator = _getOperator('$pull') + operator(doc, { field: 'value' }) + expect((doc as any).field).toEqual([]) + }) + + it('should handle number value instead of array', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, field: 42 } as unknown as Doc + const operator = _getOperator('$pull') + operator(doc, { field: 'value' }) + expect((doc as any).field).toEqual([]) + }) + + it('should handle null value', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, field: null } as unknown as Doc + const operator = _getOperator('$pull') + operator(doc, { field: 'value' }) + expect((doc as any).field).toEqual([]) + }) + + it('should work correctly with valid array', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, field: [1, 2, 3] } as unknown as Doc + const operator = _getOperator('$pull') + operator(doc, { field: 2 }) + expect((doc as any).field).toEqual([1, 3]) + }) + }) + + describe('$update with non-array values', () => { + it('should handle string value instead of array', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, field: 'not-an-array' } as unknown as Doc + const operator = _getOperator('$update') + operator(doc, { field: { $query: { id: 1 }, $update: { value: 'new' } } }) + expect((doc as any).field).toEqual([]) + }) + + it('should handle object value instead of array', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, field: { key: 'value' } } as unknown as Doc + const operator = _getOperator('$update') + operator(doc, { field: { $query: { id: 1 }, $update: { value: 'new' } } }) + expect((doc as any).field).toEqual([]) + }) + + it('should handle null value', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, field: null } as unknown as Doc + const operator = _getOperator('$update') + operator(doc, { field: { $query: { id: 1 }, $update: { value: 'new' } } }) + expect((doc as any).field).toEqual([]) + }) + + it('should work correctly with valid array', () => { + const doc: Doc = { + _id: '1' as any, + _class: 'test' as any, + field: [ + { id: 1, value: 'old' }, + { id: 2, value: 'keep' } + ] + } as unknown as Doc + const operator = _getOperator('$update') + operator(doc, { field: { $query: { id: 1 }, $update: { value: 'new' } } }) + expect((doc as any).field).toEqual([ + { id: 1, value: 'new' }, + { id: 2, value: 'keep' } + ]) + }) + }) + + describe('$inc with non-numeric values', () => { + it('should handle NaN current value', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, count: NaN } as unknown as Doc + const operator = _getOperator('$inc') + operator(doc, { count: 5 }) + expect((doc as any).count).toBe(5) + }) + + it('should handle string current value', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, count: 'not-a-number' } as unknown as Doc + const operator = _getOperator('$inc') + operator(doc, { count: 5 }) + expect((doc as any).count).toBe(5) + }) + + it('should handle NaN increment value', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, count: 10 } as unknown as Doc + const operator = _getOperator('$inc') + operator(doc, { count: NaN }) + expect((doc as any).count).toBe(10) + }) + + it('should handle string increment value', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, count: 10 } as unknown as Doc + const operator = _getOperator('$inc') + operator(doc, { count: 'not-a-number' as any }) + expect((doc as any).count).toBe(10) + }) + + it('should handle undefined current value (should default to 0)', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any } as unknown as Doc + const operator = _getOperator('$inc') + operator(doc, { count: 5 }) + expect((doc as any).count).toBe(5) + }) + + it('should work correctly with valid numbers', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, count: 10 } as unknown as Doc + const operator = _getOperator('$inc') + operator(doc, { count: 5 }) + expect((doc as any).count).toBe(15) + }) + + it('should handle negative increments', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, count: 10 } as unknown as Doc + const operator = _getOperator('$inc') + operator(doc, { count: -3 }) + expect((doc as any).count).toBe(7) + }) + + it('should handle zero increment', () => { + const doc: Doc = { _id: '1' as any, _class: 'test' as any, count: 10 } as unknown as Doc + const operator = _getOperator('$inc') + operator(doc, { count: 0 }) + expect((doc as any).count).toBe(10) + }) + }) + }) }) diff --git a/packages/core/src/operator.ts b/packages/core/src/operator.ts index 7386e80..232b555 100644 --- a/packages/core/src/operator.ts +++ b/packages/core/src/operator.ts @@ -61,7 +61,13 @@ function $pull (document: Doc, keyval: Record): void { if (doc[key] === undefined) { doc[key] = [] } - const arr = doc[key] as Array + // Ensure doc[key] is an array before attempting to filter + if (!Array.isArray(doc[key])) { + Analytics.handleError(new Error(`$pull operation on non-array field: ${key}, value: ${JSON.stringify(doc[key])}`)) + doc[key] = [] + continue + } + const arr = doc[key] const kvk = keyval[key] if (typeof kvk === 'object' && kvk !== null) { const { $in } = kvk as PullArray @@ -111,9 +117,15 @@ function $update (document: Doc, keyval: Record): void { if (doc[key] === undefined) { doc[key] = [] } + // Ensure doc[key] is an array before attempting to update + if (!Array.isArray(doc[key])) { + Analytics.handleError(new Error(`$update operation on non-array field: ${key}, value: ${JSON.stringify(doc[key])}`)) + doc[key] = [] + continue + } const val = keyval[key] if (typeof val === 'object') { - const arr = doc[key] as Array + const arr = doc[key] const desc = val as QueryUpdate for (const m of matchArrayElement(arr, desc.$query)) { for (const [k, v] of Object.entries(desc.$update)) { @@ -128,7 +140,19 @@ function $inc (document: Doc, keyval: Record): void { const doc = document as unknown as Record for (const key in keyval) { const cur = doc[key] ?? 0 - doc[key] = cur + keyval[key] + // Ensure current value is a number + if (typeof cur !== 'number' || isNaN(cur)) { + Analytics.handleError(new Error(`$inc operation on non-numeric field: ${key}, value: ${JSON.stringify(doc[key])}`)) + doc[key] = keyval[key] + continue + } + const increment = keyval[key] + // Ensure increment value is a valid number + if (typeof increment !== 'number' || isNaN(increment)) { + Analytics.handleError(new Error(`$inc operation with invalid increment: ${key}, increment: ${JSON.stringify(increment)}`)) + continue + } + doc[key] = cur + increment } }