/**
* Audio Tools Tests
* ISO/IEC 29119 compliant - comprehensive test documentation
* ISO/IEC 25010 compliant - strict testing
*
* Test Coverage Target: 80%+
*
* Test Categories (ISO 29119-4):
* 1. Input Validation Tests (Zod schema validation)
* 2. Missing Required Parameters Tests
* 3. Invalid Enum Values Tests
* 4. Path Security Tests
* 5. Edge Case Tests
* 6. Success Scenario Tests (with mocked dependencies)
*/
import { handleCreateAudioBus } from './CreateAudioBusTool';
import { handleSetupAudioPlayer } from './SetupAudioPlayerTool';
import { handleAddAudioEffect } from './AddAudioEffectTool';
describe('Audio Tools', () => {
// ============================================================================
// CreateAudioBus Tests
// ============================================================================
describe('CreateAudioBus', () => {
describe('Input Validation - Missing Required Parameters', () => {
it('should return error when projectPath is missing', async () => {
const result = await handleCreateAudioBus({
busName: 'Music',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('projectPath');
});
it('should return error when busName is missing', async () => {
const result = await handleCreateAudioBus({
projectPath: '/path/to/project',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('busName');
});
it('should return error when all required parameters are missing', async () => {
const result = await handleCreateAudioBus({});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/projectPath|busName|Validation failed/i);
});
});
describe('Input Validation - Empty/Invalid Values', () => {
it('should return error for empty busName', async () => {
const result = await handleCreateAudioBus({
projectPath: '/non/existent/path',
busName: ' ',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('cannot be empty');
});
it('should return error for empty string busName', async () => {
const result = await handleCreateAudioBus({
projectPath: '/non/existent/path',
busName: '',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/busName|cannot be empty|Validation failed/i);
});
it('should return error for empty projectPath', async () => {
const result = await handleCreateAudioBus({
projectPath: '',
busName: 'Music',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/projectPath|cannot be empty|Validation failed/i);
});
});
describe('Path Security', () => {
it('should return error for path traversal in projectPath', async () => {
const result = await handleCreateAudioBus({
projectPath: '/path/to/../../../etc/passwd',
busName: 'Music',
});
expect(result.isError).toBe(true);
// On Windows, path may be normalized; either path traversal error or invalid path is acceptable
expect(result.content[0].text).toMatch(/path traversal|\.\.|Not a valid Godot project/i);
});
it('should return error for invalid project path', async () => {
const result = await handleCreateAudioBus({
projectPath: '/non/existent/path',
busName: 'Music',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
});
describe('Optional Parameters', () => {
it('should accept valid parentBus parameter', async () => {
const result = await handleCreateAudioBus({
projectPath: '/non/existent/path',
busName: 'Music',
parentBus: 'Master',
});
expect(result.isError).toBe(true);
// Should fail on project validation, not on parentBus
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept valid volume parameter', async () => {
const result = await handleCreateAudioBus({
projectPath: '/non/existent/path',
busName: 'Music',
volume: -6.0,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept solo parameter', async () => {
const result = await handleCreateAudioBus({
projectPath: '/non/existent/path',
busName: 'Music',
solo: true,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept mute parameter', async () => {
const result = await handleCreateAudioBus({
projectPath: '/non/existent/path',
busName: 'Music',
mute: true,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept all optional parameters together', async () => {
const result = await handleCreateAudioBus({
projectPath: '/non/existent/path',
busName: 'Music',
parentBus: 'Master',
volume: -3.0,
solo: false,
mute: false,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
});
describe('Type Validation', () => {
it('should return error for non-string busName', async () => {
const result = await handleCreateAudioBus({
projectPath: '/path/to/project',
busName: 123 as unknown as string,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/busName|string|Validation failed/i);
});
it('should return error for non-number volume', async () => {
const result = await handleCreateAudioBus({
projectPath: '/path/to/project',
busName: 'Music',
volume: 'loud' as unknown as number,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/volume|number|Validation failed/i);
});
it('should return error for non-boolean solo', async () => {
const result = await handleCreateAudioBus({
projectPath: '/path/to/project',
busName: 'Music',
solo: 'yes' as unknown as boolean,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/solo|boolean|Validation failed/i);
});
});
});
// ============================================================================
// SetupAudioPlayer Tests
// ============================================================================
describe('SetupAudioPlayer', () => {
describe('Input Validation - Missing Required Parameters', () => {
it('should return error when projectPath is missing', async () => {
const result = await handleSetupAudioPlayer({
scenePath: 'scenes/main.tscn',
nodeName: 'MusicPlayer',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('projectPath');
});
it('should return error when scenePath is missing', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/path/to/project',
nodeName: 'MusicPlayer',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('scenePath');
});
it('should return error when nodeName is missing', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/path/to/project',
scenePath: 'scenes/main.tscn',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('nodeName');
});
it('should return error when all required parameters are missing', async () => {
const result = await handleSetupAudioPlayer({});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/Validation failed/i);
});
});
describe('Input Validation - Empty/Invalid Values', () => {
it('should return error for empty nodeName', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/path/to/project',
scenePath: 'scenes/main.tscn',
nodeName: '',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/nodeName|cannot be empty|Validation failed/i);
});
it('should return error for empty scenePath', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/path/to/project',
scenePath: '',
nodeName: 'MusicPlayer',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/scenePath|cannot be empty|Validation failed/i);
});
it('should return error for empty projectPath', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '',
scenePath: 'scenes/main.tscn',
nodeName: 'MusicPlayer',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/projectPath|cannot be empty|Validation failed/i);
});
});
describe('Path Security', () => {
it('should return error for path traversal in projectPath', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/path/../../../etc/passwd',
scenePath: 'scenes/main.tscn',
nodeName: 'MusicPlayer',
});
expect(result.isError).toBe(true);
// On Windows, path may be normalized; either path traversal error or invalid path is acceptable
expect(result.content[0].text).toMatch(/path traversal|\.\.|Not a valid Godot project/i);
});
it('should return error for path traversal in scenePath', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/path/to/project',
scenePath: '../../../etc/passwd.tscn',
nodeName: 'MusicPlayer',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/path traversal|\.\./i);
});
it('should return error for invalid project path', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/non/existent/path',
scenePath: 'scenes/main.tscn',
nodeName: 'MusicPlayer',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
});
describe('Optional Parameters - is3D', () => {
it('should accept is3D=true for 3D audio player', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/non/existent/path',
scenePath: 'scenes/main.tscn',
nodeName: 'SpatialAudio',
is3D: true,
});
expect(result.isError).toBe(true);
// Should fail on project validation, not on is3D
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept is3D=false for 2D audio player', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/non/existent/path',
scenePath: 'scenes/main.tscn',
nodeName: 'BackgroundMusic',
is3D: false,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
});
describe('Optional Parameters - Audio Properties', () => {
it('should accept valid streamPath', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/non/existent/path',
scenePath: 'scenes/main.tscn',
nodeName: 'MusicPlayer',
streamPath: 'audio/music.ogg',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept valid bus name', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/non/existent/path',
scenePath: 'scenes/main.tscn',
nodeName: 'MusicPlayer',
bus: 'Music',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept autoplay parameter', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/non/existent/path',
scenePath: 'scenes/main.tscn',
nodeName: 'MusicPlayer',
autoplay: true,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept volumeDb parameter', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/non/existent/path',
scenePath: 'scenes/main.tscn',
nodeName: 'MusicPlayer',
volumeDb: -12.0,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept parentNodePath parameter', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/non/existent/path',
scenePath: 'scenes/main.tscn',
nodeName: 'MusicPlayer',
parentNodePath: 'GameManager',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept all optional parameters together', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/non/existent/path',
scenePath: 'scenes/main.tscn',
nodeName: 'MusicPlayer',
parentNodePath: 'GameManager',
is3D: false,
streamPath: 'audio/theme.ogg',
bus: 'Music',
autoplay: true,
volumeDb: -6.0,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
});
describe('Type Validation', () => {
it('should return error for non-string nodeName', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/path/to/project',
scenePath: 'scenes/main.tscn',
nodeName: 123 as unknown as string,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/nodeName|string|Validation failed/i);
});
it('should return error for non-boolean is3D', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/path/to/project',
scenePath: 'scenes/main.tscn',
nodeName: 'MusicPlayer',
is3D: 'true' as unknown as boolean,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/is3D|boolean|Validation failed/i);
});
it('should return error for non-number volumeDb', async () => {
const result = await handleSetupAudioPlayer({
projectPath: '/path/to/project',
scenePath: 'scenes/main.tscn',
nodeName: 'MusicPlayer',
volumeDb: 'loud' as unknown as number,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/volumeDb|number|Validation failed/i);
});
});
});
// ============================================================================
// AddAudioEffect Tests
// ============================================================================
describe('AddAudioEffect', () => {
describe('Input Validation - Missing Required Parameters', () => {
it('should return error when projectPath is missing', async () => {
const result = await handleAddAudioEffect({
busName: 'Music',
effectType: 'reverb',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('projectPath');
});
it('should return error when busName is missing', async () => {
const result = await handleAddAudioEffect({
projectPath: '/path/to/project',
effectType: 'reverb',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('busName');
});
it('should return error when effectType is missing', async () => {
const result = await handleAddAudioEffect({
projectPath: '/path/to/project',
busName: 'Music',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('effectType');
});
it('should return error when all required parameters are missing', async () => {
const result = await handleAddAudioEffect({});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/Validation failed/i);
});
});
describe('Input Validation - Empty Values', () => {
it('should return error for empty busName', async () => {
const result = await handleAddAudioEffect({
projectPath: '/path/to/project',
busName: '',
effectType: 'reverb',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/busName|cannot be empty|Validation failed/i);
});
it('should return error for empty projectPath', async () => {
const result = await handleAddAudioEffect({
projectPath: '',
busName: 'Music',
effectType: 'reverb',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/projectPath|cannot be empty|Validation failed/i);
});
});
describe('Invalid Enum Values - effectType', () => {
it('should return error for invalid effectType', async () => {
const result = await handleAddAudioEffect({
projectPath: '/non/existent/path',
busName: 'Music',
effectType: 'invalid_effect' as 'reverb',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/effectType|Validation failed|Invalid/i);
});
it('should return error for effectType with wrong case', async () => {
const result = await handleAddAudioEffect({
projectPath: '/non/existent/path',
busName: 'Music',
effectType: 'REVERB' as 'reverb',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/effectType|Validation failed|Invalid/i);
});
it('should return error for empty effectType', async () => {
const result = await handleAddAudioEffect({
projectPath: '/path/to/project',
busName: 'Music',
effectType: '' as 'reverb',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/effectType|Validation failed/i);
});
});
describe('Valid Effect Types', () => {
const validEffectTypes = [
'amplify',
'bandlimit',
'bandpass',
'chorus',
'compressor',
'delay',
'distortion',
'eq6',
'eq10',
'eq21',
'filter',
'highpass',
'highshelf',
'limiter',
'lowpass',
'lowshelf',
'notch',
'panner',
'phaser',
'pitch_shift',
'record',
'reverb',
'spectrum_analyzer',
'stereo_enhance',
] as const;
validEffectTypes.forEach((effectType) => {
it(`should accept valid effectType: ${effectType}`, async () => {
const result = await handleAddAudioEffect({
projectPath: '/non/existent/path',
busName: 'Music',
effectType: effectType,
});
expect(result.isError).toBe(true);
// Should fail on project validation, not on effectType
expect(result.content[0].text).toContain('Not a valid Godot project');
});
});
});
describe('Path Security', () => {
it('should return error for path traversal in projectPath', async () => {
const result = await handleAddAudioEffect({
projectPath: '/path/../../../etc/passwd',
busName: 'Music',
effectType: 'reverb',
});
expect(result.isError).toBe(true);
// On Windows, path may be normalized; either path traversal error or invalid path is acceptable
expect(result.content[0].text).toMatch(/path traversal|\.\.|Not a valid Godot project/i);
});
it('should return error for invalid project path', async () => {
const result = await handleAddAudioEffect({
projectPath: '/non/existent/path',
busName: 'Music',
effectType: 'reverb',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
});
describe('Optional Parameters - effectParams', () => {
it('should accept effectParams as empty object', async () => {
const result = await handleAddAudioEffect({
projectPath: '/non/existent/path',
busName: 'Music',
effectType: 'reverb',
effectParams: {},
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept effectParams with valid properties for reverb', async () => {
const result = await handleAddAudioEffect({
projectPath: '/non/existent/path',
busName: 'Music',
effectType: 'reverb',
effectParams: {
room_size: 0.8,
damping: 0.5,
wet: 0.3,
dry: 0.7,
},
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept effectParams with valid properties for delay', async () => {
const result = await handleAddAudioEffect({
projectPath: '/non/existent/path',
busName: 'Music',
effectType: 'delay',
effectParams: {
tap1_delay_ms: 250,
tap1_level_db: -6,
feedback_active: true,
feedback_delay_ms: 500,
},
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept effectParams with valid properties for compressor', async () => {
const result = await handleAddAudioEffect({
projectPath: '/non/existent/path',
busName: 'Music',
effectType: 'compressor',
effectParams: {
threshold: -20,
ratio: 4,
attack_us: 20,
release_ms: 250,
gain: 0,
},
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept effectParams with valid properties for eq6', async () => {
const result = await handleAddAudioEffect({
projectPath: '/non/existent/path',
busName: 'Music',
effectType: 'eq6',
effectParams: {
band1_gain_db: 3.0,
band2_gain_db: -2.0,
band3_gain_db: 0.0,
},
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should accept effectParams with valid properties for amplify', async () => {
const result = await handleAddAudioEffect({
projectPath: '/non/existent/path',
busName: 'Music',
effectType: 'amplify',
effectParams: {
volume_db: 6.0,
},
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
});
describe('Type Validation', () => {
it('should return error for non-string busName', async () => {
const result = await handleAddAudioEffect({
projectPath: '/path/to/project',
busName: 123 as unknown as string,
effectType: 'reverb',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/busName|string|Validation failed/i);
});
it('should return error for non-object effectParams', async () => {
const result = await handleAddAudioEffect({
projectPath: '/path/to/project',
busName: 'Music',
effectType: 'reverb',
effectParams: 'invalid' as unknown as Record<string, unknown>,
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toMatch(/effectParams|object|Validation failed/i);
});
});
describe('Edge Cases', () => {
it('should handle busName with special characters', async () => {
const result = await handleAddAudioEffect({
projectPath: '/non/existent/path',
busName: 'Music_SFX-2',
effectType: 'reverb',
});
expect(result.isError).toBe(true);
// Should fail on project validation, not on busName
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should handle busName with unicode characters', async () => {
const result = await handleAddAudioEffect({
projectPath: '/non/existent/path',
busName: 'Música',
effectType: 'reverb',
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Not a valid Godot project');
});
it('should handle very long busName', async () => {
const result = await handleAddAudioEffect({
projectPath: '/non/existent/path',
busName: 'A'.repeat(256),
effectType: 'reverb',
});
expect(result.isError).toBe(true);
// Either validation or project path error is acceptable
expect(result.content[0].text).toMatch(/Not a valid Godot project|Validation failed/i);
});
});
});
});