176 lines
7.0 KiB
TypeScript
176 lines
7.0 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { randomUUID } from "node:crypto";
|
|
|
|
import { InventoryResponseSchema } from "@metis/assets/common";
|
|
import {
|
|
CompanyResponseSchema,
|
|
type TicketDTO,
|
|
type TicketInput,
|
|
TicketListSchema,
|
|
TicketLookupsSchema,
|
|
TicketResponseSchema,
|
|
} from "@metis/companies/common";
|
|
import { z } from "zod/v4";
|
|
|
|
type Request = (path: string, body?: unknown, origin?: string, token?: string) => Promise<Response>;
|
|
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);
|
|
}
|