UNPKG

windmill-yaml-validator

Version:

YAML validator for Windmill

228 lines (227 loc) 13.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const flow_validator_1 = require("../flow-validator"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); describe('FlowValidator', () => { let validator; const samplesDir = path.join(__dirname, 'test-samples'); const readSample = (filename) => { return fs.readFileSync(path.join(samplesDir, filename), 'utf-8'); }; beforeEach(() => { validator = new flow_validator_1.FlowValidator(); }); describe('constructor', () => { it('should create a validator instance', () => { expect(validator).toBeInstanceOf(flow_validator_1.FlowValidator); }); it('should initialize without throwing', () => { expect(() => new flow_validator_1.FlowValidator()).not.toThrow(); }); }); describe('validateFlow', () => { it('should throw error for non-string input', () => { expect(() => validator.validateFlow(null)).toThrow('Document must be a string'); expect(() => validator.validateFlow(123)).toThrow('Document must be a string'); expect(() => validator.validateFlow({})).toThrow('Document must be a string'); expect(() => validator.validateFlow([])).toThrow('Document must be a string'); }); describe('valid flows', () => { it('should validate a valid minimal flow from sample file', () => { const validFlow = readSample('valid-minimal.yaml'); const result = validator.validateFlow(validFlow); expect(result.errors).toHaveLength(0); expect(result.parsed).toBeDefined(); expect(result.parsed.data).toMatchObject({ summary: 'Test Flow', value: { modules: [] } }); }); it('should validate a script flow from sample file', () => { const validFlow = readSample('valid-script-flow.yaml'); const result = validator.validateFlow(validFlow); expect(result.errors).toHaveLength(0); expect(result.parsed.data).toMatchObject({ summary: 'Simple Script Flow', description: 'A basic flow that runs a TypeScript script', schema: expect.objectContaining({ type: 'object', properties: expect.objectContaining({ message: expect.objectContaining({ type: 'string', default: 'Hello World' }) }) }), value: { modules: expect.arrayContaining([ expect.objectContaining({ id: 'script_step', value: expect.objectContaining({ type: 'rawscript', language: 'deno', input_transforms: {} }) }) ]) } }); }); }); describe('invalid flows', () => { it('should return errors for missing summary from sample file', () => { const invalidFlow = readSample('invalid-missing-summary.yaml'); const result = validator.validateFlow(invalidFlow); expect(result.errors.length).toBeGreaterThan(0); expect(result.errors.some(error => error.instancePath === '' && error.keyword === 'required' && error.params?.missingProperty === 'summary')).toBe(true); }); it('should return errors for invalid types from sample file', () => { const invalidFlow = readSample('invalid-wrong-types.yaml'); const result = validator.validateFlow(invalidFlow); expect(result.errors.length).toBeGreaterThan(0); expect(result.errors.some(error => error.instancePath === '/summary' && error.keyword === 'type')).toBe(true); }); it('should return errors for invalid language from sample file', () => { const invalidFlow = readSample('invalid-language.yaml'); const result = validator.validateFlow(invalidFlow); expect(result.errors.length).toBeGreaterThan(0); expect(result.errors.some(error => error.instancePath === '/value/modules/0/value/language' && error.keyword === 'enum')).toBe(true); }); it('should handle empty file from sample', () => { const emptyFlow = readSample('empty.yaml'); const result = validator.validateFlow(emptyFlow); expect(result.parsed).toBeDefined(); expect(result.errors.length).toBeGreaterThan(0); }); it('should handle complex invalid flow with comprehensive error detection', () => { const complexInvalidFlow = readSample('invalid-complex-flow.yaml'); const result = validator.validateFlow(complexInvalidFlow); expect(result.errors.length).toBeGreaterThan(20); // Should have many errors // Check for missing required fields errors const missingRequiredErrors = result.errors.filter(error => error.keyword === 'required'); expect(missingRequiredErrors.length).toBeGreaterThan(10); // Check for invalid enum errors (invalid language) const enumErrors = result.errors.filter(error => error.keyword === 'enum'); expect(enumErrors.length).toBeGreaterThan(0); // Check for type errors (string vs number, boolean vs string, etc.) const typeErrors = result.errors.filter(error => error.keyword === 'type'); expect(typeErrors.length).toBeGreaterThan(5); // Should detect invalid forloop structure (by checking for forloop-related required fields) const forloopRequiredErrors = result.errors.filter(error => error.keyword === 'required' && error.message && (error.message.includes('modules') || error.message.includes('iterator') || error.message.includes('skip_failures'))); expect(forloopRequiredErrors.length).toBeGreaterThan(0); // Should detect invalid branch structure const branchRequiredErrors = result.errors.filter(error => error.keyword === 'required' && error.message && (error.message.includes('branches') || error.message.includes('default') || error.message.includes('expr'))); expect(branchRequiredErrors.length).toBeGreaterThan(0); // Should detect discriminator errors for invalid transform types const discriminatorErrors = result.errors.filter(error => error.keyword === 'discriminator'); expect(discriminatorErrors.length).toBeGreaterThan(0); }); it('should handle deeply nested invalid structures with detailed error reporting', () => { const nestedInvalidFlow = readSample('invalid-nested-structures.yaml'); const result = validator.validateFlow(nestedInvalidFlow); expect(result.errors.length).toBeGreaterThan(10); // Should have many nested errors // Check for deeply nested path errors (paths with many levels) const deepNestedErrors = result.errors.filter(error => error.instancePath.split('/').length > 6 // Deep nesting ); expect(deepNestedErrors.length).toBeGreaterThan(0); // Check for transform-related errors const transformErrors = result.errors.filter(error => error.instancePath.includes('input_transforms') || error.instancePath.includes('iterator')); expect(transformErrors.length).toBeGreaterThan(0); // Check for type errors in general const typeErrors = result.errors.filter(error => error.keyword === 'type'); expect(typeErrors.length).toBeGreaterThan(0); // Check for missing required fields in nested structures const nestedRequiredErrors = result.errors.filter(error => error.keyword === 'required' && error.instancePath.includes('/modules/')); expect(nestedRequiredErrors.length).toBeGreaterThan(0); // Check for discriminator errors in nested transforms const nestedDiscriminatorErrors = result.errors.filter(error => error.keyword === 'discriminator'); expect(nestedDiscriminatorErrors.length).toBeGreaterThan(0); }); it('should provide specific error locations for complex validation failures', () => { const complexInvalidFlow = readSample('invalid-complex-flow.yaml'); const result = validator.validateFlow(complexInvalidFlow); // Verify that errors have meaningful instance paths const errorsWithPaths = result.errors.filter(error => error.instancePath && error.instancePath.length > 0); expect(errorsWithPaths.length).toBeGreaterThan(5); // Check that we can identify specific problematic modules const moduleSpecificErrors = result.errors.filter(error => error.instancePath.includes('/value/modules/')); expect(moduleSpecificErrors.length).toBeGreaterThan(0); // Verify error messages are descriptive const descriptiveErrors = result.errors.filter(error => error.message && error.message.length > 0); expect(descriptiveErrors.length).toBe(result.errors.length); }); it('should handle all major flow control structures with validation errors', () => { const complexInvalidFlow = readSample('invalid-complex-flow.yaml'); const result = validator.validateFlow(complexInvalidFlow); expect(result.errors.length).toBeGreaterThan(20); // Should find errors related to flow control structures by checking instance paths const flowControlErrors = result.errors.filter(error => error.instancePath.includes('/modules/2/') || // forloop module error.instancePath.includes('/modules/3/') || // forloop module error.instancePath.includes('/modules/4/') || // branch module error.instancePath.includes('/modules/5/') || // branch module error.instancePath.includes('/modules/6/') || // branch all module error.instancePath.includes('/modules/7/') // while loop module ); expect(flowControlErrors.length).toBeGreaterThan(10); // Should find errors in script and flow references const pathReferenceErrors = result.errors.filter(error => error.instancePath.includes('/modules/8/') || // script path module error.instancePath.includes('/modules/9/') // flow path module ); expect(pathReferenceErrors.length).toBeGreaterThan(0); // Should detect modules with missing IDs const missingIdErrors = result.errors.filter(error => error.keyword === 'required' && error.message && error.message.includes('id')); expect(missingIdErrors.length).toBeGreaterThan(0); // Should detect type mismatches at the flow level const flowLevelTypeErrors = result.errors.filter(error => error.instancePath.startsWith('/value/') && !error.instancePath.includes('/modules/') && error.keyword === 'type'); expect(flowLevelTypeErrors.length).toBeGreaterThan(0); }); }); }); });