Stack: Node.js/Express + React/Vite + tRPC + MySQL (Drizzle ORM) Features: Gestion de podcasts, établissements, mots-clés, upload audio S3 Migrations: 0000-0002 (users, etablissements, mots_cles, podcasts, podcast_mots_cles)
199 lines
6.5 KiB
TypeScript
199 lines
6.5 KiB
TypeScript
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
import { appRouter } from "./routers";
|
|
import type { TrpcContext } from "./_core/context";
|
|
import bcrypt from "bcryptjs";
|
|
|
|
// ─── Mocks ─────────────────────────────────────────────────────────────────────
|
|
|
|
const mockPasswordHash = bcrypt.hashSync("Itinova69!", 10);
|
|
|
|
vi.mock("./db", () => ({
|
|
getUserByUsername: vi.fn().mockImplementation(async (username: string) => {
|
|
if (username === "adminServPodcast") {
|
|
return {
|
|
id: 99,
|
|
openId: "local-adminServPodcast",
|
|
username: "adminServPodcast",
|
|
name: "Administrateur Service Podcast",
|
|
email: null,
|
|
loginMethod: "local",
|
|
role: "admin",
|
|
passwordHash: mockPasswordHash,
|
|
immutable: true,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
lastSignedIn: new Date(),
|
|
};
|
|
}
|
|
return undefined;
|
|
}),
|
|
getUserByOpenId: vi.fn().mockImplementation(async (openId: string) => {
|
|
if (openId === "local-adminServPodcast") {
|
|
return {
|
|
id: 99,
|
|
openId: "local-adminServPodcast",
|
|
username: "adminServPodcast",
|
|
name: "Administrateur Service Podcast",
|
|
email: null,
|
|
loginMethod: "local",
|
|
role: "admin",
|
|
passwordHash: mockPasswordHash,
|
|
immutable: true,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
lastSignedIn: new Date(),
|
|
};
|
|
}
|
|
return undefined;
|
|
}),
|
|
upsertUser: vi.fn().mockResolvedValue(undefined),
|
|
getAllEtablissements: vi.fn().mockResolvedValue([]),
|
|
getAllMotsCles: vi.fn().mockResolvedValue([]),
|
|
getPodcasts: vi.fn().mockResolvedValue([]),
|
|
getPodcastById: vi.fn().mockResolvedValue(null),
|
|
createPodcast: vi.fn().mockResolvedValue(1),
|
|
updatePodcast: vi.fn().mockResolvedValue(undefined),
|
|
deletePodcast: vi.fn().mockResolvedValue(undefined),
|
|
getPodcastStats: vi.fn().mockResolvedValue({ total: 0, publies: 0, brouillons: 0, etablissements: 0 }),
|
|
getAllUsers: vi.fn().mockResolvedValue([]),
|
|
updateUserRole: vi.fn().mockResolvedValue(undefined),
|
|
createLocalUser: vi.fn().mockResolvedValue(undefined),
|
|
getEtablissementById: vi.fn().mockResolvedValue(null),
|
|
createEtablissement: vi.fn().mockResolvedValue(undefined),
|
|
updateEtablissement: vi.fn().mockResolvedValue(undefined),
|
|
deleteEtablissement: vi.fn().mockResolvedValue(undefined),
|
|
createMotCle: vi.fn().mockResolvedValue(undefined),
|
|
deleteMotCle: vi.fn().mockResolvedValue(undefined),
|
|
}));
|
|
|
|
// ─── Helpers ───────────────────────────────────────────────────────────────────
|
|
|
|
function makePublicCtx(): TrpcContext {
|
|
const cookies: Record<string, string> = {};
|
|
return {
|
|
user: null,
|
|
req: {
|
|
protocol: "https",
|
|
headers: {},
|
|
} as TrpcContext["req"],
|
|
res: {
|
|
clearCookie: vi.fn(),
|
|
cookie: vi.fn((name: string, value: string) => {
|
|
cookies[name] = value;
|
|
}),
|
|
} as unknown as TrpcContext["res"],
|
|
};
|
|
}
|
|
|
|
// ─── Tests ─────────────────────────────────────────────────────────────────────
|
|
|
|
describe("auth.loginLocal", () => {
|
|
it("réussit avec les bonnes credentials adminServPodcast", async () => {
|
|
const ctx = makePublicCtx();
|
|
const caller = appRouter.createCaller(ctx);
|
|
|
|
const result = await caller.auth.loginLocal({
|
|
username: "adminServPodcast",
|
|
password: "Itinova69!",
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.user).toBeDefined();
|
|
expect(result.user.username).toBe("adminServPodcast");
|
|
expect(result.user.role).toBe("admin");
|
|
expect(result.user.immutable).toBe(true);
|
|
});
|
|
|
|
it("échoue avec un mauvais mot de passe", async () => {
|
|
const ctx = makePublicCtx();
|
|
const caller = appRouter.createCaller(ctx);
|
|
|
|
await expect(
|
|
caller.auth.loginLocal({
|
|
username: "adminServPodcast",
|
|
password: "mauvais-mot-de-passe",
|
|
})
|
|
).rejects.toMatchObject({
|
|
code: "UNAUTHORIZED",
|
|
message: "Identifiant ou mot de passe incorrect",
|
|
});
|
|
});
|
|
|
|
it("échoue avec un utilisateur inexistant", async () => {
|
|
const ctx = makePublicCtx();
|
|
const caller = appRouter.createCaller(ctx);
|
|
|
|
await expect(
|
|
caller.auth.loginLocal({
|
|
username: "utilisateurInexistant",
|
|
password: "n'importe-quoi",
|
|
})
|
|
).rejects.toMatchObject({
|
|
code: "UNAUTHORIZED",
|
|
});
|
|
});
|
|
|
|
it("définit un cookie de session après connexion réussie", async () => {
|
|
const setCookieCalls: Array<{ name: string; value: string }> = [];
|
|
const ctx: TrpcContext = {
|
|
user: null,
|
|
req: { protocol: "https", headers: {} } as TrpcContext["req"],
|
|
res: {
|
|
clearCookie: vi.fn(),
|
|
cookie: vi.fn((name: string, value: string) => {
|
|
setCookieCalls.push({ name, value });
|
|
}),
|
|
} as unknown as TrpcContext["res"],
|
|
};
|
|
|
|
const caller = appRouter.createCaller(ctx);
|
|
await caller.auth.loginLocal({
|
|
username: "adminServPodcast",
|
|
password: "Itinova69!",
|
|
});
|
|
|
|
expect(setCookieCalls.length).toBeGreaterThan(0);
|
|
expect(setCookieCalls[0].name).toBeDefined();
|
|
expect(typeof setCookieCalls[0].value).toBe("string");
|
|
expect(setCookieCalls[0].value.length).toBeGreaterThan(10);
|
|
});
|
|
});
|
|
|
|
describe("compte adminServPodcast - propriétés immuables", () => {
|
|
it("le compte est marqué immutable = true", async () => {
|
|
const ctx = makePublicCtx();
|
|
const caller = appRouter.createCaller(ctx);
|
|
|
|
const result = await caller.auth.loginLocal({
|
|
username: "adminServPodcast",
|
|
password: "Itinova69!",
|
|
});
|
|
|
|
expect(result.user.immutable).toBe(true);
|
|
});
|
|
|
|
it("le compte a le rôle admin", async () => {
|
|
const ctx = makePublicCtx();
|
|
const caller = appRouter.createCaller(ctx);
|
|
|
|
const result = await caller.auth.loginLocal({
|
|
username: "adminServPodcast",
|
|
password: "Itinova69!",
|
|
});
|
|
|
|
expect(result.user.role).toBe("admin");
|
|
});
|
|
|
|
it("le compte utilise la méthode de connexion locale", async () => {
|
|
const ctx = makePublicCtx();
|
|
const caller = appRouter.createCaller(ctx);
|
|
|
|
const result = await caller.auth.loginLocal({
|
|
username: "adminServPodcast",
|
|
password: "Itinova69!",
|
|
});
|
|
|
|
expect(result.user.loginMethod).toBe("local");
|
|
});
|
|
});
|