import assert from "node:assert/strict"; import { randomUUID } from "node:crypto"; import { InventoryResponseSchema } from "@metis/assets/common"; import type { TicketDTO, TicketInput } from "@metis/companies/common"; import { CompanyResponseSchema, TicketListSchema, TicketLookupsSchema, TicketResponseSchema, } from "@metis/companies/common"; import { z } from "zod/v4"; type Request = (path: string, body?: unknown, origin?: string, token?: string) => Promise; export async function verifyTickets(request: Request, companyId: string, otherId: string) { const path = `/tickets/${companyId}`; const key = randomUUID(); const lookupsResponse = await request(`${path}/lookups`); assert.equal(lookupsResponse.status, 200); const lookups = TicketLookupsSchema.parse(await lookupsResponse.json()); const list = TicketListSchema.parse(await (await request("/tickets")).json()); const person = lookups.people.find((item) => item.active); const asset = lookups.assets.find((item) => item.active); const technician = list.technicians.find((item) => item.active); assert.ok(person && asset && technician); const values: TicketInput = { subject: "Ticket de prueba", description: "Correo no recibe mensajes", requesterId: person.id, assigneeId: technician.id, state: "open", priority: "normal", channel: "phone", affected: [ { kind: "asset", targetId: asset.id }, { kind: "person", targetId: person.id }, ], }; const body = { values, requestKey: key, expectedRevisionId: null }; async function error(url: string, payload: unknown, status: number, code: string) { const response = await request(url, payload); assert.equal(response.status, status, await response.clone().text()); assert.equal(z.object({ code: z.string() }).parse(await response.json()).code, code); } const createdResponse = await request(path, body); assert.equal(createdResponse.status, 201, await createdResponse.clone().text()); const created = TicketResponseSchema.parse(await createdResponse.json()).ticket; assert.equal(created.id[14], "7"); assert.match(created.reference, /^T-[0-9]{6,}$/); assert.equal(created.history.length, 1); assert.equal(created.requesterName, person.name); assert.equal(created.assigneeName, technician.name); const repeatedResponse = await request(path, body); assert.equal(repeatedResponse.status, 201); assert.equal(TicketResponseSchema.parse(await repeatedResponse.json()).ticket.id, created.id); await error(path, { ...body, values: { ...values, subject: "Otro" } }, 409, "REQUEST_KEY_REUSED"); await error(`/tickets/${otherId}`, body, 409, "REQUEST_KEY_REUSED"); const otherPerson = TicketLookupsSchema.parse( await (await request(`/tickets/${otherId}/lookups`)).json() ).people[0]; if (otherPerson) { await error( path, { ...body, requestKey: randomUUID(), values: { ...values, requesterId: otherPerson.id } }, 409, "REQUESTER_UNAVAILABLE" ); } await error( `/tickets/${otherId}/${created.id}/update`, { ...body, expectedRevisionId: created.history[0]?.id }, 404, "TICKET_NOT_FOUND" ); await error( path, { ...body, requestKey: randomUUID(), values: { ...values, affected: [{ kind: "resource", targetId: asset.id }] }, }, 409, "TARGET_UNAVAILABLE" ); await error( path, { ...body, requestKey: randomUUID(), values: { ...values, assigneeId: randomUUID() } }, 409, "ASSIGNEE_UNAVAILABLE" ); await error(path, { ...body, values: { ...values, subject: "" } }, 400, "INVALID_REQUEST"); assert.equal((await request(path, body, "http://evil.example")).status, 403); assert.equal((await request(path, body, undefined, "invalid")).status, 403); assert.equal((await request("/tickets?unknown=1")).status, 400); async function update(ticket: TicketDTO, next: TicketInput) { const response = await request(`${path}/${ticket.id}/update`, { values: next, requestKey: randomUUID(), expectedRevisionId: ticket.history.at(-1)?.id, }); assert.equal(response.status, 200, await response.clone().text()); return TicketResponseSchema.parse(await response.json()).ticket; } const same = await update(created, values); assert.equal(same.history.length, 1); const updated = await update(same, { ...values, state: "in_progress", priority: "high" }); assert.equal(updated.history.length, 2); assert.equal(updated.reference, created.reference); assert.equal(updated.history[0]?.values.state, "open"); await error( `${path}/${created.id}/update`, { ...body, expectedRevisionId: created.history[0]?.id }, 409, "TICKET_CHANGED" ); const concurrent = await Promise.all( ["resolved", "waiting"].map((state) => request(`${path}/${created.id}/update`, { ...body, requestKey: randomUUID(), expectedRevisionId: updated.history.at(-1)?.id, values: { ...updated.values, state }, }) ) ); assert.deepEqual(concurrent.map((response) => response.status).sort(), [200, 409]); const other = await request(`/tickets/${otherId}`, { values: { ...values, requesterId: null, assigneeId: null, affected: [] }, requestKey: randomUUID(), expectedRevisionId: null, }); assert.equal(other.status, 201); const otherTicket = TicketResponseSchema.parse(await other.json()).ticket; assert.notEqual(otherTicket.reference, created.reference); const global = TicketListSchema.parse(await (await request("/tickets")).json()); assert.ok(global.tickets.some((item) => item.id === created.id)); assert.ok(global.tickets.some((item) => item.id === otherTicket.id)); const scoped = TicketListSchema.parse(await (await request(path)).json()); assert.ok(scoped.tickets.every((item) => item.companyId === companyId)); const originalPerson = CompanyResponseSchema.parse( await (await request(`/companies/${companyId}`)).json() ).company.people.find((item) => item.id === person.id); assert.ok(originalPerson); const personValues = { name: originalPerson.name, position: originalPerson.position, email: originalPerson.email, phone: originalPerson.phone, active: originalPerson.active, isContact: originalPerson.isContact, }; const disabledPerson = await request(`/companies/${companyId}/people/${person.id}/update`, { ...personValues, active: false, }); assert.equal(disabledPerson.status, 200); CompanyResponseSchema.parse(await disabledPerson.json()); const current = scoped.tickets.find((item) => item.id === created.id); assert.ok(current); const retained = await update(current, { ...current.values, description: "Referencia histórica retenida", }); assert.equal(retained.values.requesterId, person.id); await error(path, { ...body, requestKey: randomUUID() }, 409, "REQUESTER_UNAVAILABLE"); assert.equal( (await request(`/companies/${companyId}/people/${person.id}/update`, personValues)).status, 200 ); const host = InventoryResponseSchema.parse( await (await request(`/assets/${companyId}`)).json() ).assets.find((item) => item.id === asset.id); assert.ok(host); }