feat: automações de tickets e testes de regressão
This commit is contained in:
parent
9f1a6a7401
commit
8ab510bfe9
18 changed files with 2221 additions and 20 deletions
202
tests/automations-engine.test.ts
Normal file
202
tests/automations-engine.test.ts
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
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> = {}): 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" })
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue