import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest" import { buildTestApp, cookieFrom, createUserAndLogin, TEST_PASSWORD, type TestApp, truncateAll } from "./helpers.js" let t: TestApp beforeAll(async () => { t = await buildTestApp() }) afterAll(async () => { await t.app.close() }) beforeEach(async () => { await truncateAll() t.medusa.reset() }) describe("PATCH /me", () => { it("aggiorna nome e cognome, senza toccare il resto", async () => { const { headers } = await createUserAndLogin(t.app, "mario@example.com") const res = await t.app.inject({ method: "PATCH", url: "/me", headers, payload: { firstName: " Mario ", lastName: "Bianchi" } }) expect(res.statusCode, res.body).toBe(200) expect(res.json()).toMatchObject({ firstName: "Mario", lastName: "Bianchi", email: "mario@example.com", role: "customer" }) expect((await t.app.inject({ method: "GET", url: "/me", headers })).json().lastName).toBe("Bianchi") }) it("un privato non ha dati aziendali; email e ruolo non si cambiano", async () => { const { headers } = await createUserAndLogin(t.app, "mario@example.com") const res = await t.app.inject({ method: "PATCH", url: "/me", headers, payload: { companyName: "Mario Srl" } }) expect(res.statusCode).toBe(400) expect(res.json().error.details[0].path).toBe("body.companyName") const sneaky = await t.app.inject({ method: "PATCH", url: "/me", headers, payload: { role: "admin", email: "x@example.com", firstName: "M" } }) expect(sneaky.statusCode).toBe(200) expect(sneaky.json()).toMatchObject({ role: "customer", email: "mario@example.com" }) }) it("azienda: ragione sociale e P.IVA modificabili ma non svuotabili", async () => { const { headers } = await createUserAndLogin(t.app, "srl@example.com", "company", { companyName: "Vecchia Srl", vatNumber: "12345678903" }) const ok = await t.app.inject({ method: "PATCH", url: "/me", headers, payload: { companyName: "Nuova Srl" } }) expect(ok.json()).toMatchObject({ companyName: "Nuova Srl", vatNumber: "12345678903" }) const empty = await t.app.inject({ method: "PATCH", url: "/me", headers, payload: { vatNumber: "" } }) expect(empty.statusCode).toBe(400) expect(empty.json().error.details[0].path).toBe("body.vatNumber") }) it("installatore: P.IVA facoltativa, ragione sociale obbligatoria", async () => { const { headers } = await createUserAndLogin(t.app, "inst@example.com", "installer", { companyName: "Impianti Srl", vatNumber: "12345678903" }) expect((await t.app.inject({ method: "PATCH", url: "/me", headers, payload: { vatNumber: null } })).json().vatNumber).toBeNull() expect((await t.app.inject({ method: "PATCH", url: "/me", headers, payload: { companyName: " " } })).statusCode).toBe(400) }) it("serve l'autenticazione", async () => { expect((await t.app.inject({ method: "PATCH", url: "/me", payload: { firstName: "X" } })).statusCode).toBe(401) }) }) describe("POST /auth/change-password", () => { async function loginCookie(email: string, password = TEST_PASSWORD) { const res = await t.app.inject({ method: "POST", url: "/auth/login", payload: { email, password } }) return { status: res.statusCode, cookie: cookieFrom(res.headers["set-cookie"], "iti_rt") } } it("con la password attuale: nuova sessione qui, le altre chiuse", async () => { const { headers } = await createUserAndLogin(t.app, "mario@example.com") const other = await loginCookie("mario@example.com") const res = await t.app.inject({ method: "POST", url: "/auth/change-password", headers, payload: { currentPassword: TEST_PASSWORD, newPassword: "Nuova-password-123" }, }) expect(res.statusCode, res.body).toBe(200) expect(res.json()).toMatchObject({ accessToken: expect.any(String), user: { email: "mario@example.com" } }) const fresh = cookieFrom(res.headers["set-cookie"], "iti_rt") expect(fresh).toBeTruthy() // l'altro dispositivo perde la sessione, questo no const otherRefresh = await t.app.inject({ method: "POST", url: "/auth/refresh", cookies: { iti_rt: other.cookie! } }) expect(otherRefresh.statusCode).toBe(401) const thisRefresh = await t.app.inject({ method: "POST", url: "/auth/refresh", cookies: { iti_rt: fresh! } }) expect(thisRefresh.statusCode).toBe(200) expect((await loginCookie("mario@example.com")).status).toBe(401) expect((await loginCookie("mario@example.com", "Nuova-password-123")).status).toBe(200) }) it("password attuale errata → 400 sul campo, nulla cambia", async () => { const { headers } = await createUserAndLogin(t.app, "mario@example.com") const res = await t.app.inject({ method: "POST", url: "/auth/change-password", headers, payload: { currentPassword: "sbagliata-123", newPassword: "Nuova-password-123" }, }) expect(res.statusCode).toBe(400) expect(res.json().error).toMatchObject({ code: "INVALID_CURRENT_PASSWORD", details: [{ path: "body.currentPassword" }] }) expect((await loginCookie("mario@example.com")).status).toBe(200) }) it("nuova password troppo corta o uguale all'attuale → 400", async () => { const { headers } = await createUserAndLogin(t.app, "mario@example.com") for (const newPassword of ["corta", TEST_PASSWORD]) { const res = await t.app.inject({ method: "POST", url: "/auth/change-password", headers, payload: { currentPassword: TEST_PASSWORD, newPassword } }) expect(res.statusCode).toBe(400) } }) }) describe("GET /me/orders/:number", () => { const summary = (id: string, displayId: number) => ({ id, display_id: displayId, status: "pending", created_at: "2026-10-01T10:00:00.000Z", payment_status: "captured", fulfillment_status: "shipped", item_total: 1190, shipping_total: 15, tax_total: 217.29, total: 1205, items: [], }) it("dettaglio del proprio ordine con indirizzo, spedizione e tracking; 404 per gli altri", async () => { const mario = await createUserAndLogin(t.app, "mario@example.com") const luigi = await createUserAndLogin(t.app, "luigi@example.com") await t.app.inject({ method: "GET", url: "/me/orders", headers: mario.headers }) const customerId = t.medusa.accountByEmail("mario@example.com")!.id t.medusa.customerOrderList.set(customerId, [summary("order_1", 12)]) t.medusa.orderDetails.set("order_1", { ...summary("order_1", 12), email: "mario@example.com", customer_id: customerId, currency_code: "eur", discount_total: 0, items: [ { title: "Standard", product_title: "Addolcitore", variant_title: "Standard", variant_sku: "ADD-20", thumbnail: null, quantity: 1, unit_price: 1190, total: 1190 }, ], shipping_address: { first_name: "Mario", last_name: "Rossi", address_1: "Via Roma 1", city: "Milano", postal_code: "20100", province: "MI" }, shipping_methods: [{ name: "Spedizione standard" }], fulfillments: [{ id: "ful_1", labels: [{ tracking_number: "BRT123", tracking_url: "https://track.example.com/BRT123" }] }], }) const res = await t.app.inject({ method: "GET", url: "/me/orders/12", headers: mario.headers }) expect(res.statusCode, res.body).toBe(200) expect(res.json()).toMatchObject({ number: 12, fulfillmentStatus: "shipped", totals: { total: 120500, shipping: 1500, discount: 0 }, items: [{ title: "Addolcitore - Standard", sku: "ADD-20", unitPrice: 119000 }], shippingAddress: ["Mario Rossi", "Via Roma 1", "20100 Milano (MI)"], shippingMethods: ["Spedizione standard"], tracking: [{ number: "BRT123", url: "https://track.example.com/BRT123" }], }) expect(res.body).not.toContain("order_1") expect(res.body).not.toContain(customerId) expect((await t.app.inject({ method: "GET", url: "/me/orders/12", headers: luigi.headers })).statusCode).toBe(404) expect((await t.app.inject({ method: "GET", url: "/me/orders/99", headers: mario.headers })).statusCode).toBe(404) expect((await t.app.inject({ method: "GET", url: "/me/orders/abc", headers: mario.headers })).statusCode).toBe(400) }) it("Medusa giù → 503", async () => { const mario = await createUserAndLogin(t.app, "mario@example.com") t.medusa.down = true expect((await t.app.inject({ method: "GET", url: "/me/orders/1", headers: mario.headers })).statusCode).toBe(503) }) })