import { QueryBuilder } from '../builders/QueryBuilder' function createMockQuery() { const calls: Array<{ method: string; args: any[] }> = [] const handler: ProxyHandler = { get(_target, prop) { if (prop === '_calls') return calls return (...args: any[]) => { calls.push({ method: String(prop), args }) return new Proxy({}, handler) } } } return new Proxy({}, handler) } describe('QueryBuilder', () => { describe('buildFilters', () => { it('adds organization_id and project_id to query', () => { const query = createMockQuery() QueryBuilder.buildFilters(query, { groups: [] }, 'org-1', 'proj-1') const calls = (query as any)._calls expect(calls[0]).toEqual({ method: 'eq', args: ['organization_id', 'org-1'] }) }) it('applies groups criteria with OR', () => { const query = createMockQuery() const criteria = { groups: [{ rules: [ { field: 'email', operator: 'equals', value: 'a@b.com' }, { field: 'name', operator: 'contains', value: 'John' } ] }] } QueryBuilder.buildFilters(query, criteria as any, 'org', 'proj') // Should end up calling .or() at some point const allCalls = (query as any)._calls expect(allCalls.length).toBeGreaterThan(0) }) it('returns query unchanged when no criteria format matches', () => { const query = createMockQuery() QueryBuilder.buildFilters(query, {} as any, 'org', 'proj') const calls = (query as any)._calls // Only eq calls for org/proj expect(calls[0].method).toBe('eq') }) }) describe('buildConditionString (via groups)', () => { function buildCondition(field: string, operator: string, value: any): string | null { const query = createMockQuery() const criteria = { groups: [{ rules: [{ field, operator, value }] }] } QueryBuilder.buildFilters(query, criteria as any, 'org', 'proj') // The last call should be or() with the condition string const allCalls = (query as any)._calls const orCall = allCalls.find((c: any) => c.method === 'or') return orCall ? orCall.args[0] : null } it('builds equals condition', () => { expect(buildCondition('email', 'equals', 'a@b.com')).toBe('email.eq.a@b.com') }) it('builds eq alias condition', () => { expect(buildCondition('email', 'eq', 'a@b.com')).toBe('email.eq.a@b.com') }) it('builds not_equals condition', () => { expect(buildCondition('email', 'not_equals', 'x')).toBe('email.neq.x') }) it('builds contains/ilike condition', () => { expect(buildCondition('name', 'contains', 'john')).toBe('name.ilike.*john*') }) it('builds starts_with condition', () => { expect(buildCondition('name', 'starts_with', 'Jo')).toBe('name.ilike.Jo*') }) it('builds ends_with condition', () => { expect(buildCondition('name', 'ends_with', 'hn')).toBe('name.ilike.*hn') }) it('builds greater_than condition', () => { expect(buildCondition('age', 'greater_than', 18)).toBe('age.gt.18') }) it('builds less_than condition', () => { expect(buildCondition('age', 'less_than', 65)).toBe('age.lt.65') }) it('builds is_empty condition (null OR empty string)', () => { expect(buildCondition('phone', 'is_empty', null)).toBe('or(phone.is.null,phone.eq.)') }) it('builds is_not_empty condition (NOT null AND not empty string)', () => { expect(buildCondition('phone', 'is_not_empty', null)).toBe('and(phone.not.is.null,phone.neq.)') }) it('is_not_empty for email excludes empty string (fix for "verificacao de email")', () => { expect(buildCondition('email', 'is_not_empty', null)).toBe('and(email.not.is.null,email.neq.)') }) it('is_empty for email includes empty string', () => { expect(buildCondition('email', 'is_empty', null)).toBe('or(email.is.null,email.eq.)') }) it('returns null for unknown operator', () => { expect(buildCondition('x', 'unknown_op', 'y')).toBeNull() }) it('returns null when field is missing', () => { expect(buildCondition('', 'equals', 'x')).toBeNull() }) it('maps custom_field with customFieldKey', () => { const query = createMockQuery() const criteria = { groups: [{ rules: [{ field: 'custom_field', operator: 'equals', value: 'vip', customFieldKey: 'plan' }] }] } QueryBuilder.buildFilters(query, criteria as any, 'org', 'proj') const allCalls = (query as any)._calls const orCall = allCalls.find((c: any) => c.method === 'or') expect(orCall?.args[0]).toContain('properties->>plan') }) }) describe('filters format (v1)', () => { it('applies OR filter groups', () => { const query = createMockQuery() const criteria = { filters: [{ id: 'f1', operator: 'OR', conditions: [ { id: 'c1', field: 'email', operator: 'equals', value: 'a@b.com', logicalOperator: 'OR' }, { id: 'c2', field: 'name', operator: 'contains', value: 'Jo', logicalOperator: 'OR' } ] }] } QueryBuilder.buildFilters(query, criteria as any, 'org', 'proj') const allCalls = (query as any)._calls const orCall = allCalls.find((c: any) => c.method === 'or') expect(orCall).toBeDefined() }) it('applies AND filter groups as individual conditions', () => { const query = createMockQuery() const criteria = { filters: [{ id: 'f1', operator: 'AND', conditions: [ { id: 'c1', field: 'email', operator: 'equals', value: 'a@b.com', logicalOperator: 'AND' } ] }] } QueryBuilder.buildFilters(query, criteria as any, 'org', 'proj') const allCalls = (query as any)._calls const eqCalls = allCalls.filter((c: any) => c.method === 'eq') expect(eqCalls.length).toBeGreaterThanOrEqual(2) // org + proj + email }) }) describe('conditions format', () => { it('applies OR conditions using .or()', () => { const query = createMockQuery() const criteria = { conditions: [{ groupId: 'g1', operator: 'OR', conditions: [ { field: 'email', operator: 'equals', value: 'a@b.com' } ] }] } QueryBuilder.buildFilters(query, criteria as any, 'org', 'proj') const allCalls = (query as any)._calls const orCall = allCalls.find((c: any) => c.method === 'or') expect(orCall).toBeDefined() }) it('applies AND conditions individually', () => { const query = createMockQuery() const criteria = { conditions: [{ groupId: 'g1', operator: 'AND', conditions: [ { field: 'is_subscribed', operator: 'eq', value: true } ] }] } QueryBuilder.buildFilters(query, criteria as any, 'org', 'proj') const allCalls = (query as any)._calls const eqCalls = allCalls.filter((c: any) => c.method === 'eq') expect(eqCalls.length).toBeGreaterThanOrEqual(2) }) }) })