From fc64303bfcd6d2d200a31cc22086e48c38ab274a Mon Sep 17 00:00:00 2001 From: Pedro Costa Date: Tue, 23 Sep 2025 15:07:40 -0300 Subject: [PATCH 1/2] test(nested): add whitelist forbidNonWhitelisted array ValidateNested regression test --- .../functional/nested-whitelist-array.spec.ts | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 test/functional/nested-whitelist-array.spec.ts diff --git a/test/functional/nested-whitelist-array.spec.ts b/test/functional/nested-whitelist-array.spec.ts new file mode 100644 index 000000000..438a30f08 --- /dev/null +++ b/test/functional/nested-whitelist-array.spec.ts @@ -0,0 +1,51 @@ +import { IsInt, IsOptional, IsString, ValidateNested } from '../../src/decorator/decorators'; +import { Validator } from '../../src/validation/Validator'; +import { ValidationTypes } from '../../src/validation/ValidationTypes'; + +const validator = new Validator(); + +describe('nested validation with whitelist on arrays', () => { + it('should not flag decorated child properties as non-whitelisted when using ValidateNested on array', () => { + class VehicleDump { + @IsString() + vin: string; + + @IsInt() + @IsOptional() + year?: number; + + @IsString() + @IsOptional() + make?: string; + } + + class Parent { + @ValidateNested({ each: true }) + vehicleDumps: VehicleDump[]; + } + + const parent = new Parent(); + parent.vehicleDumps = [ + { + vin: 'XXXXXXX', + year: 2005, + make: 'FREIGHTLINER', + }, + ]; + + return validator + .validate(parent, { whitelist: true, forbidNonWhitelisted: true }) + .then(errors => { + // Should not report whitelist errors for child properties like "vin" or "year" + // i.e., no constraint of type ValidationTypes.WHITELIST anywhere in the tree + const stringify = (e: any): string => JSON.stringify(e); + const flat = (errs: any[]): any[] => + errs.flatMap(e => [e, ...(e.children ? flat(e.children) : [])]); + const all = flat(errors); + const hasWhitelist = all.some(e => e.constraints && e.constraints[ValidationTypes.WHITELIST]); + expect(hasWhitelist).toBe(false); + }); + }); +}); + + From 962635e0bffd39a599458b72e071d9d9568fb2dd Mon Sep 17 00:00:00 2001 From: Pedro Costa Date: Tue, 23 Sep 2025 15:49:06 -0300 Subject: [PATCH 2/2] test(nested): cover whitelist+forbidNonWhitelisted with ValidateNested on arrays (refs #2089) --- package.json | 4 +- .../functional/nested-whitelist-array.spec.ts | 101 +++++++++++++----- 2 files changed, 74 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 0d43157a1..e50c5370a 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,8 @@ "prettier:check": "prettier --check \"**/*.{ts,md}\"", "lint:fix": "eslint --max-warnings 0 --fix --ext .ts src/", "lint:check": "eslint --max-warnings 0 --ext .ts src/", - "test": "jest --coverage --verbose", - "test:watch": "jest --watch", + "test": "jest --maxWorkers=50% --coverage --verbose", + "test:watch": "jest --maxWorkers=50% --watch", "test:ci": "jest --runInBand --no-cache --coverage --verbose" }, "dependencies": { diff --git a/test/functional/nested-whitelist-array.spec.ts b/test/functional/nested-whitelist-array.spec.ts index 438a30f08..ef631a3f2 100644 --- a/test/functional/nested-whitelist-array.spec.ts +++ b/test/functional/nested-whitelist-array.spec.ts @@ -1,51 +1,94 @@ -import { IsInt, IsOptional, IsString, ValidateNested } from '../../src/decorator/decorators'; +import { + IsArray, + IsInt, + IsNotEmpty, + IsOptional, + IsString, + ValidateNested, + ArrayMinSize, +} from '../../src/decorator/decorators'; import { Validator } from '../../src/validation/Validator'; import { ValidationTypes } from '../../src/validation/ValidationTypes'; +import { ValidationError } from '../../src/validation/ValidationError'; const validator = new Validator(); describe('nested validation with whitelist on arrays', () => { it('should not flag decorated child properties as non-whitelisted when using ValidateNested on array', () => { - class VehicleDump { + class PolVehicleDump { @IsString() + @IsNotEmpty() vin: string; @IsInt() @IsOptional() - year?: number; + year: number; @IsString() @IsOptional() make?: string; + + @IsString() + @IsOptional() + model?: string; + + @IsInt() + @IsOptional() + vehicleTypeCode?: number; } - class Parent { - @ValidateNested({ each: true }) - vehicleDumps: VehicleDump[]; + class PolDump { + @IsInt({ always: true }) + @IsNotEmpty({ always: true }) + versionNum: number; + + @IsString({ always: true }) + @IsNotEmpty({ always: true }) + polNumber: string; + + @IsString({ always: true }) + @IsNotEmpty({ always: true }) + effectiveDate: string; + + @IsString({ always: true }) + @IsNotEmpty({ always: true }) + expirationDate: string; + + @IsArray({ always: true }) + @ArrayMinSize(0, { always: true }) + @IsOptional({ always: true }) + @ValidateNested({ always: true, each: true }) + polVehicleDumps?: PolVehicleDump[]; } - const parent = new Parent(); - parent.vehicleDumps = [ - { - vin: 'XXXXXXX', - year: 2005, - make: 'FREIGHTLINER', - }, - ]; - - return validator - .validate(parent, { whitelist: true, forbidNonWhitelisted: true }) - .then(errors => { - // Should not report whitelist errors for child properties like "vin" or "year" - // i.e., no constraint of type ValidationTypes.WHITELIST anywhere in the tree - const stringify = (e: any): string => JSON.stringify(e); - const flat = (errs: any[]): any[] => - errs.flatMap(e => [e, ...(e.children ? flat(e.children) : [])]); - const all = flat(errors); - const hasWhitelist = all.some(e => e.constraints && e.constraints[ValidationTypes.WHITELIST]); - expect(hasWhitelist).toBe(false); - }); - }); -}); + class PolDumpReq { + @IsArray({ always: true }) + @ArrayMinSize(1, { always: true }) + @ValidateNested({ always: true, each: true }) + polDumps: PolDump[]; + } + const vehicle = new PolVehicleDump(); + vehicle.vin = 'XXXXXXX'; + vehicle.year = 2005; + vehicle.make = 'FREIGHTLINER'; + const dump = new PolDump(); + dump.versionNum = 1; + dump.polNumber = '123123'; + dump.effectiveDate = '2020-12-10'; + dump.expirationDate = '2021-12-10'; + dump.polVehicleDumps = [vehicle]; + + const polDumpReq = new PolDumpReq(); + polDumpReq.polDumps = [dump]; + + return validator.validate(polDumpReq, { whitelist: true, forbidNonWhitelisted: true }).then(errors => { + const flat = (errs: ReadonlyArray): ValidationError[] => + errs.flatMap((e: ValidationError) => [e, ...(e.children ? flat(e.children) : [])]); + const all = flat(errors); + const hasWhitelist = all.some(e => e.constraints && e.constraints[ValidationTypes.WHITELIST]); + expect(hasWhitelist).toBe(false); + }); + }); +});