Skip to main content
Glama
EnhancedMCPServerFixed.validation.test.ts26.2 kB
/** * Validation Tests for EnhancedMCPServerFixed * * This test suite verifies that all MCP tools properly validate their input parameters * before executing operations. Tests are organized by tool category. */ import { InputValidator } from '../utils/InputValidator'; describe('EnhancedMCPServerFixed - Parameter Validation', () => { describe('Tempo/BPM Tools', () => { describe('set_tempo tool', () => { test('should validate BPM parameter', () => { const params = { bpm: 120 }; expect(() => InputValidator.validateBPM(params.bpm)).not.toThrow(); }); test('should reject invalid BPM values', () => { const invalidBPMs = [ { bpm: 19, reason: 'below minimum' }, { bpm: 301, reason: 'above maximum' }, { bpm: -120, reason: 'negative' }, { bpm: 0, reason: 'zero' }, { bpm: NaN, reason: 'NaN' }, { bpm: 'fast' as any, reason: 'string' } ]; invalidBPMs.forEach(({ bpm, reason }) => { expect(() => InputValidator.validateBPM(bpm)).toThrow(); }); }); test('should accept decimal BPM values', () => { expect(() => InputValidator.validateBPM(128.5)).not.toThrow(); expect(() => InputValidator.validateBPM(99.99)).not.toThrow(); }); }); }); describe('Effect Tools', () => { describe('add_effect tool', () => { test('should validate gain parameter for gain effect', () => { const params = { effect: 'gain', params: '0.8' }; const gainValue = parseFloat(params.params); expect(() => InputValidator.validateGain(gainValue)).not.toThrow(); }); test('should reject invalid gain values', () => { const invalidGains = [ -0.5, // negative 2.5, // above maximum 10.0, // way above maximum NaN, // invalid number Infinity // infinite ]; invalidGains.forEach(gain => { expect(() => InputValidator.validateGain(gain)).toThrow(); }); }); test('should validate room parameter for room effect', () => { const validRoomSizes = [0, 0.5, 0.9, 1.0]; validRoomSizes.forEach(room => { expect(() => InputValidator.validateGain(room)).not.toThrow(); }); }); test('should validate effect parameter is a string', () => { const params = { effect: 'reverb', params: '0.5' }; expect(() => InputValidator.validateStringLength(params.effect, 'effect', 50)).not.toThrow(); }); test('should reject excessively long effect names', () => { const longEffect = 'a'.repeat(101); expect(() => InputValidator.validateStringLength(longEffect, 'effect', 100)).toThrow(); }); }); }); describe('Euclidean Rhythm Tools', () => { describe('generate_euclidean tool', () => { test('should validate hits and steps parameters', () => { const params = { hits: 3, steps: 8, sound: 'bd' }; expect(() => InputValidator.validateEuclidean(params.hits, params.steps)).not.toThrow(); }); test('should reject when hits exceed steps', () => { expect(() => InputValidator.validateEuclidean(10, 8)) .toThrow('Hits (10) cannot exceed steps (8)'); }); test('should accept edge case where hits equals steps', () => { expect(() => InputValidator.validateEuclidean(8, 8)).not.toThrow(); }); test('should reject negative hits', () => { expect(() => InputValidator.validateEuclidean(-1, 8)) .toThrow('Hits must be a non-negative integer'); }); test('should reject negative steps', () => { expect(() => InputValidator.validateEuclidean(3, -8)) .toThrow('Steps must be a positive integer'); }); test('should reject decimal values', () => { expect(() => InputValidator.validateEuclidean(3.5, 8)).toThrow('Hits must be an integer'); expect(() => InputValidator.validateEuclidean(3, 8.5)).toThrow('Steps must be an integer'); }); test('should reject zero steps', () => { expect(() => InputValidator.validateEuclidean(0, 0)) .toThrow('Steps must be a positive integer'); }); test('should accept zero hits with positive steps', () => { expect(() => InputValidator.validateEuclidean(0, 8)).not.toThrow(); }); test('should reject excessively large step values', () => { expect(() => InputValidator.validateEuclidean(100, 1000)) .toThrow('Steps cannot exceed 256'); }); test('should validate sound parameter', () => { const params = { hits: 3, steps: 8, sound: 'bd' }; expect(() => InputValidator.validateStringLength(params.sound, 'sound', 100)).not.toThrow(); }); test('should reject invalid sound types', () => { const longSound = 'a'.repeat(101); expect(() => InputValidator.validateStringLength(longSound, 'sound', 100)).toThrow(); }); }); describe('generate_polyrhythm tool', () => { test('should validate multiple pattern arrays', () => { const params = { sounds: ['bd', 'cp', 'hh'], patterns: [3, 5, 7] }; params.patterns.forEach(pattern => { expect(() => InputValidator.validatePositiveInteger(pattern, 'pattern')).not.toThrow(); }); }); test('should reject negative pattern values', () => { expect(() => InputValidator.validatePositiveInteger(-3, 'pattern')).toThrow(); }); test('should reject zero pattern values', () => { expect(() => InputValidator.validatePositiveInteger(0, 'pattern')) .toThrow('pattern must be a positive integer'); }); test('should validate sounds array contains strings', () => { const sounds = ['bd', 'cp', 'hh']; sounds.forEach(sound => { expect(() => InputValidator.validateStringLength(sound, 'sound', 100)).not.toThrow(); }); }); }); }); describe('Scale and Chord Tools', () => { describe('apply_scale tool', () => { test('should validate scale name', () => { const params = { scale: 'major', root: 'C' }; expect(() => InputValidator.validateScaleName(params.scale)).not.toThrow(); }); test('should validate root note', () => { const params = { scale: 'major', root: 'C' }; expect(() => InputValidator.validateRootNote(params.root)).not.toThrow(); }); test('should reject invalid scale names', () => { const invalidScales = ['invalid', 'majer', 'Major', '']; invalidScales.forEach(scale => { expect(() => InputValidator.validateScaleName(scale)).toThrow(); }); }); test('should accept all valid scale types', () => { const validScales = [ 'major', 'minor', 'dorian', 'phrygian', 'lydian', 'mixolydian', 'aeolian', 'locrian', 'pentatonic', 'blues', 'chromatic', 'wholetone', 'harmonic_minor', 'melodic_minor' ]; validScales.forEach(scale => { expect(() => InputValidator.validateScaleName(scale)).not.toThrow(); }); }); test('should reject invalid root notes', () => { const invalidRoots = ['X', 'H', 'Z', 'Db', 'C##', '1', '']; invalidRoots.forEach(root => { expect(() => InputValidator.validateRootNote(root)).toThrow(); }); }); test('should accept all chromatic root notes', () => { const validRoots = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; validRoots.forEach(root => { expect(() => InputValidator.validateRootNote(root)).not.toThrow(); }); }); test('should accept lowercase root notes', () => { const lowercaseRoots = ['c', 'f#', 'g#']; lowercaseRoots.forEach(root => { expect(() => InputValidator.validateRootNote(root)).not.toThrow(); }); }); }); describe('generate_scale tool', () => { test('should validate all scale parameters', () => { const params = { scale: 'major', root: 'C' }; expect(() => { InputValidator.validateScaleName(params.scale); InputValidator.validateRootNote(params.root); }).not.toThrow(); }); test('should reject type mismatches', () => { expect(() => InputValidator.validateScaleName(123 as any)) .toThrow('Scale name must be a string'); expect(() => InputValidator.validateRootNote(123 as any)) .toThrow('Root note must be a string'); }); }); describe('generate_chord_progression tool', () => { test('should validate chord style', () => { const params = { key: 'C', style: 'pop' }; expect(() => InputValidator.validateChordStyle(params.style)).not.toThrow(); }); test('should validate key/root note', () => { const params = { key: 'C', style: 'pop' }; expect(() => InputValidator.validateRootNote(params.key)).not.toThrow(); }); test('should accept all valid chord styles', () => { const validStyles = ['pop', 'jazz', 'blues', 'folk', 'rock', 'classical', 'modal', 'edm']; validStyles.forEach(style => { expect(() => InputValidator.validateChordStyle(style)).not.toThrow(); }); }); test('should reject invalid chord styles', () => { const invalidStyles = ['invalid', 'hiphop', 'Pop', '']; invalidStyles.forEach(style => { expect(() => InputValidator.validateChordStyle(style)).toThrow(); }); }); test('should reject non-string chord styles', () => { expect(() => InputValidator.validateChordStyle(123 as any)) .toThrow('Chord style must be a string'); }); }); }); describe('Pattern Generation Tools', () => { describe('generate_pattern tool', () => { test('should validate BPM when provided', () => { const params = { style: 'techno', bpm: 140, key: 'C' }; expect(() => InputValidator.validateBPM(params.bpm)).not.toThrow(); }); test('should validate key when provided', () => { const params = { style: 'techno', key: 'C' }; expect(() => InputValidator.validateRootNote(params.key)).not.toThrow(); }); test('should validate style parameter', () => { const params = { style: 'techno' }; expect(() => InputValidator.validateStringLength(params.style, 'style', 100)).not.toThrow(); }); test('should reject invalid BPM in pattern generation', () => { expect(() => InputValidator.validateBPM(500)).toThrow(); }); test('should reject empty style string', () => { expect(() => InputValidator.validateStringLength('', 'style', 100, false)) .toThrow('style cannot be empty'); }); }); describe('generate_drums tool', () => { test('should validate complexity parameter', () => { const params = { style: 'techno', complexity: 0.7 }; const complexity = params.complexity; expect(() => InputValidator.validateGain(complexity)).not.toThrow(); }); test('should reject complexity outside 0-1 range', () => { expect(() => InputValidator.validateNormalizedValue(1.5, 'complexity')) .toThrow('complexity must be between 0 and 1.0'); expect(() => InputValidator.validateNormalizedValue(-0.1, 'complexity')) .toThrow('complexity must be between 0 and 1.0'); }); test('should validate style string', () => { const params = { style: 'techno' }; expect(() => InputValidator.validateStringLength(params.style, 'style', 100)).not.toThrow(); }); }); describe('generate_bassline tool', () => { test('should validate key parameter', () => { const params = { key: 'C', style: 'acid' }; expect(() => InputValidator.validateRootNote(params.key)).not.toThrow(); }); test('should validate style parameter', () => { const params = { key: 'C', style: 'acid' }; expect(() => InputValidator.validateStringLength(params.style, 'style', 100)).not.toThrow(); }); }); describe('generate_melody tool', () => { test('should validate scale parameter', () => { const params = { scale: 'major', root: 'C', length: 8 }; expect(() => InputValidator.validateScaleName(params.scale)).not.toThrow(); }); test('should validate root parameter', () => { const params = { scale: 'major', root: 'C', length: 8 }; expect(() => InputValidator.validateRootNote(params.root)).not.toThrow(); }); test('should validate length parameter', () => { const params = { scale: 'major', root: 'C', length: 8 }; expect(() => InputValidator.validatePositiveInteger(params.length, 'length')).not.toThrow(); }); test('should reject zero or negative length', () => { expect(() => InputValidator.validatePositiveInteger(0, 'length')).toThrow(); expect(() => InputValidator.validatePositiveInteger(-5, 'length')).toThrow(); }); test('should reject decimal length', () => { expect(() => InputValidator.validatePositiveInteger(8.5, 'length')) .toThrow('length must be an integer'); }); }); }); describe('Pattern Manipulation Tools', () => { describe('transpose tool', () => { test('should validate semitones parameter is a number', () => { const params = { semitones: 5 }; expect(typeof params.semitones).toBe('number'); }); test('should accept positive and negative semitones', () => { const validSemitones = [-12, -5, 0, 5, 12, 24]; validSemitones.forEach(semitones => { expect(typeof semitones).toBe('number'); }); }); test('should reject non-numeric semitones', () => { const params = { semitones: 'up' as any }; expect(() => { if (typeof params.semitones !== 'number') { throw new Error('Semitones must be a number'); } }).toThrow(); }); }); describe('stretch tool', () => { test('should validate factor parameter', () => { const params = { factor: 1.5 }; expect(typeof params.factor).toBe('number'); expect(params.factor).toBeGreaterThan(0); }); test('should accept various stretch factors', () => { const validFactors = [0.25, 0.5, 1.0, 1.5, 2.0, 4.0]; validFactors.forEach(factor => { expect(factor).toBeGreaterThan(0); }); }); test('should reject negative stretch factors', () => { const params = { factor: -1.5 }; expect(() => { if (params.factor <= 0) { throw new Error('Stretch factor must be positive'); } }).toThrow(); }); test('should reject zero stretch factor', () => { const params = { factor: 0 }; expect(() => { if (params.factor <= 0) { throw new Error('Stretch factor must be positive'); } }).toThrow(); }); }); describe('humanize tool', () => { test('should validate amount parameter is in 0-1 range', () => { const params = { amount: 0.3 }; expect(() => InputValidator.validateNormalizedValue(params.amount, 'amount')).not.toThrow(); }); test('should reject amount outside 0-1 range', () => { expect(() => InputValidator.validateNormalizedValue(-0.1, 'amount')).toThrow(); expect(() => InputValidator.validateNormalizedValue(1.5, 'amount')).toThrow(); }); test('should accept boundary values', () => { expect(() => InputValidator.validateNormalizedValue(0, 'amount')).not.toThrow(); expect(() => InputValidator.validateNormalizedValue(1.0, 'amount')).not.toThrow(); }); }); describe('quantize tool', () => { test('should validate grid parameter is a string', () => { const params = { grid: '1/16' }; expect(() => InputValidator.validateStringLength(params.grid, 'grid', 20)).not.toThrow(); }); test('should accept common quantize grid values', () => { const validGrids = ['1/4', '1/8', '1/16', '1/32']; validGrids.forEach(grid => { expect(() => InputValidator.validateStringLength(grid, 'grid', 20)).not.toThrow(); }); }); test('should reject non-string grid values', () => { const params = { grid: 16 as any }; expect(() => InputValidator.validateStringLength(params.grid, 'grid', 20)) .toThrow('grid must be a string'); }); }); }); describe('Pattern Text Manipulation Tools', () => { describe('write tool', () => { test('should validate pattern parameter', () => { const params = { pattern: 's("bd*4")' }; expect(() => InputValidator.validateStringLength(params.pattern, 'pattern', 10000)) .not.toThrow(); }); test('should reject excessively long patterns', () => { const longPattern = 'a'.repeat(10001); expect(() => InputValidator.validateStringLength(longPattern, 'pattern', 10000)).toThrow(); }); test('should accept empty pattern string', () => { const params = { pattern: '' }; expect(() => InputValidator.validateStringLength(params.pattern, 'pattern', 10000)) .not.toThrow(); }); test('should accept patterns with special characters', () => { const params = { pattern: 's("bd*4").room(0.9)\n// Comment' }; expect(() => InputValidator.validateStringLength(params.pattern, 'pattern', 10000)) .not.toThrow(); }); }); describe('append tool', () => { test('should validate code parameter', () => { const params = { code: '.slow(2)' }; expect(() => InputValidator.validateStringLength(params.code, 'code', 10000)) .not.toThrow(); }); test('should reject non-string code', () => { const params = { code: 123 as any }; expect(() => InputValidator.validateStringLength(params.code, 'code', 10000)) .toThrow('code must be a string'); }); }); describe('insert tool', () => { test('should validate position parameter', () => { const params = { position: 5, code: 'test' }; expect(() => InputValidator.validatePositiveInteger(params.position, 'position')) .not.toThrow(); }); test('should validate code parameter', () => { const params = { position: 5, code: 'test' }; expect(() => InputValidator.validateStringLength(params.code, 'code', 10000)) .not.toThrow(); }); test('should reject negative position', () => { expect(() => InputValidator.validatePositiveInteger(-1, 'position')).toThrow(); }); test('should reject decimal position', () => { expect(() => InputValidator.validatePositiveInteger(5.5, 'position')) .toThrow('position must be an integer'); }); }); describe('replace tool', () => { test('should validate search and replace parameters', () => { const params = { search: 'old', replace: 'new' }; expect(() => { InputValidator.validateStringLength(params.search, 'search', 1000); InputValidator.validateStringLength(params.replace, 'replace', 1000); }).not.toThrow(); }); test('should reject non-string search parameter', () => { const params = { search: 123 as any, replace: 'new' }; expect(() => InputValidator.validateStringLength(params.search, 'search', 1000)) .toThrow('search must be a string'); }); test('should accept empty strings', () => { const params = { search: '', replace: '' }; expect(() => { InputValidator.validateStringLength(params.search, 'search', 1000); InputValidator.validateStringLength(params.replace, 'replace', 1000); }).not.toThrow(); }); }); }); describe('Pattern Storage Tools', () => { describe('save tool', () => { test('should validate name parameter', () => { const params = { name: 'my-pattern', tags: ['techno'] }; expect(() => InputValidator.validateStringLength(params.name, 'name', 200)) .not.toThrow(); }); test('should reject excessively long pattern names', () => { const longName = 'a'.repeat(201); expect(() => InputValidator.validateStringLength(longName, 'name', 200)).toThrow(); }); test('should accept pattern names with special characters', () => { const params = { name: 'Pattern_With-Special.Chars' }; expect(() => InputValidator.validateStringLength(params.name, 'name', 200)) .not.toThrow(); }); test('should reject empty pattern name', () => { const params = { name: '' }; expect(() => { InputValidator.validateStringLength(params.name, 'name', 200); if (params.name.trim() === '') { throw new Error('Pattern name cannot be empty'); } }).toThrow(); }); }); describe('load tool', () => { test('should validate name parameter', () => { const params = { name: 'my-pattern' }; expect(() => InputValidator.validateStringLength(params.name, 'name', 200)) .not.toThrow(); }); }); describe('list tool', () => { test('should validate optional tag parameter', () => { const params = { tag: 'techno' }; expect(() => InputValidator.validateStringLength(params.tag, 'tag', 100)) .not.toThrow(); }); test('should accept undefined tag parameter', () => { const params = { tag: undefined }; if (params.tag !== undefined) { expect(() => InputValidator.validateStringLength(params.tag, 'tag', 100)) .not.toThrow(); } }); }); }); describe('Swing and Timing Tools', () => { describe('add_swing tool', () => { test('should validate amount parameter is in 0-1 range', () => { const params = { amount: 0.5 }; expect(() => InputValidator.validateNormalizedValue(params.amount, 'amount')).not.toThrow(); }); test('should reject amount outside 0-1 range', () => { expect(() => InputValidator.validateNormalizedValue(-0.1, 'amount')).toThrow(); expect(() => InputValidator.validateNormalizedValue(1.5, 'amount')).toThrow(); }); test('should accept boundary values', () => { expect(() => InputValidator.validateNormalizedValue(0, 'amount')).not.toThrow(); expect(() => InputValidator.validateNormalizedValue(1.0, 'amount')).not.toThrow(); }); }); }); describe('Integration - Complete Tool Workflows', () => { test('should validate complete pattern generation workflow', () => { expect(() => { // Generate pattern InputValidator.validateStringLength('techno', 'style', 100); InputValidator.validateBPM(140); InputValidator.validateRootNote('C'); // Apply scale InputValidator.validateScaleName('minor'); InputValidator.validateRootNote('C'); // Add effects InputValidator.validateGain(0.8); // Save pattern InputValidator.validateStringLength('my-techno-pattern', 'name', 200); }).not.toThrow(); }); test('should validate complete Euclidean rhythm workflow', () => { expect(() => { InputValidator.validateEuclidean(5, 8); InputValidator.validateStringLength('bd', 'sound', 100); InputValidator.validateBPM(128); }).not.toThrow(); }); test('should catch validation errors in multi-step workflow', () => { expect(() => { InputValidator.validateBPM(500); // Invalid }).toThrow(); expect(() => { InputValidator.validateEuclidean(10, 5); // Invalid }).toThrow(); expect(() => { InputValidator.validateScaleName('invalid'); // Invalid }).toThrow(); }); }); describe('Error Message Quality', () => { test('should provide descriptive error messages', () => { expect(() => InputValidator.validateBPM(500)) .toThrow(/BPM must be between 20 and 300/); expect(() => InputValidator.validateGain(3.0)) .toThrow(/Gain must be between 0 and 2.0/); expect(() => InputValidator.validateEuclidean(10, 8)) .toThrow(/Hits \(10\) cannot exceed steps \(8\)/); expect(() => InputValidator.validateScaleName('invalid')) .toThrow(/Invalid scale name: invalid/); expect(() => InputValidator.validateChordStyle('invalid')) .toThrow(/Invalid chord style: invalid/); }); test('should include context in error messages', () => { const longString = 'a'.repeat(101); expect(() => InputValidator.validateStringLength(longString, 'pattern_name', 100)) .toThrow(/pattern_name too long/); }); }); describe('Type Safety', () => { test('should catch type errors before processing', () => { expect(() => InputValidator.validateBPM('120' as any)) .toThrow(/BPM must be a number/); expect(() => InputValidator.validateGain('0.8' as any)) .toThrow(/Gain must be a number/); expect(() => InputValidator.validateScaleName(123 as any)) .toThrow(/Scale name must be a string/); expect(() => InputValidator.validateChordStyle(null as any)) .toThrow(/Chord style must be a string/); }); test('should reject null and undefined appropriately', () => { expect(() => InputValidator.validateBPM(null as any)).toThrow(); expect(() => InputValidator.validateBPM(undefined as any)).toThrow(); expect(() => InputValidator.validateGain(null as any)).toThrow(); expect(() => InputValidator.validateScaleName(undefined as any)).toThrow(); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/williamzujkowski/strudel-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server