Piattaforma ITI TECHNOLOGY: monorepo, Medusa, API, sito, area riservata e deploy
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:
Lorenzo Sanesi
2026-10-20 09:30:48 +02:00
co-authored by Claude Opus 5.5
commit b83ce039fb
505 changed files with 84544 additions and 0 deletions
+167
View File
@@ -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)
})
})