import * as ClassTransformer from 'class-transformer' ; import * as ClassValidator from 'class-validator' ; import { filteredByDto , getDtoErrors , getTransformed , validateDto , } from './dto-validation' ; describe ( 'DtoValidation' , ( ) => { beforeEach ( ( ) => { jest . resetAllMocks ( ) ; jest . restoreAllMocks ( ) ; } ) ; describe ( 'getTransformed' , ( ) => { it ( 'should call "plainToInstance" from "class-transformer" with the given arguments' , ( ) => { jest . spyOn ( ClassTransformer , 'plainToInstance' ) ; class TestClass { } const plain = { foo : 'bar' } ; const resultValidationOptions = undefined ; getTransformed ( plain , TestClass ) ; expect ( ClassTransformer . plainToInstance ) . toHaveBeenCalledTimes ( 1 ) ; expect ( ClassTransformer . plainToInstance ) . toHaveBeenCalledWith ( TestClass , plain , resultValidationOptions ) ; } ) ; } ) ; describe ( 'validateDto' , ( ) => { it ( 'should call "plainToInstance" from "class-transformer" through "getTransformed" call' , async ( ) => { jest . spyOn ( ClassTransformer , 'plainToInstance' ) ; jest . spyOn ( ClassValidator , 'validate' ) . mockImplementation ( async ( ) => [ ] ) ; class TestClass { } const plain = { foo : 'bar' } ; const validationOptions = { whitelist : false } ; const resultValidationOptions = undefined ; await validateDto ( plain , TestClass , validationOptions ) ; expect ( ClassTransformer . plainToInstance ) . toHaveBeenCalledTimes ( 1 ) ; expect ( ClassTransformer . plainToInstance ) . toHaveBeenCalledWith ( TestClass , plain , resultValidationOptions ) ; } ) ; it ( 'should call "plainToInstance" from "class-transformer" through "getTransformed" call with full options' , async ( ) => { jest . spyOn ( ClassTransformer , 'plainToInstance' ) ; jest . spyOn ( ClassValidator , 'validate' ) . mockImplementation ( async ( ) => [ ] ) ; class TestClass { } const plain = { foo : 'bar' } ; const validationOptions = { whitelist : false } ; const transformOptions = { groups : [ 'hello' ] } ; await validateDto ( plain , TestClass , validationOptions , transformOptions ) ; expect ( ClassTransformer . plainToInstance ) . toHaveBeenCalledTimes ( 1 ) ; expect ( ClassTransformer . plainToInstance ) . toHaveBeenCalledWith ( TestClass , plain , transformOptions ) ; } ) ; it ( 'should call "validate" from "class-validator" with given Dto' , async ( ) => { jest . spyOn ( ClassValidator , 'validate' ) ; class TestClass { } const plain = { foo : 'bar' } ; const validationOptions = { whitelist : false } ; await validateDto ( plain , TestClass , validationOptions ) ; expect ( ClassValidator . validate ) . toHaveBeenCalledTimes ( 1 ) ; expect ( ClassValidator . validate ) . toHaveBeenCalledWith ( plain , validationOptions ) ; } ) ; it ( 'should return an empty array if no error is found' , async ( ) => { jest . spyOn ( ClassTransformer , 'plainToInstance' ) ; jest . spyOn ( ClassValidator , 'validate' ) . mockResolvedValue ( [ ] ) ; class TestClass { } const plain = { foo : 'bar' } ; const validationOptions = { whitelist : false } ; const errors = await validateDto ( plain , TestClass , validationOptions ) ; expect ( errors ) . toBeInstanceOf ( Array ) ; expect ( errors . length ) . toStrictEqual ( 0 ) ; } ) ; it ( 'should return the "validate" call result' , async ( ) => { const validateResult = [ { property : 'foo' , constraints : { Bar : 'oops !' , Rab : 'oops too !' , } , children : [ ] , } , ] ; jest . spyOn ( ClassTransformer , 'plainToInstance' ) ; jest . spyOn ( ClassValidator , 'validate' ) . mockResolvedValue ( validateResult ) ; class TestClass { } const plain = { foo : 'bar' } ; const validationOptions = { whitelist : false } ; const errors = await validateDto ( plain , TestClass , validationOptions ) ; expect ( errors ) . toBeInstanceOf ( Array ) ; expect ( errors . length ) . toStrictEqual ( 1 ) ; expect ( errors ) . toMatchObject ( validateResult ) ; } ) ; } ) ; describe ( 'filteredByDto' , ( ) => { it ( 'should call "plainToInstance" and "instanceToPlain" from "class-transformer"' , async ( ) => { class TestClass { } const plain = { foo : 'bar' } ; const validationOptions = { whitelist : false } ; const resultValidationOptions = undefined ; jest . spyOn ( ClassTransformer , 'plainToInstance' ) . mockReturnValueOnce ( plain ) ; jest . spyOn ( ClassValidator , 'validate' ) . mockResolvedValueOnce ( [ ] ) ; jest . spyOn ( ClassTransformer , 'instanceToPlain' ) . mockReturnValueOnce ( plain as any ) ; const result = await filteredByDto ( plain , TestClass , validationOptions ) ; expect ( result ) . toEqual ( { errors : [ ] , result : plain } ) ; expect ( ClassTransformer . plainToInstance ) . toHaveBeenCalledTimes ( 1 ) ; expect ( ClassTransformer . plainToInstance ) . toHaveBeenCalledWith ( TestClass , plain , resultValidationOptions ) ; expect ( ClassTransformer . instanceToPlain ) . toHaveBeenCalledTimes ( 1 ) ; expect ( ClassTransformer . instanceToPlain ) . toHaveBeenCalledWith ( plain ) ; } ) ; it ( 'should call "plainToInstance" and "instanceToPlain" from "class-transformer" with full options' , async ( ) => { class TestClass { } const plain = { foo : 'bar' } ; const validationOptions = { whitelist : false } ; const transformOptions = { groups : [ 'hello' ] } ; jest . spyOn ( ClassTransformer , 'plainToInstance' ) ; jest . spyOn ( ClassValidator , 'validate' ) . mockResolvedValueOnce ( [ ] ) ; jest . spyOn ( ClassTransformer , 'instanceToPlain' ) . mockReturnValueOnce ( plain as any ) ; const result = await filteredByDto ( plain , TestClass , validationOptions , transformOptions ) ; expect ( result ) . toEqual ( { errors : [ ] , result : plain } ) ; expect ( ClassTransformer . plainToInstance ) . toHaveBeenCalledTimes ( 1 ) ; expect ( ClassTransformer . plainToInstance ) . toHaveBeenCalledWith ( TestClass , plain , transformOptions ) ; expect ( ClassTransformer . instanceToPlain ) . toHaveBeenCalledTimes ( 1 ) ; expect ( ClassTransformer . instanceToPlain ) . toHaveBeenCalledWith ( plain ) ; } ) ; it ( 'should call "validate" from "class-validator" with given Dto' , async ( ) => { jest . spyOn ( ClassValidator , 'validate' ) ; class TestClass { } const plain = { foo : 'bar' } ; const validationOptions = { whitelist : false } ; await filteredByDto ( plain , TestClass , validationOptions ) ; expect ( ClassValidator . validate ) . toHaveBeenCalledTimes ( 1 ) ; expect ( ClassValidator . validate ) . toHaveBeenCalledWith ( plain , validationOptions ) ; } ) ; it ( 'should return data if no error is found' , async ( ) => { class TestClass { } const plain = { foo : 'bar' } ; const validationOptions = { whitelist : false } ; jest . spyOn ( ClassTransformer , 'plainToInstance' ) ; jest . spyOn ( ClassValidator , 'validate' ) . mockResolvedValue ( [ ] ) ; jest . spyOn ( ClassTransformer , 'instanceToPlain' ) . mockReturnValueOnce ( plain as any ) ; const { errors , result } = await filteredByDto ( plain , TestClass , validationOptions ) ; expect ( errors ) . toBeInstanceOf ( Array ) ; expect ( errors . length ) . toStrictEqual ( 0 ) ; expect ( result ) . toEqual ( plain ) ; } ) ; it ( 'should return the "validate" call result' , async ( ) => { const validateResult = [ { children : [ ] , constraints : { Bar : 'oops !' , Rab : 'oops too !' , } , property : 'foo' , } , ] ; class TestClass { } const plain = { foo : 'bar' } ; const validationOptions = { whitelist : false } ; jest . spyOn ( ClassTransformer , 'plainToInstance' ) ; jest . spyOn ( ClassValidator , 'validate' ) . mockResolvedValue ( validateResult ) ; const instanceToPlainMock = jest . spyOn ( ClassTransformer , 'instanceToPlain' ) . mockReturnValueOnce ( plain as any ) ; const { errors , result } = await filteredByDto ( plain , TestClass , validationOptions ) ; expect ( errors ) . toBeInstanceOf ( Array ) ; expect ( errors . length ) . toStrictEqual ( 1 ) ; expect ( errors ) . toMatchObject ( validateResult ) ; expect ( result ) . toEqual ( null ) ; expect ( instanceToPlainMock ) . toHaveBeenCalledTimes ( 0 ) ; } ) ; } ) ; describe ( 'getAllPropertiesErrors' , ( ) => { it ( 'should return "null" if no error is found' , ( ) => { const validationErrors = [ ] ; const errors = getDtoErrors ( validationErrors ) ; expect ( errors ) . toStrictEqual ( null ) ; } ) ; it ( 'should return an error if a constraint fails' , ( ) => { const validationErrors = [ { property : 'foo' , constraints : { Bar : 'oops !' , } , children : [ ] , } , ] ; const error = getDtoErrors ( validationErrors ) ; expect ( error ) . toBeInstanceOf ( Error ) ; } ) ; it ( 'should return an error with "message" containing all failed constraints formatted "<property>: <failed-constraint>" and "\n" separated' , ( ) => { const validationErrors = [ { property : 'foo' , constraints : { Bar : 'oops !' , Rab : 'oops too !' , } , children : [ ] , } , { property : 'thisOne' , constraints : { ThaOne : 'oops too too !' , ThatOtherOne : 'oops too too too !' , } , children : [ ] , } , ] ; const failedConstraints = [ 'foo: Bar' , 'foo: Rab' , 'thisOne: ThaOne' , 'thisOne: ThatOtherOne' , ] ; const expectedErrorMessage = failedConstraints . join ( '\n' ) ; const error = getDtoErrors ( validationErrors ) ; expect ( error ) . toBeInstanceOf ( Error ) ; expect ( error . message ) . toStrictEqual ( expectedErrorMessage ) ; } ) ; it ( 'should work with nested validation and format failed contraints "<lvl1-property>.<...>.<levelN-property>: <failed-constraint>"' , ( ) => { const validationErrors = [ { property : 'foo' , constraints : { Bar : 'oops !' , Rab : 'oops too !' , } , children : [ { property : 'thisOne' , constraints : { ThaOne : 'oops too too !' , ThatOtherOne : 'oops too too too !' , } , children : [ { property : 'name' , constraints : { IsString : 'oops too too too too !' , } , children : [ ] , } , { property : 'underwear' , constraints : { IsString : 'oops too too too too too !' , } , children : [ ] , } , ] , } , ] , } , ] ; const failedConstraints = [ 'foo: Bar' , 'foo: Rab' , 'foo.thisOne: ThaOne' , 'foo.thisOne: ThatOtherOne' , 'foo.thisOne.name: IsString' , 'foo.thisOne.underwear: IsString' , ] ; const expectedErrorMessage = failedConstraints . join ( '\n' ) ; const error = getDtoErrors ( validationErrors ) ; expect ( error ) . toBeInstanceOf ( Error ) ; expect ( error . message ) . toStrictEqual ( expectedErrorMessage ) ; } ) ; it ( 'should work with no nested validation and field without contraints' , ( ) => { const validationErrors = [ { property : 'foo' , } , ] ; const error = getDtoErrors ( validationErrors as any ) ; expect ( error ) . toBe ( null ) ; } ) ; } ) ; } ) ;