import { ClickHouseEventQueryExecutor, TriggerEventRef } from '../executors/ClickHouseEventQueryExecutor' /** * Regressão do bug "split com event_property avalia histórico inteiro". * * Contexto: em journeys, um split/condition com event_property deve avaliar APENAS * o evento que disparou o fluxo (triggerEvent), não todo o histórico do contato. * O narrow já existia no path Supabase do V2AudienceEngine, mas o path ClickHouse * (primário em produção) ignorava o triggerEvent — fazendo a condição retornar * "true" sempre que o contato tivesse o evento alguma vez na vida. * * Estes testes garantem que: * - COM triggerEvent (event_name casando) → a query restringe por `id`. * - SEM triggerEvent → a query NÃO restringe por id/timestamp (varre histórico, * comportamento correto para audiências/segmentos como RFM e live audiences). * - triggerEvent de OUTRO evento (event_name não casa) → não restringe. */ const CONTACT_ID = 'contact-uuid-1' const ORG = 'org-1' const PROJ = 'proj-1' const TZ = 'UTC' function makeExecutor() { const calls: Array<{ query: string; query_params: Record }> = [] const client = { query: jest.fn(async ({ query, query_params }: any) => { calls.push({ query, query_params }) // Simula o contato tendo o evento no histórico (1 linha). return { json: async () => [{ contact_id: CONTACT_ID, reachy_id: '' }], } }), } const executor = new ClickHouseEventQueryExecutor(client as any) return { executor, client, calls } } const baseMaps = () => ({ reachyIdToContactId: new Map(), contactIdToReachyId: new Map(), allContactIds: new Set([CONTACT_ID]), }) // Regra de evento simples (DID 'button_clicked' com atributo button_id) — cai no // path _executeFetchAndProcess, que monta WHERE via _baseWhere. const eventRule = { kind: 'event', eventName: 'button_clicked', attributes: [{ key: 'custom_data.button_id', op: 'equals', value: 'cta_hero' }], } const criteria = { type: 'past-behavior', groups: [{ operator: 'AND', rules: [eventRule] }] } describe('ClickHouseEventQueryExecutor — triggerEvent narrow', () => { it('COM triggerEvent (event_name casa): restringe a query por id do evento de gatilho', async () => { const { executor, calls } = makeExecutor() const maps = baseMaps() const triggerEvent: TriggerEventRef = { event_id: 'evt-current-123', event_name: 'button_clicked', event_timestamp: '2026-05-28T12:00:00.000Z', } await executor.execute( eventRule, criteria, ORG, PROJ, TZ, maps.reachyIdToContactId, maps.contactIdToReachyId, maps.allContactIds, triggerEvent ) expect(calls.length).toBeGreaterThan(0) const last = calls[calls.length - 1] expect(last.query).toContain('id = {p_trigger_event_id:String}') expect(last.query_params.p_trigger_event_id).toBe('evt-current-123') }) it('SEM triggerEvent: NÃO restringe por id (varre histórico — comportamento de audiência)', async () => { const { executor, calls } = makeExecutor() const maps = baseMaps() await executor.execute( eventRule, criteria, ORG, PROJ, TZ, maps.reachyIdToContactId, maps.contactIdToReachyId, maps.allContactIds // sem triggerEvent ) expect(calls.length).toBeGreaterThan(0) const last = calls[calls.length - 1] expect(last.query).not.toContain('p_trigger_event_id') expect(last.query).not.toContain('p_trigger_event_ts') }) it('triggerEvent de OUTRO evento (event_name não casa): NÃO restringe', async () => { const { executor, calls } = makeExecutor() const maps = baseMaps() const triggerEvent: TriggerEventRef = { event_id: 'evt-other-999', event_name: 'page_view', // diferente de button_clicked } await executor.execute( eventRule, criteria, ORG, PROJ, TZ, maps.reachyIdToContactId, maps.contactIdToReachyId, maps.allContactIds, triggerEvent ) const last = calls[calls.length - 1] expect(last.query).not.toContain('p_trigger_event_id') }) it('triggerEvent sem event_id mas com timestamp: restringe por event_timestamp', async () => { const { executor, calls } = makeExecutor() const maps = baseMaps() const triggerEvent: TriggerEventRef = { event_name: 'button_clicked', event_timestamp: '2026-05-28T12:00:00.000Z', } await executor.execute( eventRule, criteria, ORG, PROJ, TZ, maps.reachyIdToContactId, maps.contactIdToReachyId, maps.allContactIds, triggerEvent ) const last = calls[calls.length - 1] expect(last.query).toContain('event_timestamp = {p_trigger_event_ts:DateTime64(3)}') expect(last.query_params.p_trigger_event_ts).toBeDefined() }) })