feat: implement groups risk overview feature with analytics and routes

This commit is contained in:
Jose Selesan
2026-09-24 20:45:11 -03:00
parent 2e184845e1
commit efde1aaf33
11 changed files with 382 additions and 16 deletions

View File

@@ -0,0 +1,114 @@
import { beforeEach, describe, expect, it, mock, vi } from 'bun:test';
import { Hono } from 'hono';
const db = {
group: {
findMany: mock(),
},
attendee: {
findMany: mock(),
},
classSession: {
findMany: mock(),
},
attendance: {
findMany: mock(),
},
};
mock.module('@/lib/prisma', () => ({
default: db,
getPrismaClient: mock(),
UnitOfWork: class {
executeResult = mock(async (cb: (tx: unknown) => Promise<unknown>) => cb(db));
execute = mock(async (cb: (tx: unknown) => Promise<unknown>) => cb(db));
},
}));
import { groupsRiskOverviewRoute } from '@/modules/analytics';
const userId = 'user-1';
const DAY_MS = 24 * 60 * 60 * 1000;
const now = Date.now();
const at = (offsetDays: number) => new Date(now - offsetDays * DAY_MS);
function makeApp(userValue: unknown) {
const app = new Hono();
app.use('*', async (c, next) => {
c.set('user', userValue as never);
await next();
});
app.route('/groups-risk', groupsRiskOverviewRoute);
return app;
}
describe('GET /groups-risk/overview', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('devuelve items vacíos si el usuario no tiene grupos', async () => {
prismaFetchGroupFindMany([]);
prismaFetchAttendeeFindMany([]);
prismaFetchClassSessionFindMany([]);
prismaFetchAttendanceFindMany([]);
const res = await makeApp({ id: userId }).request('/groups-risk/overview');
expect(res.status).toBe(200);
expect(await res.json()).toEqual({ items: [] });
});
it('marca HIGH a un grupo con un alumno con 3 ausencias consecutivas', async () => {
prismaFetchGroupFindMany([
{ id: 'group-1', name: 'Funcional' },
{ id: 'group-2', name: 'Yoga' },
]);
prismaFetchAttendeeFindMany([
{ id: 'a-1', groupId: 'group-1' },
{ id: 'a-2', groupId: 'group-2' },
]);
prismaFetchClassSessionFindMany([
{ id: 's1', groupId: 'group-1', startsAt: at(-1) },
{ id: 's2', groupId: 'group-1', startsAt: at(-2) },
{ id: 's3', groupId: 'group-1', startsAt: at(-3) },
{ id: 's4', groupId: 'group-2', startsAt: at(-1) },
]);
prismaFetchAttendanceFindMany([
attendance('a-1', 'ABSENT', at(-1)),
attendance('a-1', 'ABSENT', at(-2)),
attendance('a-1', 'ABSENT', at(-3)),
attendance('a-2', 'PRESENT', at(-1)),
]);
const res = await makeApp({ id: userId }).request('/groups-risk/overview');
expect(res.status).toBe(200);
expect(await res.json()).toEqual({
items: [
{ groupId: 'group-1', groupName: 'Funcional', riskLevel: 'HIGH' },
{ groupId: 'group-2', groupName: 'Yoga', riskLevel: 'NONE' },
],
});
});
});
function prismaFetchGroupFindMany(rows: unknown[]) {
db.group.findMany.mockResolvedValue(rows);
}
function prismaFetchAttendeeFindMany(rows: unknown[]) {
db.attendee.findMany.mockResolvedValue(rows);
}
function prismaFetchClassSessionFindMany(rows: unknown[]) {
db.classSession.findMany.mockResolvedValue(rows);
}
function prismaFetchAttendanceFindMany(rows: unknown[]) {
db.attendance.findMany.mockResolvedValue(rows);
}
function attendance(attendeeId: string, status: string, startsAt: string) {
return { attendeeId, status, classSession: { startsAt } };
}