Files
iti-technology/apps/api/test/admin.test.ts
T
Lorenzo SanesiandClaude Opus 5.5 b83ce039fb
Deploy stack su Proxmox / deploy (push) Failing after 3s
Piattaforma ITI TECHNOLOGY: monorepo, Medusa, API, sito, area riservata e deploy
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
2026-10-20 09:30:48 +02:00

149 lines
7.7 KiB
TypeScript

import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"
import { eq } from "drizzle-orm"
import { contactRequests, installerProfiles, quoteRequests, quotes, showroomBookings, tickets, users } from "../src/db/schema/index.js"
import { buildTestApp, createUserAndLogin, TEST_PASSWORD, type TestApp, truncateAll } from "./helpers.js"
let t: TestApp
let admin: { user: { id: string }; headers: Record<string, string> }
beforeAll(async () => {
t = await buildTestApp()
})
afterAll(() => t.app.close())
beforeEach(async () => {
await truncateAll()
t.medusa.reset()
t.email.clear()
admin = await createUserAndLogin(t.app, "admin@iti.local", "admin")
})
const call = (method: "GET" | "POST", url: string, headers = admin.headers) =>
t.app.inject({ method, url, headers, ...(method === "POST" ? { payload: {} } : {}) })
const login = (email: string) => t.app.inject({ method: "POST", url: "/auth/login", payload: { email, password: TEST_PASSWORD } })
describe("accesso all'area admin", () => {
it("admin disabilitato o declassato: rifiutato subito, anche con un access token ancora valido", async () => {
expect((await call("GET", "/admin/summary")).statusCode).toBe(200)
await t.app.db.update(users).set({ role: "customer" }).where(eq(users.id, admin.user.id))
expect((await call("GET", "/admin/summary")).statusCode).toBe(403)
await t.app.db.update(users).set({ role: "admin", disabledAt: new Date() }).where(eq(users.id, admin.user.id))
expect((await call("GET", "/admin/summary")).statusCode).toBe(401)
})
})
describe("admin utenti", () => {
it("ricerca per testo, ruolo e stato; solo admin", async () => {
const mario = await createUserAndLogin(t.app, "mario@example.com", "customer", { firstName: "Mario", lastName: "Rossi" })
await createUserAndLogin(t.app, "acme@example.com", "company", { companyName: "Acme Srl", vatNumber: "01234567890" })
const inst = await createUserAndLogin(t.app, "inst@example.com", "installer")
await t.app.db.insert(installerProfiles).values({ userId: inst.user.id, businessName: "Impianti Srl" })
const all = await call("GET", "/admin/users")
expect(all.statusCode).toBe(200)
expect(all.json().total).toBe(4)
const item = all.json().items.find((u: { email: string }) => u.email === "inst@example.com")
expect(item).toMatchObject({ role: "installer", installerStatus: "pending", disabledAt: null })
expect(item).not.toHaveProperty("passwordHash")
expect(item).not.toHaveProperty("medusaCustomerId")
expect((await call("GET", "/admin/users?q=mario%20rossi")).json().items.map((u: { email: string }) => u.email)).toEqual(["mario@example.com"])
expect((await call("GET", "/admin/users?q=acme")).json().total).toBe(1)
expect((await call("GET", "/admin/users?role=company")).json().items[0].companyName).toBe("Acme Srl")
expect((await call("GET", "/admin/users?status=disabled")).json().total).toBe(0)
// % e _ sono letterali
expect((await call("GET", "/admin/users?q=%25")).json().total).toBe(0)
expect((await call("GET", `/admin/users/${mario.user.id}`)).json()).toMatchObject({ firstName: "Mario", installerStatus: null })
expect((await call("GET", "/admin/users/00000000-0000-4000-8000-000000000000")).statusCode).toBe(404)
expect((await call("GET", "/admin/users", mario.headers)).statusCode).toBe(403)
expect((await t.app.inject({ method: "GET", url: "/admin/users" })).statusCode).toBe(401)
})
it("disabilita: sessioni chiuse e login rifiutato; riabilita: login di nuovo possibile", async () => {
const mario = await createUserAndLogin(t.app, "mario@example.com")
const res = await call("POST", `/admin/users/${mario.user.id}/disable`)
expect(res.statusCode).toBe(200)
expect(res.json()).toMatchObject({ medusaSynced: null })
expect(res.json().disabledAt).not.toBeNull()
expect((await login("mario@example.com")).statusCode).toBe(401)
expect((await call("GET", "/me/tickets", mario.headers)).statusCode).toBe(401)
expect((await call("GET", "/admin/users?status=disabled")).json().total).toBe(1)
const enabled = await call("POST", `/admin/users/${mario.user.id}/enable`)
expect(enabled.json().disabledAt).toBeNull()
expect((await login("mario@example.com")).statusCode).toBe(200)
})
it("installatore disabilitato esce dal gruppo del listino riservato, riabilitato rientra", async () => {
const inst = await createUserAndLogin(t.app, "inst@example.com", "installer")
await t.app.db.insert(installerProfiles).values({ userId: inst.user.id, businessName: "Impianti Srl", status: "approved", approvedAt: new Date() })
expect((await call("POST", `/admin/installers/${inst.user.id}/sync`)).json()).toEqual({ member: true })
const customerId = t.medusa.accountByEmail("inst@example.com")!.id
const off = await call("POST", `/admin/users/${inst.user.id}/disable`)
expect(off.json()).toMatchObject({ medusaSynced: true, installerStatus: "approved" })
expect(t.medusa.installerGroup.has(customerId)).toBe(false)
const on = await call("POST", `/admin/users/${inst.user.id}/enable`)
expect(on.json()).toMatchObject({ medusaSynced: true })
expect(t.medusa.installerGroup.has(customerId)).toBe(true)
// Medusa giù: l'operazione riesce comunque
t.medusa.down = true
expect((await call("POST", `/admin/users/${inst.user.id}/disable`)).json()).toMatchObject({ medusaSynced: false })
})
it("un admin non può disabilitare se stesso", async () => {
const res = await call("POST", `/admin/users/${admin.user.id}/disable`)
expect(res.statusCode).toBe(409)
expect(res.json().error.code).toBe("SELF_DISABLE")
})
})
describe("admin dashboard", () => {
it("contatori", async () => {
const empty = await call("GET", "/admin/summary")
expect(empty.statusCode).toBe(200)
expect(Object.values(empty.json()).every((v) => v === 0)).toBe(true)
const mario = await createUserAndLogin(t.app, "mario@example.com")
const inst = await createUserAndLogin(t.app, "inst@example.com", "installer")
await t.app.db.insert(installerProfiles).values({ userId: inst.user.id, businessName: "Impianti Srl" })
const base = { email: "x@example.com", name: "X", configuratorInput: {} }
await t.app.db.insert(quoteRequests).values([base, base, { ...base, status: "closed" }])
await t.app.db.insert(tickets).values([
{ userId: mario.user.id, subject: "a", status: "open" },
{ userId: mario.user.id, subject: "b", status: "waiting_customer" },
{ userId: mario.user.id, subject: "c", status: "closed" },
])
const contact = { name: "X", email: "x@example.com", message: "Messaggio di prova" }
await t.app.db.insert(contactRequests).values([contact, { ...contact, status: "closed" }])
const day = 24 * 3600 * 1000
const booking = { name: "X", email: "x@example.com", people: 2 }
await t.app.db.insert(showroomBookings).values([
{ ...booking, requestedAt: new Date(Date.now() + 2 * day), status: "confirmed" },
{ ...booking, requestedAt: new Date(Date.now() + 3 * day), status: "cancelled" },
{ ...booking, requestedAt: new Date(Date.now() - 2 * day), status: "confirmed" },
])
await t.app.db.insert(quotes).values([
{ userId: mario.user.id, year: 2026, seq: 1, number: "2026-0001", title: "A", amountCents: 1, status: "sent" },
{ userId: mario.user.id, year: 2026, seq: 2, number: "2026-0002", title: "B", amountCents: 1, status: "sent", validUntil: "2020-01-01" },
])
expect((await call("GET", "/admin/summary")).json()).toEqual({
newQuoteRequests: 2,
openTickets: 1,
activeTickets: 2,
pendingInstallers: 1,
upcomingShowroomBookings: 1,
newContactRequests: 1,
sentQuotes: 1,
})
expect((await call("GET", "/admin/summary", mario.headers)).statusCode).toBe(403)
})
})