Initial commit: itinova-podcasts v1
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)
This commit is contained in:
198
server/auth.local.test.ts
Normal file
198
server/auth.local.test.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user