Fonctionnalités implémentées : ✅ Authentification locale (email/password) + Azure AD + Manus OAuth ✅ Upload drag-and-drop de fichiers PDF avec suivi en temps réel ✅ Extraction automatique avec Mistral AI (OCR + LLM) ✅ Détection de doublons (fournisseur, numéro, date) ✅ Score de qualité d'extraction (0-100) ✅ Tableau de bord avec statistiques ✅ Liste des factures avec recherche et filtres ✅ Paramètres utilisateur (LLM, keywords, SFTP) ✅ Historique des imports avec logs détaillés ✅ Gestion des utilisateurs (admin) ✅ Export SFTP manuel/automatique ✅ Stockage local avec organisation YYYY-MM ✅ Tests unitaires d'authentification Architecture : - Frontend : React 19 + Vite + TailwindCSS + Radix UI - Backend : Express + tRPC + Drizzle ORM - Base de données : MySQL (6 tables) - IA : Mistral AI pour extraction - Stockage : Local filesystem - Export : SFTP Pages : - Login (choix local/Azure/Manus) - Dashboard (statistiques) - Upload (drag-and-drop) - Invoices (liste avec recherche) - Settings (LLM, keywords, SFTP) - History (logs d'import) - Users (gestion admin)
70 lines
1.9 KiB
TypeScript
70 lines
1.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { appRouter } from "./routers";
|
|
import { COOKIE_NAME } from "../shared/const";
|
|
import type { TrpcContext } from "./_core/context";
|
|
|
|
type CookieCall = {
|
|
name: string;
|
|
options: Record<string, unknown>;
|
|
};
|
|
|
|
type AuthenticatedUser = NonNullable<TrpcContext["user"]>;
|
|
|
|
function createAuthContext(): { ctx: TrpcContext; clearedCookies: CookieCall[] } {
|
|
const clearedCookies: CookieCall[] = [];
|
|
|
|
const user: AuthenticatedUser = {
|
|
id: 1,
|
|
openId: "sample-user",
|
|
email: "sample@example.com",
|
|
name: "Sample User",
|
|
loginMethod: "manus",
|
|
role: "user",
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
lastSignedIn: new Date(),
|
|
};
|
|
|
|
const ctx: TrpcContext = {
|
|
user,
|
|
req: {
|
|
protocol: "https",
|
|
headers: {},
|
|
} as TrpcContext["req"],
|
|
res: {
|
|
clearCookie: (name: string, options: Record<string, unknown>) => {
|
|
clearedCookies.push({ name, options });
|
|
},
|
|
} as TrpcContext["res"],
|
|
};
|
|
|
|
return { ctx, clearedCookies };
|
|
}
|
|
|
|
describe("auth.logout", () => {
|
|
it("clears the session cookie and reports success", async () => {
|
|
const { ctx, clearedCookies } = createAuthContext();
|
|
const caller = appRouter.createCaller(ctx);
|
|
|
|
const result = await caller.auth.logout();
|
|
|
|
expect(result).toEqual({ success: true });
|
|
expect(clearedCookies).toHaveLength(2); // COOKIE_NAME + auth_token
|
|
|
|
// Check that both cookies are cleared
|
|
const cookieNames = clearedCookies.map(c => c.name);
|
|
expect(cookieNames).toContain(COOKIE_NAME);
|
|
expect(cookieNames).toContain("auth_token");
|
|
|
|
// Check options for the main cookie
|
|
const mainCookie = clearedCookies.find(c => c.name === COOKIE_NAME);
|
|
expect(mainCookie?.options).toMatchObject({
|
|
maxAge: -1,
|
|
secure: true,
|
|
sameSite: "none",
|
|
httpOnly: true,
|
|
path: "/",
|
|
});
|
|
});
|
|
});
|