Initial local backup snapshot

This commit is contained in:
Selecta Keke 2026-06-24 00:02:55 -03:00
commit acd9e14ba5
367 changed files with 118038 additions and 0 deletions

452
server/admin.test.ts Normal file
View file

@ -0,0 +1,452 @@
import { describe, expect, it, vi, beforeEach } from "vitest";
import { appRouter } from "./routers";
import type { TrpcContext } from "./_core/context";
import * as db from "./db";
// Mock the database module
vi.mock("./db", () => ({
getDashboardStats: vi.fn().mockResolvedValue({
totalAssociations: 10,
activeAssociations: 8,
totalRequests: 25,
pendingRequests: 5,
validatedRequests: 15,
rejectedRequests: 3,
newAssociationsThisMonth: 2,
newRequestsThisMonth: 8,
requestsThisWeek: 3,
overdueRequests: 1,
acceptanceRate: 83,
}),
getRequestsPerMonth: vi.fn().mockResolvedValue([
{ month: '2026-01', total: 10, validated: 5, rejected: 2, pending: 3 },
]),
getAssociationsPerMonth: vi.fn().mockResolvedValue([
{ month: '2026-01', total: 3 },
]),
getRequestsByTypeStats: vi.fn().mockResolvedValue([
{ type: 'subvention_fonctionnement', total: 10, validated: 5, totalMontantAccorde: 50000 },
]),
getAverageProcessingTime: vi.fn().mockResolvedValue(7.5),
getPendingRequests: vi.fn().mockResolvedValue([]),
getOverdueRequests: vi.fn().mockResolvedValue([]),
getAdminUsers: vi.fn().mockResolvedValue([
{ id: 1, name: 'Admin', email: 'admin@test.com', role: 'admin', lastSignedIn: new Date() },
]),
getAuditLogs: vi.fn().mockResolvedValue({ data: [], total: 0 }),
getAdminNotifications: vi.fn().mockResolvedValue([]),
getUnreadNotificationCount: vi.fn().mockResolvedValue(0),
markNotificationAsRead: vi.fn().mockResolvedValue(undefined),
markAllNotificationsAsRead: vi.fn().mockResolvedValue(undefined),
searchAssociations: vi.fn().mockResolvedValue({ data: [], total: 0 }),
getAssociationCommuneCounts: vi.fn().mockResolvedValue({
all: 5,
kourou: 2,
sinnamary: 1,
iracoubo: 1,
saint_elie: 1,
}),
searchRequests: vi.fn().mockResolvedValue({ data: [], total: 0 }),
getAllPortalSettings: vi.fn().mockResolvedValue([]),
getPortalSetting: vi.fn().mockResolvedValue(null),
getPortalSettingRecord: vi.fn().mockResolvedValue(null),
setPortalSetting: vi.fn().mockResolvedValue(undefined),
getAssociationDirectoryEntriesSummary: vi.fn().mockResolvedValue({ total: 5, withEmail: 4, withoutEmail: 1, registered: 2, unregistered: 3, lastImportAt: null }),
listAssociationDirectoryEntriesWithStatus: vi.fn().mockResolvedValue({ data: [], total: 0 }),
listAssociationDirectoryMapEntries: vi.fn().mockResolvedValue({ data: [], total: 0 }),
getAssociationInvitationSummariesForDirectoryEntryIds: vi.fn().mockResolvedValue({}),
listAssociationDirectoryEntries: vi.fn().mockResolvedValue([]),
listAssociationDirectoryReviews: vi.fn().mockResolvedValue([]),
findAssociationDirectoryMatch: vi.fn().mockResolvedValue({ status: "none", candidates: [], reason: "no match" }),
upsertAssociationDirectoryEntry: vi.fn().mockResolvedValue("created"),
createAssociationDirectoryEntry: vi.fn().mockResolvedValue(1),
updateAssociationDirectoryEntry: vi.fn().mockResolvedValue(undefined),
getAssociationDirectoryEntryById: vi.fn().mockResolvedValue(null),
getAssociationDirectoryEntryDetails: vi.fn().mockResolvedValue(null),
createAssociationDirectoryReview: vi.fn().mockResolvedValue(1),
getAssociationDirectoryReviewById: vi.fn().mockResolvedValue(null),
getPendingAssociationDirectoryReviewByUserId: vi.fn().mockResolvedValue(null),
updateAssociationDirectoryReview: vi.fn().mockResolvedValue(undefined),
getAssociationBySourceDirectoryEntryId: vi.fn().mockResolvedValue(null),
createAssociationInvitation: vi.fn().mockResolvedValue(1),
getAssociationInvitationByToken: vi.fn().mockResolvedValue(null),
getActiveAssociationInvitationByDirectoryEntryId: vi.fn().mockResolvedValue(null),
getLatestAssociationInvitationByDirectoryEntryId: vi.fn().mockResolvedValue(null),
revokeAssociationInvitationsByDirectoryEntryId: vi.fn().mockResolvedValue(undefined),
markAssociationInvitationUsed: vi.fn().mockResolvedValue(undefined),
createAuditLog: vi.fn().mockResolvedValue(1),
getMaterialReturnFollowupByRequestId: vi.fn().mockResolvedValue(null),
}));
type AuthenticatedUser = NonNullable<TrpcContext["user"]>;
function createAdminContext(): { ctx: TrpcContext } {
const user: AuthenticatedUser = {
id: 1,
openId: "admin-user",
email: "admin@example.com",
name: "Admin User",
loginMethod: "manus",
role: "admin",
canManageLogistics: false,
canSignSalle: false,
delegatedSalleSignerUserId: null,
salleSignatureDelegatedByUserIds: [],
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
lastSignedIn: new Date(),
};
const ctx: TrpcContext = {
user,
req: {
protocol: "https",
headers: {},
} as TrpcContext["req"],
res: {
clearCookie: vi.fn(),
} as unknown as TrpcContext["res"],
};
return { ctx };
}
function createSuperAdminContext(): { ctx: TrpcContext } {
const user: AuthenticatedUser = {
id: 3,
openId: "super-admin-user",
email: "superadmin@example.com",
name: "Super Admin User",
loginMethod: "manus",
role: "super_admin",
canManageLogistics: true,
canSignSalle: true,
delegatedSalleSignerUserId: null,
salleSignatureDelegatedByUserIds: [],
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
lastSignedIn: new Date(),
};
const ctx: TrpcContext = {
user,
req: {
protocol: "https",
headers: {},
} as TrpcContext["req"],
res: {
clearCookie: vi.fn(),
} as unknown as TrpcContext["res"],
};
return { ctx };
}
function createUserContext(): { ctx: TrpcContext } {
const user: AuthenticatedUser = {
id: 2,
openId: "regular-user",
email: "user@example.com",
name: "Regular User",
loginMethod: "manus",
role: "user",
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
lastSignedIn: new Date(),
};
const ctx: TrpcContext = {
user,
req: {
protocol: "https",
headers: {},
} as TrpcContext["req"],
res: {
clearCookie: vi.fn(),
} as unknown as TrpcContext["res"],
};
return { ctx };
}
beforeEach(() => {
vi.clearAllMocks();
});
describe("Admin Dashboard Stats", () => {
it("returns dashboard statistics for admin users", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.stats.getDashboard();
expect(result).toBeDefined();
expect(result.totalAssociations).toBe(10);
expect(result.activeAssociations).toBe(8);
expect(result.totalRequests).toBe(25);
expect(result.pendingRequests).toBe(5);
expect(result.acceptanceRate).toBe(83);
});
it("denies access to non-admin users", async () => {
const { ctx } = createUserContext();
const caller = appRouter.createCaller(ctx);
await expect(caller.stats.getDashboard()).rejects.toThrow(
"Accès réservé à laccueil, aux administrateurs et aux super administrateurs",
);
});
});
describe("Admin Request Statistics", () => {
it("returns requests per month for admin users", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.stats.getRequestsPerMonth({ months: 12 });
expect(Array.isArray(result)).toBe(true);
});
it("returns requests by type for admin users", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.stats.getRequestsByType();
expect(Array.isArray(result)).toBe(true);
});
it("returns average processing time for admin users", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.stats.getAverageProcessingTime();
expect(result).toBe(7.5);
});
});
describe("Admin Notifications", () => {
it("returns notifications for admin users", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.notifications.getAll({ unreadOnly: false });
expect(Array.isArray(result)).toBe(true);
});
it("returns unread count for admin users", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.notifications.getUnreadCount();
expect(typeof result).toBe("number");
});
it("marks notification as read", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.notifications.markAsRead({ id: 1 });
expect(result.success).toBe(true);
});
it("marks all notifications as read", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.notifications.markAllAsRead();
expect(result.success).toBe(true);
});
});
describe("Admin User Management", () => {
it("lists admin users for super admin", async () => {
const { ctx } = createSuperAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.adminUsers.listAll();
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
expect(result[0].role).toBe("admin");
});
it("denies admin user listing to regular admins", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
await expect(caller.adminUsers.listAll()).rejects.toThrow("Accès réservé aux super administrateurs");
});
});
describe("Audit Log", () => {
it("returns audit logs for admin users", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.auditLog.list({ limit: 10 });
expect(result).toBeDefined();
expect(result.data).toBeDefined();
expect(Array.isArray(result.data)).toBe(true);
});
});
describe("Association Directory Admin", () => {
it("returns directory summary for admin", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.associationDirectory.getSummary();
expect(result.total).toBe(5);
expect(result.withEmail).toBe(4);
expect(result.unregistered).toBe(3);
});
it("returns directory entries with registration filters for admin", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.associationDirectory.listLatest({
registrationStatus: "unregistered",
commune: "kourou",
limit: 25,
});
expect(result).toEqual({ data: [], total: 0 });
});
it("denies directory summary to regular users", async () => {
const { ctx } = createUserContext();
const caller = appRouter.createCaller(ctx);
await expect(caller.associationDirectory.getSummary()).rejects.toThrow("Accès réservé aux administrateurs");
});
});
describe("Association Search", () => {
it("searches associations for admin users", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.association.search({
search: "test",
limit: 10,
});
expect(result).toBeDefined();
expect(result.data).toBeDefined();
expect(typeof result.total).toBe("number");
});
it("returns commune counts for admin users", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.association.getCommuneCounts();
expect(result.kourou).toBe(2);
expect(result.all).toBe(5);
});
});
describe("Request Search", () => {
it("searches requests for admin users", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.request.search({
search: "test",
status: "soumise",
limit: 10,
});
expect(result).toBeDefined();
expect(result.data).toBeDefined();
expect(typeof result.total).toBe("number");
});
it("returns pending requests for admin users", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.request.getPending();
expect(Array.isArray(result)).toBe(true);
});
it("returns overdue requests for admin users", async () => {
const { ctx } = createAdminContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.request.getOverdue();
expect(Array.isArray(result)).toBe(true);
});
});
describe("Material Availability", () => {
it("counts submitted and in-progress material requests before validation", async () => {
vi.mocked(db.searchRequests).mockResolvedValue({
data: [
{
id: 101,
type: "demande_materiel_evenementiel",
status: "soumise",
formData: JSON.stringify({
dateDebutManifestation: "2026-06-10",
dateRestitution: "2026-06-12",
materielsDemandes: { tente3x3: true },
quantitesDemandees: { tente3x3: "4" },
}),
},
{
id: 102,
type: "demande_materiel_evenementiel",
status: "en_cours_traitement",
formData: JSON.stringify({
dateDebutManifestation: "2026-06-10",
dateRestitution: "2026-06-12",
materielsDemandes: { tente3x3: true },
quantitesDemandees: { tente3x3: "3" },
}),
},
{
id: 103,
type: "demande_materiel_evenementiel",
status: "brouillon",
formData: JSON.stringify({
dateDebutManifestation: "2026-06-10",
dateRestitution: "2026-06-12",
materielsDemandes: { tente3x3: true },
quantitesDemandees: { tente3x3: "2" },
}),
},
] as any,
total: 3,
});
vi.mocked(db.getMaterialReturnFollowupByRequestId).mockResolvedValue(null as any);
const { ctx } = createUserContext();
const caller = appRouter.createCaller(ctx);
const result = await caller.request.getMaterialAvailability({
dateDebut: "2026-06-10",
dateFin: "2026-06-12",
});
const tente = result.items.find((item) => item.key === "tente3x3");
expect(tente?.reserved).toBe(7);
expect(tente?.available).toBe(5);
});
});