sistema-de-chamados/tests/machines.listTicketsHistory.test.ts

255 lines
7.8 KiB
TypeScript

import { describe, expect, it, vi } from "bun:test"
import type { Doc, Id } from "../convex/_generated/dataModel"
import { getTicketsHistoryStatsHandler, listTicketsHistoryHandler } from "../convex/machines"
const MACHINE_ID = "machine_1" as Id<"machines">
const TENANT_ID = "tenant-1"
function buildMachine(overrides: Partial<Doc<"machines">> = {}): Doc<"machines"> {
const machine: Record<string, unknown> = {
_id: MACHINE_ID,
tenantId: TENANT_ID,
hostname: "desktop-01",
macAddresses: [],
serialNumbers: [],
fingerprint: "fp",
isActive: true,
lastHeartbeatAt: Date.now(),
createdAt: Date.now() - 10_000,
updatedAt: Date.now() - 5_000,
linkedUserIds: [],
remoteAccess: null,
}
return { ...(machine as Doc<"machines">), ...overrides }
}
function buildTicket(overrides: Partial<Doc<"tickets">> = {}): Doc<"tickets"> {
const base: Record<string, unknown> = {
_id: "ticket_base" as Id<"tickets">,
tenantId: TENANT_ID,
reference: 42600,
subject: "Generic ticket",
summary: "",
status: "PENDING",
priority: "MEDIUM",
channel: "EMAIL",
queueId: undefined,
requesterId: "user_req" as Id<"users">,
requesterSnapshot: { name: "Alice", email: "alice@example.com", avatarUrl: undefined, teams: [] },
assigneeId: "user_assignee" as Id<"users">,
assigneeSnapshot: { name: "Bob", email: "bob@example.com", avatarUrl: undefined, teams: [] },
companyId: undefined,
companySnapshot: undefined,
machineId: MACHINE_ID,
machineSnapshot: undefined,
working: false,
dueAt: undefined,
firstResponseAt: undefined,
resolvedAt: undefined,
closedAt: undefined,
updatedAt: Date.now(),
createdAt: Date.now() - 2000,
tags: [],
customFields: [],
totalWorkedMs: 0,
internalWorkedMs: 0,
externalWorkedMs: 0,
activeSessionId: undefined,
}
return { ...(base as Doc<"tickets">), ...overrides }
}
type PaginateHandler = (options: { cursor: string | null; numItems: number }) => Promise<{
page: Doc<"tickets">[]
isDone: boolean
continueCursor: string
}>
function createCtx({
machine = buildMachine(),
queues = new Map<string, Doc<"queues">>(),
paginate,
}: {
machine?: Doc<"machines">
queues?: Map<string, Doc<"queues">>
paginate: PaginateHandler
}) {
const createFilterBuilder = () => {
const builder: Record<string, (..._args: unknown[]) => typeof builder> = {}
builder.eq = () => builder
builder.gte = () => builder
builder.lte = () => builder
builder.or = () => builder
return builder
}
return {
db: {
get: vi.fn(async (id: Id<"machines"> | Id<"queues">) => {
if (machine && id === machine._id) return machine
const queue = queues.get(String(id))
if (queue) return queue
return null
}),
query: vi.fn(() => {
const chain = {
filter: vi.fn((cb?: (builder: ReturnType<typeof createFilterBuilder>) => unknown) => {
cb?.(createFilterBuilder())
return chain
}),
paginate: vi.fn((options: { cursor: string | null; numItems: number }) => paginate(options)),
}
return {
withIndex: vi.fn((_indexName: string, cb?: (builder: ReturnType<typeof createFilterBuilder>) => unknown) => {
cb?.(createFilterBuilder())
return {
order: vi.fn(() => chain),
}
}),
}
}),
},
} as unknown as Parameters<typeof listTicketsHistoryHandler>[0]
}
describe("convex.machines.listTicketsHistory", () => {
it("maps tickets metadata and resolves queue names", async () => {
const machine = buildMachine()
const ticket = buildTicket({
_id: "ticket_1" as Id<"tickets">,
subject: "Printer offline",
priority: "HIGH",
status: "PENDING",
queueId: "queue_1" as Id<"queues">,
updatedAt: 170000,
createdAt: 160000,
})
const paginate = vi.fn(async () => ({
page: [ticket],
isDone: false,
continueCursor: "cursor-next",
}))
const queues = new Map<string, Doc<"queues">>([
["queue_1", { _id: "queue_1" as Id<"queues">, name: "Atendimento", tenantId: TENANT_ID } as Doc<"queues">],
])
const ctx = createCtx({ machine, queues, paginate })
const result = await listTicketsHistoryHandler(ctx, {
machineId: machine._id,
paginationOpts: { numItems: 25, cursor: null },
})
expect(paginate).toHaveBeenCalledWith({ numItems: 25, cursor: null })
expect(result.page).toHaveLength(1)
expect(result.page[0]).toMatchObject({
id: "ticket_1",
subject: "Printer offline",
priority: "HIGH",
status: "PENDING",
queue: "Atendimento",
})
expect(result.continueCursor).toBe("cursor-next")
})
it("applies search filtering over paginated results", async () => {
const machine = buildMachine()
const ticketMatches = buildTicket({
_id: "ticket_match" as Id<"tickets">,
reference: 44321,
subject: "Notebook com tela quebrada",
requesterSnapshot: { name: "Carla", email: "carla@example.com", avatarUrl: undefined, teams: [] },
})
const ticketIgnored = buildTicket({
_id: "ticket_other" as Id<"tickets">,
subject: "Troca de teclado",
requesterSnapshot: { name: "Roberto", email: "roberto@example.com", avatarUrl: undefined, teams: [] },
})
const paginate = vi.fn(async () => ({
page: [ticketMatches, ticketIgnored],
isDone: true,
continueCursor: "",
}))
const ctx = createCtx({ machine, paginate })
const result = await listTicketsHistoryHandler(ctx, {
machineId: machine._id,
search: "notebook",
paginationOpts: { numItems: 50, cursor: null },
})
expect(result.page).toHaveLength(1)
expect(result.page[0].id).toBe("ticket_match")
expect(result.isDone).toBe(true)
})
})
describe("convex.machines.getTicketsHistoryStats", () => {
it("aggregates totals across multiple pages respecting open status", async () => {
const machine = buildMachine()
const firstPageTicket = buildTicket({
_id: "ticket_open" as Id<"tickets">,
status: "AWAITING_ATTENDANCE",
})
const secondPageTicket = buildTicket({
_id: "ticket_resolved" as Id<"tickets">,
status: "RESOLVED",
})
const paginate = vi.fn(async ({ cursor }: { cursor: string | null }) => {
if (!cursor) {
return { page: [firstPageTicket], isDone: false, continueCursor: "cursor-1" }
}
return { page: [secondPageTicket], isDone: true, continueCursor: "" }
})
const ctx = createCtx({ machine, paginate })
const stats = await getTicketsHistoryStatsHandler(
ctx as unknown as Parameters<typeof getTicketsHistoryStatsHandler>[0],
{
machineId: machine._id,
}
)
expect(stats).toEqual({ total: 2, openCount: 1, resolvedCount: 1 })
expect(paginate).toHaveBeenCalledTimes(2)
})
it("filters results by search term when aggregating", async () => {
const machine = buildMachine()
const matchingTicket = buildTicket({
_id: "ticket_search" as Id<"tickets">,
subject: "Notebook com lentidão",
})
const nonMatchingTicket = buildTicket({
_id: "ticket_ignored" as Id<"tickets">,
subject: "Impressora parada",
})
const paginate = vi.fn(async ({ cursor }: { cursor: string | null }) => {
if (!cursor) {
return { page: [matchingTicket, nonMatchingTicket], isDone: true, continueCursor: "" }
}
return { page: [], isDone: true, continueCursor: "" }
})
const ctx = createCtx({ machine, paginate })
const stats = await getTicketsHistoryStatsHandler(
ctx as unknown as Parameters<typeof getTicketsHistoryStatsHandler>[0],
{
machineId: machine._id,
search: "notebook",
}
)
expect(stats).toEqual({ total: 1, openCount: 1, resolvedCount: 0 })
})
})