Piattaforma ITI TECHNOLOGY: monorepo, Medusa, API, sito, area riservata e deploy
Deploy stack su Proxmox / deploy (push) Failing after 3s
Deploy stack su Proxmox / deploy (push) Failing after 3s
Monorepo npm con apps/public (Angular SSR), apps/private (Angular SPA), apps/api (Fastify), packages/shared e packages/styles; Medusa 2.21 in medusa/; infrastruttura Docker (Postgres PostGIS+pgvector, Redis, Caddy, backup) e workflow Gitea Actions per il deploy via SSH a ogni push su master. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jQyT6dR9czzGwqb7FkG6u
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user