ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: code-infra-01 <[email protected]>
105 lines
4.1 KiB
TypeScript
105 lines
4.1 KiB
TypeScript
/**
|
|
* Boot-time ValidationPipe metatype self-check (#1391).
|
|
*
|
|
* The check exists to fail loud at boot when the global pipe cannot see a
|
|
* guarded DTO's decorated properties — the #436 class-erasure signature and
|
|
* its dependency-graph cousins. Red/green arms:
|
|
*
|
|
* GREEN real module state: BootstrapSetupDto's three properties are
|
|
* decorated and visible through the globalThis-shared storage.
|
|
* RED a control class with NO decorators (the erasure shape): the
|
|
* check throws PipeMetatypeCheckError naming every property.
|
|
* RED-2 a control where one property is decorated and two are not: the
|
|
* error names exactly the missing two — the miss list is precise,
|
|
* not a blanket failure.
|
|
*/
|
|
import { describe, expect, it } from 'vitest';
|
|
import { IsString } from 'class-validator';
|
|
import {
|
|
assertValidationPipeSeesDtoDecorators,
|
|
PipeMetatypeCheckError,
|
|
} from './validation-pipe-check.js';
|
|
|
|
describe('assertValidationPipeSeesDtoDecorators (#1391 boot check)', () => {
|
|
it('GREEN: passes on real module state (decorated DTO visible to the pipe)', () => {
|
|
expect(() => assertValidationPipeSeesDtoDecorators()).not.toThrow();
|
|
});
|
|
|
|
it('RED control: a class whose properties lost their decorators throws, naming them', async () => {
|
|
// Simulate metatype erasure: an undecorated class standing where a
|
|
// decorated DTO should be. Redefine the guard table for the test by
|
|
// importing the module and pointing its table at the eroded class —
|
|
// the check reads the table at call time, so a fresh module instance
|
|
// with a swapped table reproduces the boot failure deterministically.
|
|
const { PIPE_GUARDED_DTOS } = await import('./validation-pipe-check.js');
|
|
|
|
class ErodedDto {
|
|
name?: string;
|
|
email?: string;
|
|
password?: string;
|
|
}
|
|
|
|
const original = PIPE_GUARDED_DTOS[0];
|
|
expect(original).toBeDefined();
|
|
// Swap in the eroded target (same declared properties, zero decorators).
|
|
(
|
|
PIPE_GUARDED_DTOS as unknown as Array<{ name: string; target: object; properties: string[] }>
|
|
).splice(0, PIPE_GUARDED_DTOS.length, {
|
|
name: 'ErodedDto',
|
|
target: ErodedDto,
|
|
properties: ['name', 'email', 'password'],
|
|
});
|
|
|
|
try {
|
|
expect(() => assertValidationPipeSeesDtoDecorators()).toThrow(PipeMetatypeCheckError);
|
|
try {
|
|
assertValidationPipeSeesDtoDecorators();
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : '';
|
|
expect(message).toContain('ErodedDto.name');
|
|
expect(message).toContain('ErodedDto.email');
|
|
expect(message).toContain('ErodedDto.password');
|
|
}
|
|
} finally {
|
|
// Restore real module state for any later test in this file.
|
|
(PIPE_GUARDED_DTOS as unknown as unknown[]).splice(0, PIPE_GUARDED_DTOS.length, original);
|
|
}
|
|
// And confirm the restore is real.
|
|
expect(() => assertValidationPipeSeesDtoDecorators()).not.toThrow();
|
|
});
|
|
|
|
it('RED-2 control: a partially decorated class names exactly the missing properties', async () => {
|
|
const { PIPE_GUARDED_DTOS } = await import('./validation-pipe-check.js');
|
|
|
|
class HalfErodedDto {
|
|
@IsString()
|
|
name?: string;
|
|
email?: string;
|
|
password?: string;
|
|
}
|
|
|
|
const original = PIPE_GUARDED_DTOS[0];
|
|
(
|
|
PIPE_GUARDED_DTOS as unknown as Array<{ name: string; target: object; properties: string[] }>
|
|
).splice(0, PIPE_GUARDED_DTOS.length, {
|
|
name: 'HalfErodedDto',
|
|
target: HalfErodedDto,
|
|
properties: ['name', 'email', 'password'],
|
|
});
|
|
|
|
try {
|
|
try {
|
|
assertValidationPipeSeesDtoDecorators();
|
|
expect.unreachable('partially decorated DTO must fail the boot check');
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : '';
|
|
expect(message).toContain('HalfErodedDto.email');
|
|
expect(message).toContain('HalfErodedDto.password');
|
|
expect(message).not.toContain('HalfErodedDto.name has no');
|
|
}
|
|
} finally {
|
|
(PIPE_GUARDED_DTOS as unknown as unknown[]).splice(0, PIPE_GUARDED_DTOS.length, original);
|
|
}
|
|
});
|
|
});
|