refacto: DTOs class-validator — validation active sur tous les endpoints
This commit is contained in:
11
backend/src/list/dto/add-to-list.dto.ts
Normal file
11
backend/src/list/dto/add-to-list.dto.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { IsEnum, IsInt, Min } from 'class-validator';
|
||||||
|
import { ListStatus } from '../user-work.entity';
|
||||||
|
|
||||||
|
export class AddToListDto {
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
anilistId: number;
|
||||||
|
|
||||||
|
@IsEnum(ListStatus)
|
||||||
|
status: ListStatus;
|
||||||
|
}
|
||||||
168
backend/src/list/dto/dto-validation.spec.ts
Normal file
168
backend/src/list/dto/dto-validation.spec.ts
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import { validate } from 'class-validator';
|
||||||
|
import { plainToInstance } from 'class-transformer';
|
||||||
|
import { AddToListDto } from './add-to-list.dto';
|
||||||
|
import { UpdateProgressDto } from './update-progress.dto';
|
||||||
|
import { UpdateStatusDto } from './update-status.dto';
|
||||||
|
import { SetScoreDto } from './set-score.dto';
|
||||||
|
import { ListStatus } from '../user-work.entity';
|
||||||
|
|
||||||
|
// Helper: transform plain object to class instance and validate
|
||||||
|
async function check<T extends object>(
|
||||||
|
cls: new () => T,
|
||||||
|
plain: Record<string, any>,
|
||||||
|
): Promise<string[]> {
|
||||||
|
const instance = plainToInstance(cls, plain);
|
||||||
|
const errors = await validate(instance);
|
||||||
|
return errors.flatMap((e) => Object.values(e.constraints ?? {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── AddToListDto ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('AddToListDto', () => {
|
||||||
|
it('should accept valid input', async () => {
|
||||||
|
const errors = await check(AddToListDto, {
|
||||||
|
anilistId: 12345,
|
||||||
|
status: ListStatus.WATCHING,
|
||||||
|
});
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject missing anilistId', async () => {
|
||||||
|
const errors = await check(AddToListDto, {
|
||||||
|
status: ListStatus.WATCHING,
|
||||||
|
});
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject non-integer anilistId', async () => {
|
||||||
|
const errors = await check(AddToListDto, {
|
||||||
|
anilistId: 'abc',
|
||||||
|
status: ListStatus.WATCHING,
|
||||||
|
});
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject anilistId < 1', async () => {
|
||||||
|
const errors = await check(AddToListDto, {
|
||||||
|
anilistId: 0,
|
||||||
|
status: ListStatus.WATCHING,
|
||||||
|
});
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject missing status', async () => {
|
||||||
|
const errors = await check(AddToListDto, {
|
||||||
|
anilistId: 12345,
|
||||||
|
});
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid status', async () => {
|
||||||
|
const errors = await check(AddToListDto, {
|
||||||
|
anilistId: 12345,
|
||||||
|
status: 'binge_watching',
|
||||||
|
});
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept all valid ListStatus values', async () => {
|
||||||
|
for (const status of Object.values(ListStatus)) {
|
||||||
|
const errors = await check(AddToListDto, { anilistId: 1, status });
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── UpdateProgressDto ───────────────────────────────────────
|
||||||
|
|
||||||
|
describe('UpdateProgressDto', () => {
|
||||||
|
it('should accept valid progress', async () => {
|
||||||
|
const errors = await check(UpdateProgressDto, { progress: 5 });
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept progress = 0', async () => {
|
||||||
|
const errors = await check(UpdateProgressDto, { progress: 0 });
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject negative progress', async () => {
|
||||||
|
const errors = await check(UpdateProgressDto, { progress: -1 });
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject non-integer progress', async () => {
|
||||||
|
const errors = await check(UpdateProgressDto, { progress: 3.5 });
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject missing progress', async () => {
|
||||||
|
const errors = await check(UpdateProgressDto, {});
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject string progress', async () => {
|
||||||
|
const errors = await check(UpdateProgressDto, { progress: 'five' });
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── UpdateStatusDto ─────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('UpdateStatusDto', () => {
|
||||||
|
it('should accept valid status', async () => {
|
||||||
|
const errors = await check(UpdateStatusDto, {
|
||||||
|
status: ListStatus.COMPLETED,
|
||||||
|
});
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject missing status', async () => {
|
||||||
|
const errors = await check(UpdateStatusDto, {});
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid status string', async () => {
|
||||||
|
const errors = await check(UpdateStatusDto, { status: 'finished' });
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── SetScoreDto ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('SetScoreDto', () => {
|
||||||
|
it('should accept valid score', async () => {
|
||||||
|
const errors = await check(SetScoreDto, { score: 8.5 });
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept score = 0', async () => {
|
||||||
|
const errors = await check(SetScoreDto, { score: 0 });
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept score = 10', async () => {
|
||||||
|
const errors = await check(SetScoreDto, { score: 10 });
|
||||||
|
expect(errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject score > 10', async () => {
|
||||||
|
const errors = await check(SetScoreDto, { score: 11 });
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject negative score', async () => {
|
||||||
|
const errors = await check(SetScoreDto, { score: -1 });
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject missing score', async () => {
|
||||||
|
const errors = await check(SetScoreDto, {});
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject string score', async () => {
|
||||||
|
const errors = await check(SetScoreDto, { score: 'great' });
|
||||||
|
expect(errors.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
8
backend/src/list/dto/set-score.dto.ts
Normal file
8
backend/src/list/dto/set-score.dto.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { IsNumber, Min, Max } from 'class-validator';
|
||||||
|
|
||||||
|
export class SetScoreDto {
|
||||||
|
@IsNumber()
|
||||||
|
@Min(0)
|
||||||
|
@Max(10)
|
||||||
|
score: number;
|
||||||
|
}
|
||||||
7
backend/src/list/dto/update-progress.dto.ts
Normal file
7
backend/src/list/dto/update-progress.dto.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { IsInt, Min } from 'class-validator';
|
||||||
|
|
||||||
|
export class UpdateProgressDto {
|
||||||
|
@IsInt()
|
||||||
|
@Min(0)
|
||||||
|
progress: number;
|
||||||
|
}
|
||||||
7
backend/src/list/dto/update-status.dto.ts
Normal file
7
backend/src/list/dto/update-status.dto.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { IsEnum } from 'class-validator';
|
||||||
|
import { ListStatus } from '../user-work.entity';
|
||||||
|
|
||||||
|
export class UpdateStatusDto {
|
||||||
|
@IsEnum(ListStatus)
|
||||||
|
status: ListStatus;
|
||||||
|
}
|
||||||
@@ -15,6 +15,10 @@ import { ListService } from './list.service';
|
|||||||
import { ListStatus } from './user-work.entity';
|
import { ListStatus } from './user-work.entity';
|
||||||
import { AuthGuard } from '../auth/auth.guard';
|
import { AuthGuard } from '../auth/auth.guard';
|
||||||
import { UserService } from '../user/user.service';
|
import { UserService } from '../user/user.service';
|
||||||
|
import { AddToListDto } from './dto/add-to-list.dto';
|
||||||
|
import { UpdateProgressDto } from './dto/update-progress.dto';
|
||||||
|
import { UpdateStatusDto } from './dto/update-status.dto';
|
||||||
|
import { SetScoreDto } from './dto/set-score.dto';
|
||||||
|
|
||||||
@Controller('api/list')
|
@Controller('api/list')
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
@@ -37,7 +41,7 @@ export class ListController {
|
|||||||
@Post()
|
@Post()
|
||||||
async addToList(
|
async addToList(
|
||||||
@Req() req: any,
|
@Req() req: any,
|
||||||
@Body() body: { anilistId: number; status: ListStatus },
|
@Body() body: AddToListDto,
|
||||||
) {
|
) {
|
||||||
const user = await this.userService.findOrCreate({
|
const user = await this.userService.findOrCreate({
|
||||||
id: req.user.id,
|
id: req.user.id,
|
||||||
@@ -51,7 +55,7 @@ export class ListController {
|
|||||||
async updateProgress(
|
async updateProgress(
|
||||||
@Req() req: any,
|
@Req() req: any,
|
||||||
@Param('id', ParseIntPipe) id: number,
|
@Param('id', ParseIntPipe) id: number,
|
||||||
@Body() body: { progress: number },
|
@Body() body: UpdateProgressDto,
|
||||||
) {
|
) {
|
||||||
const user = await this.userService.findOrCreate({
|
const user = await this.userService.findOrCreate({
|
||||||
id: req.user.id,
|
id: req.user.id,
|
||||||
@@ -65,7 +69,7 @@ export class ListController {
|
|||||||
async updateStatus(
|
async updateStatus(
|
||||||
@Req() req: any,
|
@Req() req: any,
|
||||||
@Param('id', ParseIntPipe) id: number,
|
@Param('id', ParseIntPipe) id: number,
|
||||||
@Body() body: { status: ListStatus },
|
@Body() body: UpdateStatusDto,
|
||||||
) {
|
) {
|
||||||
const user = await this.userService.findOrCreate({
|
const user = await this.userService.findOrCreate({
|
||||||
id: req.user.id,
|
id: req.user.id,
|
||||||
@@ -79,7 +83,7 @@ export class ListController {
|
|||||||
async setScore(
|
async setScore(
|
||||||
@Req() req: any,
|
@Req() req: any,
|
||||||
@Param('id', ParseIntPipe) id: number,
|
@Param('id', ParseIntPipe) id: number,
|
||||||
@Body() body: { score: number },
|
@Body() body: SetScoreDto,
|
||||||
) {
|
) {
|
||||||
const user = await this.userService.findOrCreate({
|
const user = await this.userService.findOrCreate({
|
||||||
id: req.user.id,
|
id: req.user.id,
|
||||||
|
|||||||
Reference in New Issue
Block a user