Fix dashboard range filter dropping startDate/endDate query params
The aggregations controller typed its query DTO as a `class` with no class-validator decorators. Because main.ts installs ValidationPipe with `whitelist: true`, every undecorated property was stripped before reaching the handler, so all three aggregations endpoints silently fell back to "current month UTC" no matter what the URL said. Switching to an `interface` (matching the convention TransactionFilters and ActivityLogFilters already use) takes the DTO out of class-validator's metatype reflection and lets the dates through untouched. Adds a regression test that exercises the real HTTP pipeline via supertest with the same useGlobalPipes config as main.ts — the existing direct-call controller tests bypass the pipeline, which is how the bug shipped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { INestApplication, ValidationPipe } from '@nestjs/common';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import request from 'supertest';
|
||||
import { AggregationsController } from './aggregations.controller';
|
||||
import { AggregationsService } from './aggregations.service';
|
||||
import { AuthGuard } from '../auth/auth.guard';
|
||||
@@ -114,4 +116,56 @@ describe('AggregationsController', () => {
|
||||
);
|
||||
expect(result).toEqual({ inflows: 5000, outflows: 3500, net: 1500 });
|
||||
});
|
||||
|
||||
// Regression: the dashboard's range selector silently fell back to "current
|
||||
// month" because main.ts installs `ValidationPipe({ whitelist: true })`, and
|
||||
// a class-typed query DTO with no decorators had every field stripped before
|
||||
// it reached the handler. The direct-call tests above bypass the HTTP
|
||||
// pipeline entirely, so the bug shipped. This block goes through the real
|
||||
// pipeline (supertest + the same useGlobalPipes config as main.ts).
|
||||
describe('through the full HTTP pipeline', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
controllers: [AggregationsController],
|
||||
providers: [{ provide: AggregationsService, useValue: mockService }],
|
||||
})
|
||||
.overrideGuard(AuthGuard)
|
||||
.useValue({
|
||||
canActivate: (ctx: any) => {
|
||||
ctx.switchToHttp().getRequest().user = mockUser;
|
||||
return true;
|
||||
},
|
||||
})
|
||||
.compile();
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
transform: true,
|
||||
transformOptions: { enableImplicitConversion: true },
|
||||
}),
|
||||
);
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('preserves startDate/endDate query params end-to-end', async () => {
|
||||
await request(app.getHttpServer())
|
||||
.get('/aggregations/summary?startDate=2026-04-01&endDate=2026-04-30')
|
||||
.expect(200);
|
||||
|
||||
expect(mockService.getSummary).toHaveBeenCalledWith(
|
||||
'user-123',
|
||||
'2026-04-01',
|
||||
'2026-04-30',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AuthGuard } from '../auth/auth.guard';
|
||||
import { CurrentUser } from '../auth/user.decorator';
|
||||
import type { User } from '@prisma/client';
|
||||
|
||||
class DateRangeQuery {
|
||||
interface DateRangeQuery {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user