import { describe, expect, it, vi } from "bun:test" import { runTicketAutomationsForEvent } from "../convex/automations" import type { AutomationConditionGroup, TicketForAutomation } from "../convex/automationsEngine" import { evaluateAutomationConditions } from "../convex/automationsEngine" function buildTicket(overrides: Partial = {}): TicketForAutomation { const base: TicketForAutomation = { tenantId: "tenant-1", status: "AWAITING_ATTENDANCE", priority: "MEDIUM", channel: "EMAIL", queueId: "queue-1" as never, companyId: "company-1" as never, categoryId: "cat-1" as never, subcategoryId: "subcat-1" as never, tags: ["vip", "windows"], formTemplate: "DEFAULT", chatEnabled: true, } return { ...base, ...overrides } } describe("automationsEngine.evaluateAutomationConditions", () => { it("retorna true quando não há condições", () => { const ticket = buildTicket() expect(evaluateAutomationConditions(ticket, null)).toBe(true) expect(evaluateAutomationConditions(ticket, undefined)).toBe(true) }) it("aplica AND: todas devem bater", () => { const ticket = buildTicket({ priority: "HIGH" }) const group: AutomationConditionGroup = { op: "AND", conditions: [ { field: "priority", op: "eq", value: "HIGH" }, { field: "status", op: "eq", value: "AWAITING_ATTENDANCE" }, ], } expect(evaluateAutomationConditions(ticket, group)).toBe(true) }) it("aplica OR: basta uma bater", () => { const ticket = buildTicket({ priority: "LOW" }) const group: AutomationConditionGroup = { op: "OR", conditions: [ { field: "priority", op: "eq", value: "HIGH" }, { field: "priority", op: "eq", value: "LOW" }, ], } expect(evaluateAutomationConditions(ticket, group)).toBe(true) }) it("suporta comparadores eq/neq/in/not_in para campos simples", () => { const ticket = buildTicket() expect( evaluateAutomationConditions(ticket, { op: "AND", conditions: [{ field: "companyId", op: "eq", value: "company-1" }], }) ).toBe(true) expect( evaluateAutomationConditions(ticket, { op: "AND", conditions: [{ field: "queueId", op: "neq", value: "queue-2" }], }) ).toBe(true) expect( evaluateAutomationConditions(ticket, { op: "AND", conditions: [{ field: "priority", op: "in", value: ["LOW", "MEDIUM"] }], }) ).toBe(true) expect( evaluateAutomationConditions(ticket, { op: "AND", conditions: [{ field: "status", op: "not_in", value: ["RESOLVED", "PAUSED"] }], }) ).toBe(true) }) it("suporta chatEnabled (is_true/is_false/eq/neq)", () => { const ticket = buildTicket({ chatEnabled: true }) expect( evaluateAutomationConditions(ticket, { op: "AND", conditions: [{ field: "chatEnabled", op: "is_true" }], }) ).toBe(true) expect( evaluateAutomationConditions(ticket, { op: "AND", conditions: [{ field: "chatEnabled", op: "is_false" }], }) ).toBe(false) expect( evaluateAutomationConditions(ticket, { op: "AND", conditions: [{ field: "chatEnabled", op: "eq", value: true }], }) ).toBe(true) expect( evaluateAutomationConditions(ticket, { op: "AND", conditions: [{ field: "chatEnabled", op: "neq", value: false }], }) ).toBe(true) }) it("suporta tag (contains/not_contains/eq/neq)", () => { const ticket = buildTicket({ tags: ["vip", "linux"] }) expect( evaluateAutomationConditions(ticket, { op: "AND", conditions: [{ field: "tag", op: "contains", value: "vip" }], }) ).toBe(true) expect( evaluateAutomationConditions(ticket, { op: "AND", conditions: [{ field: "tag", op: "not_contains", value: "windows" }], }) ).toBe(true) expect( evaluateAutomationConditions(ticket, { op: "AND", conditions: [{ field: "tag", op: "eq", value: "linux" }], }) ).toBe(true) expect( evaluateAutomationConditions(ticket, { op: "AND", conditions: [{ field: "tag", op: "neq", value: "vip" }], }) ).toBe(false) }) }) describe("automations.runTicketAutomationsForEvent", () => { it("agenda automação DELAYED via scheduler.runAfter", async () => { const runAfter = vi.fn(async () => {}) const ticketId = "ticket_1" as never const automationId = "auto_1" as never const ticket = buildTicket({ tenantId: "tenant-1" }) as unknown as { _id: unknown; tenantId: string } const automations = [ { _id: automationId, tenantId: "tenant-1", name: "Auto", enabled: true, trigger: "TICKET_CREATED", timing: "DELAYED", delayMs: 60_000, conditions: null, actions: [{ type: "SET_CHAT_ENABLED", enabled: true }], createdBy: "user_1", createdAt: 0, updatedAt: 0, }, ] const take = vi.fn(async () => automations) const filter = vi.fn(() => ({ take })) const withIndex = vi.fn(() => ({ filter, take })) const query = vi.fn(() => ({ withIndex })) const get = vi.fn(async (id: unknown) => { if (id === ticketId) return { ...ticket, _id: ticketId, tenantId: "tenant-1" } return null }) await runTicketAutomationsForEvent( { db: { get, query } as unknown, scheduler: { runAfter } as unknown, } as never, { tenantId: "tenant-1", ticketId, eventType: "TICKET_CREATED" } ) expect(runAfter).toHaveBeenCalledTimes(1) expect(runAfter).toHaveBeenCalledWith( 60_000, expect.anything(), expect.objectContaining({ automationId, ticketId, eventType: "TICKET_CREATED" }) ) }) })