diff --git a/src/modules/products/dto/create-product.dto.ts b/src/modules/products/dto/create-product.dto.ts index 5397e47..ac92a1c 100644 --- a/src/modules/products/dto/create-product.dto.ts +++ b/src/modules/products/dto/create-product.dto.ts @@ -1,3 +1,21 @@ -import { ProductEntity } from '../entities/product.entity'; +import { IsInt, IsString, IsOptional, Min, IsNumber } from 'class-validator'; -export class CreateProductDto extends ProductEntity {} +export class CreateProductDto { + + @IsInt() + sellerId: number; // 사용자는 판매자의 ID만 제공합니다. 연관된 엔터티는 서비스 레벨에서 처리됩니다. + + @IsString() + name: string; + + @IsString() + price: string; + + @IsNumber() + @Min(0) + quantity: number; + + @IsString() + @IsOptional() // 선택적 필드입니다. + description?: string; +} \ No newline at end of file diff --git a/src/modules/products/entities/product.entity.ts b/src/modules/products/entities/product.entity.ts index 5af4f20..5aea718 100644 --- a/src/modules/products/entities/product.entity.ts +++ b/src/modules/products/entities/product.entity.ts @@ -1,8 +1,28 @@ import { CommonColumns } from '@/common/entities/common-columns'; -import { Column, Entity } from 'typeorm'; +import { UserEntity } from '@/modules/users/entities/user.entity'; +import { Column, PrimaryGeneratedColumn, ManyToMany, JoinTable, ManyToOne, JoinColumn, Entity } from 'typeorm'; @Entity({ name: 'products' }) export class ProductEntity extends CommonColumns { + + @PrimaryGeneratedColumn() + id: number; + + @ManyToOne(() => UserEntity, seller => seller.products) + @JoinColumn({ name: 'seller_id' }) // This sets up the foreign key + seller: UserEntity; + @Column() name: string; + + @Column() + price: string; + + @Column() + quantity: number; + + @Column({ nullable: true }) + description?: string; + + } diff --git a/src/modules/products/products.service.spec.ts b/src/modules/products/products.service.spec.ts index 6e88ec5..c27b43b 100644 --- a/src/modules/products/products.service.spec.ts +++ b/src/modules/products/products.service.spec.ts @@ -30,8 +30,52 @@ describe('ProductsService', () => { service = module.get(ProductsService); }); + //post 하기 전 유효성 검사(통과케이스) + it('should validate product upload and return true', async() => { + const ProductInfo = {id: 1, name: 'Sample Product', price: 100} + mockRepo.create.mockResolvedValue(storedProductInfo); - it('should be defined', () => { - expect(service).toBeDefined(); + const isValid = await service.add_product_validation(storedProductInfo) + expect(isValid).toBe(true); }); + //post 유효성검사 : 제품 이름이 빈 문자열이면 validation Error + it('should throw an error if product name is empty', async() => { + const productWithEmptyName = { name: '', price: 100 }; + const storedProductInfo = { id: 1, ...productWithEmptyName }; + mockRepo.create.mockResolvedValue(storedProductInfo); + + await expect(service.add_product_validation(storedProductInfo)).rejects.toThrow('Validation Error: Product name is required.'); +}); + // post 유효성검사 : 가격이 음수면 validation Error + it('should throw an error if product price is minus', async() => { + const productWithMinusPrice = {name: 'Sample Product', price: -1}; + const storedProductInfo = {id :1, ...productWithMinusPrice} + mockRepo.create.mockResolvedValue(storedProductInfo); + + await expect(service.add_product_validation(storedProductInfo)).rejects.toThrow('Validation Error:product price is minus'); + }); + // post 유효성검사 : 가격이 정수가 아니면 validation Error + it('should throw an error if product price is not a natural number', async() => { + const productWithDecimalPrice = {name: 'Sample Product', price: 100.5}; + const storedProductInfo = {id :1, ...productWithDecimalPrice} + mockRepo.create.mockResolvedValue(storedProductInfo); + + await expect(service.add_product_validation(storedProductInfo)).rejects.toThrow('Validation Error:product price is not natural number'); + }); + // post 유효성검사 : description 멘트가 너무 길면 validation Error + it('should throw an error if product description is too long', async() => { + const longDescription = 'a'.repeat(1001); // 1000자 이상이면 에러라고 가정 + const productWithLongDescription = { name: 'Sample Product', price: 100, description: longDescription }; + const storedProductInfo = { id: 1, ...productWithLongDescription }; + mockRepo.create.mockResolvedValue(storedProductInfo); + + await expect(service.add_product_validation(storedProductInfo)).rejects.toThrow('Validation Error: Product description is too long.'); +}); + // post 유효성검사: 카테고리가 실제로 데이터베이스 내에 존재하지 않을때 에러 발생 + it('should throw an error if product category does not exist', async() => { + const productWithNonExistingCategory = { name: 'Sample Product', price: 100, category: 'UnknownCategory' }; + mockRepo.create.mockResolvedValue(productWithNonExistingCategory); + + await expect(service.add_product_validation(productWithNonExistingCategory)).rejects.toThrow('Validation Error: Provided category does not exist.'); +}); }); diff --git a/src/modules/products/products.service.ts b/src/modules/products/products.service.ts index 8aa1132..6c9dc73 100644 --- a/src/modules/products/products.service.ts +++ b/src/modules/products/products.service.ts @@ -35,8 +35,8 @@ export class ProductsService { return [newSomething, newOtherThing]; } - private some_validation_post() { - return true; + private product_add_validation() { + return false; } private some_validation_comment() { @@ -47,6 +47,16 @@ export class ProductsService { * 트랜젝션이 사용 될 때, 우리가 주로 사용하기로 한 방식 */ create_with_component(createProductDto: CreateProductDto) { + + // 1. 어떤 로직으로 게시글을 작성하고 검증한다 + this.some_validation_post(); + // 2. 다른 로직으로 댓글을 작성하고 검증한다. + this.some_validation_comment(); + return this.comp.createSomethingAndOther(createProductDto); + } + + create_with_component(createProductDto: CreateProductDto) { + // 1. 어떤 로직으로 게시글을 작성하고 검증한다 this.some_validation_post(); // 2. 다른 로직으로 댓글을 작성하고 검증한다.