Files
veille-reglementaire/server/veille.test.ts
manus-admin 716431ede3
All checks were successful
Deploy Production / deploy (push) Has been skipped
Deploy Recette / deploy (push) Successful in 1m30s
Validation applicative / TypeScript, tests et build (push) Successful in 1m52s
feat: purger les contenus expirés ou anciens
2026-10-09 15:46:52 +02:00

228 lines
8.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it, vi, beforeEach } from "vitest";
// Les tests de contrat tRPC ne doivent jamais dépendre d'une base ou d'une migration
// disponible dans l'environnement CI. Les quatre lectures publiques sont donc isolées.
vi.mock("./db", async () => {
const actual = await vi.importActual<typeof import("./db")>("./db");
return {
...actual,
getVeilleItems: vi.fn(async () => ({ items: [], total: 0 })),
getVeilleDistinctValues: vi.fn(async () => ({ categories: [], niveaux: [], territoires: [] })),
getAapItems: vi.fn(async () => ({ items: [], total: 0 })),
getAapDistinctValues: vi.fn(async () => ({ regions: [], departements: [] })),
purgeVeilleItems: vi.fn(async () => 12),
purgeVeilleItemsOlderThan: vi.fn(async () => 4),
purgeAapItems: vi.fn(async () => 9),
purgeExpiredAapItems: vi.fn(async () => 3),
purgeAapItemsOlderThan: vi.fn(async () => 2),
};
});
import { appRouter } from "./routers";
import type { TrpcContext } from "./_core/context";
import {
purgeAapItems,
purgeAapItemsOlderThan,
purgeExpiredAapItems,
purgeVeilleItems,
purgeVeilleItemsOlderThan,
} from "./db";
// ─── Helpers ─────────────────────────────────────────────────────────────────
function makeAdminCtx(): TrpcContext {
return {
user: {
id: 1,
openId: "admin-test",
email: "admin@itinova.fr",
name: "Admin Test",
loginMethod: "local",
role: "admin",
createdAt: new Date(),
updatedAt: new Date(),
lastSignedIn: new Date(),
},
req: { protocol: "https", headers: {} } as TrpcContext["req"],
res: {
clearCookie: vi.fn(),
cookie: vi.fn(),
} as unknown as TrpcContext["res"],
};
}
function makeUserCtx(): TrpcContext {
return {
user: {
id: 2,
openId: "user-test",
email: "user@itinova.fr",
name: "User Test",
loginMethod: "local",
role: "user",
createdAt: new Date(),
updatedAt: new Date(),
lastSignedIn: new Date(),
},
req: { protocol: "https", headers: {} } as TrpcContext["req"],
res: {
clearCookie: vi.fn(),
cookie: vi.fn(),
} as unknown as TrpcContext["res"],
};
}
function makeAnonCtx(): TrpcContext {
return {
user: null,
req: { protocol: "https", headers: {} } as TrpcContext["req"],
res: {
clearCookie: vi.fn(),
cookie: vi.fn(),
} as unknown as TrpcContext["res"],
};
}
// ─── Tests Auth ───────────────────────────────────────────────────────────────
describe("auth.logout", () => {
it("efface le cookie de session et retourne success", async () => {
const { ctx } = { ctx: makeAdminCtx() };
const clearedCookies: string[] = [];
ctx.res.clearCookie = (name: string) => { clearedCookies.push(name); };
const caller = appRouter.createCaller(ctx);
const result = await caller.auth.logout();
expect(result.success).toBe(true);
expect(clearedCookies.length).toBeGreaterThan(0);
});
it("retourne l'utilisateur connecté via auth.me", async () => {
const ctx = makeAdminCtx();
const caller = appRouter.createCaller(ctx);
const user = await caller.auth.me();
expect(user).not.toBeNull();
expect(user?.email).toBe("admin@itinova.fr");
});
it("retourne null pour un utilisateur non connecté", async () => {
const ctx = makeAnonCtx();
const caller = appRouter.createCaller(ctx);
const user = await caller.auth.me();
expect(user).toBeNull();
});
});
// ─── Tests protection admin ───────────────────────────────────────────────────
describe("protection admin", () => {
it("refuse l'accès aux logs pour un utilisateur non admin", async () => {
const ctx = makeUserCtx();
const caller = appRouter.createCaller(ctx);
await expect(caller.import.logs({ page: 1, pageSize: 10 })).rejects.toThrow();
});
it("refuse l'accès aux paramètres pour un utilisateur non admin", async () => {
const ctx = makeUserCtx();
const caller = appRouter.createCaller(ctx);
await expect(caller.settings.get()).rejects.toThrow();
});
it("refuse la gestion des utilisateurs pour un non admin", async () => {
const ctx = makeUserCtx();
const caller = appRouter.createCaller(ctx);
await expect(caller.users.list()).rejects.toThrow();
});
it("refuse le rapport d’erreurs de classification pour un non admin", async () => {
const ctx = makeUserCtx();
const caller = appRouter.createCaller(ctx);
await expect(caller.rss.classificationErrors({ page: 1, pageSize: 25 })).rejects.toThrow();
});
it("refuse les purges ciblées pour un non admin", async () => {
const caller = appRouter.createCaller(makeUserCtx());
await expect(caller.veille.purge({ scope: "older_than", months: 6 })).rejects.toThrow();
await expect(caller.aap.purge({ scope: "expired" })).rejects.toThrow();
});
});
describe("purges administrateur", () => {
beforeEach(() => vi.clearAllMocks());
it("applique la durée choisie à la veille", async () => {
const result = await appRouter.createCaller(makeAdminCtx()).veille.purge({ scope: "older_than", months: 6 });
expect(purgeVeilleItemsOlderThan).toHaveBeenCalledWith(6);
expect(purgeVeilleItems).not.toHaveBeenCalled();
expect(result).toMatchObject({ success: true, deleted: 4, scope: "older_than", months: 6 });
});
it("supprime uniquement les AAP clôturés lorsque demandé", async () => {
const result = await appRouter.createCaller(makeAdminCtx()).aap.purge({ scope: "expired" });
expect(purgeExpiredAapItems).toHaveBeenCalledOnce();
expect(purgeAapItems).not.toHaveBeenCalled();
expect(purgeAapItemsOlderThan).not.toHaveBeenCalled();
expect(result).toMatchObject({ success: true, deleted: 3, scope: "expired", months: null });
});
it("refuse une purge par ancienneté sans durée", async () => {
const caller = appRouter.createCaller(makeAdminCtx());
await expect(caller.aap.purge({ scope: "older_than" } as never)).rejects.toThrow();
});
});
// ─── Tests accès public ───────────────────────────────────────────────────────
describe("accès public", () => {
it("veille.list est accessible sans authentification", async () => {
const ctx = makeAnonCtx();
const caller = appRouter.createCaller(ctx);
// Ne devrait pas lever d'erreur UNAUTHORIZED
const result = await caller.veille.list({ page: 1, pageSize: 10 });
expect(result).toHaveProperty("items");
expect(result).toHaveProperty("total");
});
it("aap.list est accessible sans authentification", async () => {
const ctx = makeAnonCtx();
const caller = appRouter.createCaller(ctx);
const result = await caller.aap.list({ page: 1, pageSize: 10 });
expect(result).toHaveProperty("items");
expect(result).toHaveProperty("total");
});
it("veille.filters est accessible sans authentification", async () => {
const ctx = makeAnonCtx();
const caller = appRouter.createCaller(ctx);
const result = await caller.veille.filters();
expect(result).toHaveProperty("categories");
expect(result).toHaveProperty("niveaux");
expect(result).toHaveProperty("territoires");
});
it("aap.filters est accessible sans authentification", async () => {
const ctx = makeAnonCtx();
const caller = appRouter.createCaller(ctx);
const result = await caller.aap.filters();
expect(result).toHaveProperty("regions");
expect(result).toHaveProperty("departements");
});
});
// ─── Tests import (admin seulement) ──────────────────────────────────────────
describe("import.run protection", () => {
it("refuse l'import pour un utilisateur non authentifié", async () => {
const ctx = makeAnonCtx();
const caller = appRouter.createCaller(ctx);
await expect(caller.import.run({ type: "all" })).rejects.toThrow();
});
it("refuse l'import pour un utilisateur standard", async () => {
const ctx = makeUserCtx();
const caller = appRouter.createCaller(ctx);
await expect(caller.import.run({ type: "all" })).rejects.toThrow();
});
});