import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest" import { buildTestApp, createUserAndLogin, type TestApp, truncateAll } from "./helpers.js" let t: TestApp let admin: Record let mario: Record let luigi: Record const call = (method: "GET" | "POST" | "PATCH", url: string, headers: Record, payload?: object) => t.app.inject({ method, url, headers, ...(payload ? { payload } : {}) }) const openTicket = async (headers = mario) => { const res = await call("POST", "/me/tickets", headers, { subject: "Addolcitore in allarme", body: "Lampeggia la spia rossa" }) expect(res.statusCode, res.body).toBe(201) return res.json() } beforeAll(async () => { t = await buildTestApp() }) afterAll(() => t.app.close()) beforeEach(async () => { await truncateAll() t.email.clear() admin = (await createUserAndLogin(t.app, "admin@iti.local", "admin")).headers mario = (await createUserAndLogin(t.app, "mario@example.com")).headers luigi = (await createUserAndLogin(t.app, "luigi@example.com")).headers }) describe("ticket", () => { it("apertura con primo messaggio e notifica allo staff", async () => { const tk = await openTicket() expect(tk).toMatchObject({ subject: "Addolcitore in allarme", status: "open", priority: "normal" }) expect(tk.messages).toHaveLength(1) expect(tk.messages[0]).toMatchObject({ body: "Lampeggia la spia rossa", fromStaff: false }) expect(t.email.last("staff-notification")?.to).toBe("admin@iti.local") // priorità urgent riservata all'assistenza expect((await call("POST", "/me/tickets", mario, { subject: "Urgente!", body: "x", priority: "urgent" })).statusCode).toBe(400) }) it("un cliente non può leggere né scrivere nei ticket di un altro (404)", async () => { const tk = await openTicket() expect((await call("GET", `/me/tickets/${tk.id}`, luigi)).statusCode).toBe(404) expect((await call("POST", `/me/tickets/${tk.id}/messages`, luigi, { body: "intrusione" })).statusCode).toBe(404) expect((await call("GET", "/me/tickets", luigi)).json().total).toBe(0) expect((await call("GET", "/me/tickets", mario)).json().total).toBe(1) expect((await call("GET", "/admin/tickets", mario)).statusCode).toBe(403) }) it("risposta admin: email al cliente, stato in attesa del cliente; la risposta del cliente riapre", async () => { const tk = await openTicket() t.email.clear() const reply = await call("POST", `/admin/tickets/${tk.id}/messages`, admin, { body: "Il tecnico passa giovedì" }) expect(reply.statusCode).toBe(201) expect(reply.json()).toMatchObject({ status: "waiting_customer", customer: { email: "mario@example.com" } }) expect(reply.json().messages.map((m: { fromStaff: boolean }) => m.fromStaff)).toEqual([false, true]) const mail = t.email.last("ticket-reply") expect(mail?.to).toBe("mario@example.com") expect(mail?.text).toContain("Il tecnico passa giovedì") expect(mail?.text).toContain(`/assistenza/${tk.id}`) const back = await call("POST", `/me/tickets/${tk.id}/messages`, mario, { body: "Va bene, grazie" }) expect(back.json().status).toBe("open") expect(back.json().messages).toHaveLength(3) }) it("admin: elenco con filtri, cambio stato e priorità; chiuso e riaperto dal cliente", async () => { const tk = await openTicket() await openTicket(luigi) expect((await call("GET", "/admin/tickets?q=luigi", admin)).json().total).toBe(1) const patched = await call("PATCH", `/admin/tickets/${tk.id}`, admin, { priority: "urgent" }) expect(patched.json()).toMatchObject({ priority: "urgent", status: "open" }) await call("PATCH", `/admin/tickets/${tk.id}`, admin, { status: "closed" }) expect((await call("GET", "/admin/tickets?status=closed", admin)).json().items.map((x: { id: string }) => x.id)).toEqual([tk.id]) const reopened = await call("POST", `/me/tickets/${tk.id}/messages`, mario, { body: "Il problema si è ripresentato" }) expect(reopened.json().status).toBe("open") }) })