SONUM v7 - Évolution v6 (éditeurs/blocs CRUD, tableau de bord stats) + vue liste alternance couleurs

This commit is contained in:
Manus Agent
2026-04-20 11:51:04 -04:00
commit 3bccb0a743
143 changed files with 30933 additions and 0 deletions

154
server/sonum.test.ts Normal file
View File

@@ -0,0 +1,154 @@
import { describe, expect, it, vi, beforeEach } from "vitest";
import { appRouter } from "./routers";
import type { TrpcContext } from "./_core/context";
// ─── Helpers ──────────────────────────────────────────────────────────────────
function makeUser(overrides: Partial<TrpcContext["user"]> = {}): NonNullable<TrpcContext["user"]> {
return {
id: 1,
openId: "user-1",
name: "Référent Test",
email: "referent@test.fr",
loginMethod: "manus",
role: "user",
sonumRole: "referent",
cguAccepted: true,
cguAcceptedAt: new Date(),
createdAt: new Date(),
updatedAt: new Date(),
lastSignedIn: new Date(),
...overrides,
};
}
function makeGestionnaire(): NonNullable<TrpcContext["user"]> {
return makeUser({ id: 2, openId: "gestionnaire-1", sonumRole: "gestionnaire", name: "Gestionnaire SONUM" });
}
function makeCtx(user: NonNullable<TrpcContext["user"]> | null = null): TrpcContext {
return {
user,
req: { protocol: "https", headers: {} } as TrpcContext["req"],
res: {
clearCookie: vi.fn(),
} as unknown as TrpcContext["res"],
};
}
// ─── Tests Auth ───────────────────────────────────────────────────────────────
describe("auth.logout", () => {
it("clears the session cookie and returns success", async () => {
const ctx = makeCtx(makeUser());
const caller = appRouter.createCaller(ctx);
const result = await caller.auth.logout();
expect(result).toEqual({ success: true });
});
it("works for unauthenticated users too (public procedure)", async () => {
const ctx = makeCtx(null);
const caller = appRouter.createCaller(ctx);
const result = await caller.auth.logout();
expect(result).toEqual({ success: true });
});
});
describe("auth.me", () => {
it("returns null for unauthenticated user", async () => {
const ctx = makeCtx(null);
const caller = appRouter.createCaller(ctx);
const result = await caller.auth.me();
expect(result).toBeNull();
});
it("returns user for authenticated user", async () => {
const user = makeUser();
const ctx = makeCtx(user);
const caller = appRouter.createCaller(ctx);
const result = await caller.auth.me();
expect(result?.id).toBe(1);
expect(result?.sonumRole).toBe("referent");
});
});
// ─── Tests CGU ────────────────────────────────────────────────────────────────
describe("cgu.status", () => {
it("throws UNAUTHORIZED for unauthenticated user", async () => {
const ctx = makeCtx(null);
const caller = appRouter.createCaller(ctx);
await expect(caller.cgu.status()).rejects.toThrow();
});
it("returns cgu status for authenticated user", async () => {
const user = makeUser({ cguAccepted: true });
const ctx = makeCtx(user);
const caller = appRouter.createCaller(ctx);
const result = await caller.cgu.status();
expect(result.accepted).toBe(true);
});
it("returns false when CGU not accepted", async () => {
const user = makeUser({ cguAccepted: false, cguAcceptedAt: undefined });
const ctx = makeCtx(user);
const caller = appRouter.createCaller(ctx);
const result = await caller.cgu.status();
expect(result.accepted).toBe(false);
});
});
// ─── Tests Gestion des rôles ──────────────────────────────────────────────────
describe("admin.users (gestionnaire only)", () => {
it("throws FORBIDDEN for a referent", async () => {
const ctx = makeCtx(makeUser());
const caller = appRouter.createCaller(ctx);
await expect(caller.admin.users()).rejects.toMatchObject({ code: "FORBIDDEN" });
});
it("throws UNAUTHORIZED for unauthenticated user", async () => {
const ctx = makeCtx(null);
const caller = appRouter.createCaller(ctx);
await expect(caller.admin.users()).rejects.toThrow();
});
});
describe("contact.toutesLesDemandes (gestionnaire only)", () => {
it("throws FORBIDDEN for a referent", async () => {
const ctx = makeCtx(makeUser());
const caller = appRouter.createCaller(ctx);
await expect(caller.contact.toutesLesDemandes()).rejects.toMatchObject({ code: "FORBIDDEN" });
});
});
// ─── Tests Référentiel ────────────────────────────────────────────────────────
describe("referentiel.editeurs", () => {
it("is accessible as a public procedure", async () => {
const ctx = makeCtx(null);
const caller = appRouter.createCaller(ctx);
// Should not throw (may return empty array if DB not available)
const result = await caller.referentiel.editeurs();
expect(Array.isArray(result)).toBe(true);
});
});
describe("referentiel.blocsFonctionnels", () => {
it("is accessible as a public procedure", async () => {
const ctx = makeCtx(null);
const caller = appRouter.createCaller(ctx);
const result = await caller.referentiel.blocsFonctionnels();
expect(Array.isArray(result)).toBe(true);
});
});
// ─── Tests Traçabilité ────────────────────────────────────────────────────────
describe("tracabilite.compteur", () => {
it("throws UNAUTHORIZED for unauthenticated user", async () => {
const ctx = makeCtx(null);
const caller = appRouter.createCaller(ctx);
await expect(caller.tracabilite.compteur({ etablissementId: 1 })).rejects.toThrow();
});
});