feat(home): implement home summary feature with routes and use case
- Added home routes and corresponding route handler for fetching home summary. - Created GetHomeSummary use case to aggregate user data including groups, attendees, and upcoming payments. - Introduced HomeSummaryDto and related schemas for data validation. - Implemented nextClassOccurrence function to determine the next class based on user timezone. - Added tests for home summary functionality and next class occurrence logic.
This commit is contained in:
112
apps/backend/test/home-scheduler.test.ts
Normal file
112
apps/backend/test/home-scheduler.test.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { CLASS_WINDOW_HOURS, nextClassOccurrence } from '@/modules/home/lib/scheduler';
|
||||
|
||||
const TZ = 'America/Mexico_City';
|
||||
|
||||
describe('nextClassOccurrence', () => {
|
||||
it('devuelve la próxima clase de hoy cuando aún no arrancó', () => {
|
||||
const now = new Date('2026-09-23T18:00:00.000Z'); // miércoles 12:00 hora CDMX
|
||||
|
||||
const result = nextClassOccurrence({
|
||||
days: ['WEDNESDAY'],
|
||||
time: '18:00',
|
||||
now,
|
||||
timeZone: TZ,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
occurrenceAt: new Date('2026-09-24T00:00:00.000Z'),
|
||||
isNow: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('marca en curso si la clase arrancó hace menos de la ventana', () => {
|
||||
const now = new Date('2026-09-24T01:00:00.000Z'); // miércoles 19:00 hora CDMX (1 h tras la clase)
|
||||
|
||||
const result = nextClassOccurrence({
|
||||
days: ['WEDNESDAY'],
|
||||
time: '18:00',
|
||||
now,
|
||||
timeZone: TZ,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
occurrenceAt: new Date('2026-09-24T00:00:00.000Z'),
|
||||
isNow: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('salta a la próxima semana si la clase ya pasó la ventana', () => {
|
||||
const now = new Date('2026-09-24T03:00:00.000Z'); // miércoles 21:00 hora CDMX (3 h tras la clase)
|
||||
|
||||
const result = nextClassOccurrence({
|
||||
days: ['WEDNESDAY'],
|
||||
time: '18:00',
|
||||
now,
|
||||
timeZone: TZ,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
occurrenceAt: new Date('2026-10-01T00:00:00.000Z'),
|
||||
isNow: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('elige la ocurrencia más próxima entre varios días', () => {
|
||||
const now = new Date('2026-09-23T18:00:00.000Z'); // miércoles 12:00 hora CDMX
|
||||
|
||||
const result = nextClassOccurrence({
|
||||
days: ['MONDAY', 'WEDNESDAY', 'FRIDAY'],
|
||||
time: '18:00',
|
||||
now,
|
||||
timeZone: TZ,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
occurrenceAt: new Date('2026-09-24T00:00:00.000Z'),
|
||||
isNow: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('cruza de mes sin problema', () => {
|
||||
const now = new Date('2026-09-29T18:00:00.000Z'); // martes 12:00 hora CDMX (último día de septiembre es miércoles 30)
|
||||
|
||||
const result = nextClassOccurrence({
|
||||
days: ['THURSDAY'],
|
||||
time: '09:00',
|
||||
now,
|
||||
timeZone: TZ,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
occurrenceAt: new Date('2026-10-01T15:00:00.000Z'),
|
||||
isNow: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('devuelve null si no hay días de clase', () => {
|
||||
const result = nextClassOccurrence({
|
||||
days: [],
|
||||
time: '18:00',
|
||||
now: new Date('2026-09-23T18:00:00.000Z'),
|
||||
timeZone: TZ,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('devuelve null si la hora es inválida', () => {
|
||||
const result = nextClassOccurrence({
|
||||
days: ['WEDNESDAY'],
|
||||
time: '99:99',
|
||||
now: new Date('2026-09-23T18:00:00.000Z'),
|
||||
timeZone: TZ,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('expone el tamaño de la ventana de "en curso"', () => {
|
||||
expect(CLASS_WINDOW_HOURS).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user