186 lines
6.7 KiB
TypeScript
186 lines
6.7 KiB
TypeScript
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: [] })),
|
||
};
|
||
});
|
||
|
||
import { appRouter } from "./routers";
|
||
import type { TrpcContext } from "./_core/context";
|
||
|
||
// ─── 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();
|
||
});
|
||
});
|
||
|
||
// ─── 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();
|
||
});
|
||
});
|