From 05160509ec9e58475d7ab0dfcb9a524fba8fd142 Mon Sep 17 00:00:00 2001 From: liuyi Date: Wed, 28 May 2025 22:58:00 +0800 Subject: [PATCH] add constraint --- src/modules/content/dtos/category.dto.ts | 14 ++++++ src/modules/content/dtos/comment.dto.ts | 6 +++ src/modules/content/dtos/post.dto.ts | 10 ++++ src/modules/content/dtos/tag.dto.ts | 2 + .../constraints/tree.unique.constraint.ts | 2 +- .../tree.unique.exist.constraint.ts | 2 +- src/modules/core/providers/app.pipe.ts | 2 - test/all-case.test.ts | 49 +++++++++++++++++-- 8 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/modules/content/dtos/category.dto.ts b/src/modules/content/dtos/category.dto.ts index c0f9997..5e6dfc4 100644 --- a/src/modules/content/dtos/category.dto.ts +++ b/src/modules/content/dtos/category.dto.ts @@ -12,9 +12,14 @@ import { } from 'class-validator'; import { toNumber } from 'lodash'; +import { IsDataExist } from '@/modules/core/constraints/data.exist.constraint'; +import { IsTreeUnique } from '@/modules/core/constraints/tree.unique.constraint'; +import { IsTreeUniqueExist } from '@/modules/core/constraints/tree.unique.exist.constraint'; import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator'; import { PaginateOptions } from '@/modules/database/types'; +import { CategoryEntity } from '../entities'; + @DtoValidation({ type: 'query' }) export class QueryCategoryDto implements PaginateOptions { @Transform(({ value }) => toNumber(value)) @@ -32,6 +37,14 @@ export class QueryCategoryDto implements PaginateOptions { @DtoValidation({ groups: ['create'] }) export class CreateCategoryDto { + @IsTreeUnique(CategoryEntity, { + groups: ['create'], + message: 'The Category names are duplicated', + }) + @IsTreeUniqueExist(CategoryEntity, { + groups: ['update'], + message: 'The Category names are duplicated', + }) @MaxLength(25, { always: true, message: 'The length of the category name shall not exceed $constraint1', @@ -40,6 +53,7 @@ export class CreateCategoryDto { @IsOptional({ groups: ['update'] }) name: string; + @IsDataExist(CategoryEntity, { always: true, message: 'The parent category does not exist' }) @IsUUID(undefined, { always: true, message: 'The format of the parent category ID is incorrect.', diff --git a/src/modules/content/dtos/comment.dto.ts b/src/modules/content/dtos/comment.dto.ts index 61c447f..c7a5571 100644 --- a/src/modules/content/dtos/comment.dto.ts +++ b/src/modules/content/dtos/comment.dto.ts @@ -12,9 +12,12 @@ import { } from 'class-validator'; import { toNumber } from 'lodash'; +import { IsDataExist } from '@/modules/core/constraints/data.exist.constraint'; import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator'; import { PaginateOptions } from '@/modules/database/types'; +import { CommentEntity, PostEntity } from '../entities'; + @DtoValidation({ type: 'query' }) export class QueryCommentDto implements PaginateOptions { @Transform(({ value }) => toNumber(value)) @@ -29,6 +32,7 @@ export class QueryCommentDto implements PaginateOptions { @IsOptional() limit = 10; + @IsDataExist(PostEntity, { message: 'The post does not exist' }) @IsUUID(undefined, { message: 'The ID format is incorrect' }) @IsOptional() post?: string; @@ -43,10 +47,12 @@ export class CreateCommentDto { @IsNotEmpty({ message: '' }) body: string; + @IsDataExist(PostEntity, { message: 'The post does not exist' }) @IsUUID(undefined, { message: 'The ID format is incorrect' }) @IsDefined({ message: 'The ID must be specified' }) post: string; + @IsDataExist(CommentEntity, { message: 'The parent comment does not exist' }) @IsUUID(undefined, { message: 'The ID format is incorrect', always: true }) @ValidateIf((value) => value.parent !== null && value.parent) @IsOptional({ always: true }) diff --git a/src/modules/content/dtos/post.dto.ts b/src/modules/content/dtos/post.dto.ts index b91f8cd..8757cb7 100644 --- a/src/modules/content/dtos/post.dto.ts +++ b/src/modules/content/dtos/post.dto.ts @@ -17,10 +17,13 @@ import { import { isNil, toNumber } from 'lodash'; import { PostOrder } from '@/modules/content/constants'; +import { IsDataExist } from '@/modules/core/constraints/data.exist.constraint'; import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator'; import { toBoolean } from '@/modules/core/helpers'; import { PaginateOptions } from '@/modules/database/types'; +import { CategoryEntity, TagEntity } from '../entities'; + @DtoValidation({ type: 'query' }) export class QueryPostDto implements PaginateOptions { @Transform(({ value }) => toBoolean(value)) @@ -46,6 +49,7 @@ export class QueryPostDto implements PaginateOptions { @IsOptional() limit = 10; + @IsDataExist(CategoryEntity, { always: true, message: 'The category does not exist' }) @IsUUID(undefined, { message: 'The ID format is incorrect' }) @IsOptional() category?: string; @@ -96,6 +100,7 @@ export class CreatePostDto { @IsOptional({ always: true }) customOrder?: number; + @IsDataExist(CategoryEntity, { always: true, message: 'The category does not exist' }) @IsUUID(undefined, { always: true, message: 'The ID format is incorrect', @@ -103,6 +108,11 @@ export class CreatePostDto { @IsOptional({ always: true }) category?: string; + @IsDataExist(TagEntity, { + always: true, + each: true, + message: 'The tag does not exist', + }) @IsUUID(undefined, { always: true, each: true, diff --git a/src/modules/content/dtos/tag.dto.ts b/src/modules/content/dtos/tag.dto.ts index 2d1363a..aa1bf08 100644 --- a/src/modules/content/dtos/tag.dto.ts +++ b/src/modules/content/dtos/tag.dto.ts @@ -12,6 +12,7 @@ import { import { toNumber } from 'lodash'; import { IsUnique } from '@/modules/core/constraints/unique.constraint'; +import { IsUniqueExist } from '@/modules/core/constraints/unique.exist.constraint'; import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator'; import { PaginateOptions } from '@/modules/database/types'; @@ -35,6 +36,7 @@ export class QueryTagDto implements PaginateOptions { @DtoValidation({ groups: ['create'] }) export class CreateTagDto { @IsUnique(TagEntity, { groups: ['create'], message: 'The label names are repeated' }) + @IsUniqueExist(TagEntity, { groups: ['update'], message: 'The label names are repeated' }) @MaxLength(255, { always: true, message: 'The maximum length of the label name is $constraint1', diff --git a/src/modules/core/constraints/tree.unique.constraint.ts b/src/modules/core/constraints/tree.unique.constraint.ts index 97d088e..07dfc3c 100644 --- a/src/modules/core/constraints/tree.unique.constraint.ts +++ b/src/modules/core/constraints/tree.unique.constraint.ts @@ -59,7 +59,7 @@ export class TreeUniqueConstraint implements ValidatorConstraintInterface { } } -export function IsUnique( +export function IsTreeUnique( params: ObjectType | Condition, validationOptions?: ValidationOptions, ) { diff --git a/src/modules/core/constraints/tree.unique.exist.constraint.ts b/src/modules/core/constraints/tree.unique.exist.constraint.ts index 24741e4..258e517 100644 --- a/src/modules/core/constraints/tree.unique.exist.constraint.ts +++ b/src/modules/core/constraints/tree.unique.exist.constraint.ts @@ -73,7 +73,7 @@ export class TreeUniqueExistContraint implements ValidatorConstraintInterface { } } -export function IsUniqueExist( +export function IsTreeUniqueExist( params: ObjectType | Condition, validationOptions?: ValidationOptions, ) { diff --git a/src/modules/core/providers/app.pipe.ts b/src/modules/core/providers/app.pipe.ts index 55ca16d..33216e9 100644 --- a/src/modules/core/providers/app.pipe.ts +++ b/src/modules/core/providers/app.pipe.ts @@ -35,8 +35,6 @@ export class AppPipe extends ValidationPipe { }), ) : value; - console.log(value); - console.log(toValidate); try { let result = await super.transform(toValidate, metadata); if (typeof result.transform === 'function') { diff --git a/test/all-case.test.ts b/test/all-case.test.ts index a7f4ee0..528cd3f 100644 --- a/test/all-case.test.ts +++ b/test/all-case.test.ts @@ -6,8 +6,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { pick } from 'lodash'; import { DataSource } from 'typeorm'; -import { database } from '@/config'; -import { ContentModule } from '@/modules/content/content.module'; +import { AppModule } from '@/app.module'; import { CategoryEntity, CommentEntity, PostEntity, TagEntity } from '@/modules/content/entities'; import { CategoryRepository, @@ -15,7 +14,6 @@ import { PostRepository, TagRepository, } from '@/modules/content/repositories'; -import { DatabaseModule } from '@/modules/database/database.module'; import { generateRandomNumber, generateUniqueRandomNumbers } from './generate-mock-data'; import { categoriesData, commentData, INIT_DATA, postData, tagData } from './test-data'; @@ -35,7 +33,7 @@ describe('category test', () => { beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ - imports: [ContentModule, DatabaseModule.forRoot(database)], + imports: [AppModule], }).compile(); app = module.createNestApplication(new FastifyAdapter()); await app.init(); @@ -96,6 +94,49 @@ describe('category test', () => { it('repository init', () => { expect(categoryRepository).toBeDefined(); }); + + // const category1: CreateCategoryDto = {}; + it('create category without name', async () => { + const result = await app.inject({ + method: 'POST', + url: '/category', + body: {}, + }); + expect(result.json()).toEqual({ + message: [ + 'The classification name cannot be empty', + 'The length of the category name shall not exceed 25', + ], + error: 'Bad Request', + statusCode: 400, + }); + }); + + it('create category with long name', async () => { + const result = await app.inject({ + method: 'POST', + url: '/category', + body: { name: 'A'.repeat(30) }, + }); + expect(result.json()).toEqual({ + message: ['The length of the category name shall not exceed 25'], + error: 'Bad Request', + statusCode: 400, + }); + }); + + it('create category with same name', async () => { + const result = await app.inject({ + method: 'POST', + url: '/category', + body: { name: 'A'.repeat(30) }, + }); + expect(result.json()).toEqual({ + message: ['The length of the category name shall not exceed 25'], + error: 'Bad Request', + statusCode: 400, + }); + }); }); describe('tag test', () => {