jest.mock('@nestjs/common', () => ({ BadRequestException: class BadRequestException extends Error { constructor(message: string) { super(message); this.name = 'BadRequestException'; } }, Injectable: () => (target: any) => target, PipeTransform: class {}, })); jest.mock('class-validator', () => ({ isDefined: (v: any) => v !== undefined && v !== null, isEnum: (value: any, entity: any) => Object.values(entity).includes(value), })); jest.mock('@paralleldrive/cuid2', () => ({ createId: jest.fn().mockReturnValue('generated-cuid'), isCuid: jest.fn((v: string) => v === 'valid-cuid-value'), })); import { AppendIdPipe } from '../pipe/append-id.pipe'; import { ValidateEnumPipe } from '../pipe/validate-enum.pipe'; import { ValidateIdPipe } from '../pipe/validate-id.pipe'; import { ValidateSearchPipe } from '../pipe/validate-search.pipe'; enum TestEnum { Photo = 'Photo', Video = 'Video', Document = 'Document', } describe('AppendIdPipe', () => { let pipe: AppendIdPipe; beforeEach(() => { pipe = new AppendIdPipe('MediaId'); }); it('appends the named id field to the value', () => { const result = pipe.transform({ Title: 'Test' }); expect(result).toEqual({ Title: 'Test', MediaId: 'generated-cuid' }); }); it('preserves existing fields', () => { const result = pipe.transform({ a: 1, b: 2 }); expect(result.a).toBe(1); expect(result.b).toBe(2); expect(result.MediaId).toBe('generated-cuid'); }); it('works with an empty object', () => { const result = pipe.transform({}); expect(result).toHaveProperty('MediaId', 'generated-cuid'); }); }); describe('ValidateEnumPipe', () => { let pipe: ValidateEnumPipe; beforeEach(() => { pipe = new ValidateEnumPipe(TestEnum); }); it('returns the enum value when valid', () => { const result = pipe.transform('Photo'); expect(result).toBe('Photo'); }); it('throws BadRequestException for an invalid enum value', () => { expect(() => pipe.transform('Invalid')).toThrow(); }); it('returns the original value when undefined', () => { const result = pipe.transform(undefined as any); expect(result).toBeUndefined(); }); it('validates each item when value is an array', () => { const result = pipe.transform(['Photo', 'Video'] as any); expect(result).toEqual(['Photo', 'Video']); }); it('throws when an array contains an invalid value', () => { expect(() => pipe.transform(['Photo', 'Bad'] as any)).toThrow(); }); }); describe('ValidateIdPipe', () => { let pipe: ValidateIdPipe; beforeEach(() => { pipe = new ValidateIdPipe(); }); it('passes through a valid cuid', () => { const result = pipe.transform('valid-cuid-value'); expect(result).toBe('valid-cuid-value'); }); it('throws BadRequestException for an invalid id', () => { expect(() => pipe.transform('not-a-cuid')).toThrow(); }); it('passes through when value is falsy (optional id)', () => { const result = pipe.transform(undefined as any); expect(result).toBeUndefined(); }); it('uses a custom error message', () => { const customPipe = new ValidateIdPipe('Custom error'); try { customPipe.transform('not-a-cuid'); } catch (e: any) { expect(e.message).toBe('Custom error'); } }); }); describe('ValidateSearchPipe', () => { const mockModel = { tableAttributes: { MediaId: {}, Title: {}, Type: {} }, }; let pipe: ValidateSearchPipe; beforeEach(() => { pipe = new ValidateSearchPipe(mockModel); }); it('returns value when search is valid JSON with valid keys', () => { const search = JSON.stringify({ Title: 'test' }); expect(pipe.transform(search)).toBe(search); }); it('returns undefined when value is falsy', () => { expect(pipe.transform(undefined as any)).toBeUndefined(); }); it('throws BadRequestException for invalid JSON', () => { expect(() => pipe.transform('{bad json')).toThrow(); }); it('throws BadRequestException for unknown search key', () => { const search = JSON.stringify({ UnknownField: 'value' }); expect(() => pipe.transform(search)).toThrow(); }); it('allows additionalAttributes alongside model attributes', () => { const pipeWithExtra = new ValidateSearchPipe(mockModel, ['ExtraField']); const search = JSON.stringify({ ExtraField: 'value' }); expect(pipeWithExtra.transform(search)).toBe(search); }); });