chore: expand reports coverage and upgrade next
This commit is contained in:
parent
2fb587b01d
commit
8b82284e8c
21 changed files with 2952 additions and 2713 deletions
255
tests/machines.listTicketsHistory.test.ts
Normal file
255
tests/machines.listTicketsHistory.test.ts
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
import { describe, expect, it, vi } from "vitest"
|
||||
|
||||
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 })
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue