Skip to main content
Glama
inspector.test.js17.3 kB
import { describe, test, expect, beforeEach, afterEach, jest } from '@jest/globals'; import { AudioInspector } from '../lib/inspector.js'; import { promises as fs } from 'fs'; import path from 'path'; describe('AudioInspector', () => { let inspector; let testFilesDir; beforeEach(() => { inspector = new AudioInspector(); testFilesDir = path.join(process.cwd(), 'tests', 'assets'); }); afterEach(() => { jest.clearAllMocks(); }); describe('Constructor and Initialization', () => { test('should initialize with supported formats', () => { expect(inspector).toBeInstanceOf(AudioInspector); expect(inspector.supportedFormats).toEqual( expect.arrayContaining(['.mp3', '.wav', '.flac', '.ogg', '.m4a']) ); }); test('should have all expected methods', () => { expect(typeof inspector.analyzeFile).toBe('function'); expect(typeof inspector.analyzeBatch).toBe('function'); expect(typeof inspector.findAudioFiles).toBe('function'); expect(typeof inspector.extractFormatInfo).toBe('function'); expect(typeof inspector.extractTags).toBe('function'); expect(typeof inspector.analyzeForGameAudio).toBe('function'); expect(typeof inspector.getSupportedFormats).toBe('function'); }); }); describe('getSupportedFormats', () => { test('should return supported formats information', () => { const formats = inspector.getSupportedFormats(); expect(formats).toHaveProperty('primary'); expect(formats).toHaveProperty('fallback'); expect(formats).toHaveProperty('note'); expect(Array.isArray(formats.primary)).toBe(true); expect(formats.primary.length).toBeGreaterThan(0); }); }); describe('extractFormatInfo', () => { test('should extract format info from valid metadata', () => { const mockMetadata = { format: { container: 'MP3', codec: 'mp3', lossless: false, duration: 180.5, bitrate: 128000, sampleRate: 44100, numberOfChannels: 2, bitsPerSample: 16 } }; const formatInfo = inspector.extractFormatInfo(mockMetadata); expect(formatInfo).toEqual({ container: 'MP3', codec: 'mp3', lossless: false, duration: 180.5, bitrate: 128000, sampleRate: 44100, channels: 2, bitsPerSample: 16 }); }); test('should handle missing format data gracefully', () => { const mockMetadata = {}; const formatInfo = inspector.extractFormatInfo(mockMetadata); expect(formatInfo).toEqual({ container: 'unknown', codec: 'unknown', lossless: false, duration: 0, bitrate: 0, sampleRate: 0, channels: 0, bitsPerSample: 0 }); }); test('should use codec fallback for container identification', () => { const mockMetadata = { format: { codec: 'flac', duration: 120 } }; const formatInfo = inspector.extractFormatInfo(mockMetadata); expect(formatInfo.container).toBe('FLAC'); }); }); describe('extractTags', () => { test('should extract tags from valid metadata', () => { const mockMetadata = { common: { title: 'Test Song', artist: 'Test Artist', album: 'Test Album', year: 2024, genre: ['Electronic', 'Ambient'], track: { no: 5 }, comment: ['Test comment', 'Another comment'] } }; const tags = inspector.extractTags(mockMetadata); expect(tags).toEqual({ title: 'Test Song', artist: 'Test Artist', album: 'Test Album', year: 2024, genre: 'Electronic, Ambient', track: 5, comment: 'Test comment Another comment' }); }); test('should handle missing tags gracefully', () => { const mockMetadata = {}; const tags = inspector.extractTags(mockMetadata); expect(tags).toEqual({ title: '', artist: '', album: '', year: null, genre: '', track: null, comment: '' }); }); test('should handle single genre string', () => { const mockMetadata = { common: { genre: 'Rock' } }; const tags = inspector.extractTags(mockMetadata); expect(tags.genre).toBe('Rock'); }); }); describe('analyzeForGameAudio', () => { test('should provide complete game audio analysis', async () => { const mockBasicInfo = { format: { duration: 15.5, sampleRate: 44100, channels: 2, bitsPerSample: 16, lossless: false }, file: { size: 1024000 } }; const gameAnalysis = await inspector.analyzeForGameAudio(mockBasicInfo, 'test.mp3'); expect(gameAnalysis).toHaveProperty('suitableForLoop'); expect(gameAnalysis).toHaveProperty('recommendedCompressionFormat'); expect(gameAnalysis).toHaveProperty('estimatedMemoryUsage'); expect(gameAnalysis).toHaveProperty('platformOptimizations'); expect(gameAnalysis).toHaveProperty('compressionRatio'); expect(gameAnalysis).toHaveProperty('gameDevNotes'); expect(typeof gameAnalysis.suitableForLoop).toBe('boolean'); expect(typeof gameAnalysis.recommendedCompressionFormat).toBe('string'); expect(typeof gameAnalysis.estimatedMemoryUsage).toBe('number'); expect(typeof gameAnalysis.platformOptimizations).toBe('object'); expect(typeof gameAnalysis.compressionRatio).toBe('number'); }); test('should identify short files as suitable for looping', async () => { const mockBasicInfo = { format: { duration: 5.0 }, file: { size: 100000 } }; const gameAnalysis = await inspector.analyzeForGameAudio(mockBasicInfo, 'short.wav'); expect(gameAnalysis.suitableForLoop).toBe(true); }); test('should identify long files as not suitable for looping', async () => { const mockBasicInfo = { format: { duration: 120.0 }, file: { size: 5000000 } }; const gameAnalysis = await inspector.analyzeForGameAudio(mockBasicInfo, 'long.mp3'); expect(gameAnalysis.suitableForLoop).toBe(false); }); test('should recommend appropriate compression formats', async () => { // High quality lossless file const losslessInfo = { format: { lossless: true, bitsPerSample: 24 }, file: { size: 10000000 } }; let analysis = await inspector.analyzeForGameAudio(losslessInfo, 'test.flac'); expect(analysis.recommendedCompressionFormat).toContain('High Quality'); // Short sound effect const shortInfo = { format: { duration: 3.0, lossless: false }, file: { size: 150000 } }; analysis = await inspector.analyzeForGameAudio(shortInfo, 'sfx.wav'); expect(analysis.recommendedCompressionFormat).toContain('Uncompressed'); // Standard audio const standardInfo = { format: { duration: 60.0, lossless: false, bitsPerSample: 16 }, file: { size: 2000000 } }; analysis = await inspector.analyzeForGameAudio(standardInfo, 'music.mp3'); expect(analysis.recommendedCompressionFormat).toContain('Standard'); }); }); describe('File Analysis - Error Handling', () => { test('should handle non-existent files gracefully', async () => { const result = await inspector.analyzeFile('non-existent-file.mp3'); expect(result).toHaveProperty('error', true); expect(result).toHaveProperty('message'); expect(result).toHaveProperty('file'); expect(result.file).toHaveProperty('error'); }); test('should handle directory instead of file', async () => { // Create a temporary directory const tempDir = path.join(testFilesDir, 'temp-dir'); await fs.mkdir(tempDir, { recursive: true }); try { const result = await inspector.analyzeFile(tempDir); expect(result).toHaveProperty('error', true); expect(result.message).toContain('not a file'); } finally { await fs.rmdir(tempDir); } }); test('should handle corrupted file metadata', async () => { // Create a fake audio file with invalid content const fakeFile = path.join(testFilesDir, 'fake.mp3'); await fs.writeFile(fakeFile, 'This is not an audio file'); try { const result = await inspector.analyzeFile(fakeFile); // Should either return error or fallback to FFprobe expect(result).toBeDefined(); if (result.error) { expect(result).toHaveProperty('message'); } else { expect(result).toHaveProperty('source'); } } finally { await fs.unlink(fakeFile).catch(() => {}); // Ignore cleanup errors } }); }); describe('Batch Analysis', () => { let tempDir; beforeEach(async () => { tempDir = path.join(testFilesDir, 'batch-test'); await fs.mkdir(tempDir, { recursive: true }); }); afterEach(async () => { await fs.rmdir(tempDir, { recursive: true }).catch(() => {}); }); test('should handle empty directory', async () => { const result = await inspector.analyzeBatch(tempDir); expect(result).toHaveProperty('summary'); expect(result).toHaveProperty('results'); expect(result).toHaveProperty('timestamp'); expect(result.summary.totalFiles).toBe(0); expect(result.summary.successful).toBe(0); expect(result.summary.failed).toBe(0); expect(Array.isArray(result.results)).toBe(true); expect(result.results.length).toBe(0); }); test('should find and analyze audio files', async () => { // Create fake audio files const mp3File = path.join(tempDir, 'test.mp3'); const wavFile = path.join(tempDir, 'test.wav'); const txtFile = path.join(tempDir, 'readme.txt'); await fs.writeFile(mp3File, 'fake mp3 content'); await fs.writeFile(wavFile, 'fake wav content'); await fs.writeFile(txtFile, 'text file content'); const result = await inspector.analyzeBatch(tempDir); expect(result.summary.totalFiles).toBe(2); // Only audio files expect(result.results.length).toBe(2); // Clean up await fs.unlink(mp3File).catch(() => {}); await fs.unlink(wavFile).catch(() => {}); await fs.unlink(txtFile).catch(() => {}); }); test('should handle recursive directory scanning', async () => { const subDir = path.join(tempDir, 'subdirectory'); await fs.mkdir(subDir, { recursive: true }); const mp3File = path.join(subDir, 'nested.mp3'); await fs.writeFile(mp3File, 'fake mp3 content'); const result = await inspector.analyzeBatch(tempDir, true); // recursive = true expect(result.summary.totalFiles).toBe(1); const nonRecursiveResult = await inspector.analyzeBatch(tempDir, false); expect(nonRecursiveResult.summary.totalFiles).toBe(0); // Clean up await fs.unlink(mp3File).catch(() => {}); await fs.rmdir(subDir).catch(() => {}); }); }); describe('Helper Methods', () => { test('getContainerFromCodec should map codecs correctly', () => { expect(inspector.getContainerFromCodec('mp3')).toBe('MP3'); expect(inspector.getContainerFromCodec('wav')).toBe('WAV'); expect(inspector.getContainerFromCodec('flac')).toBe('FLAC'); expect(inspector.getContainerFromCodec('vorbis')).toBe('OGG'); expect(inspector.getContainerFromCodec('aac')).toBe('M4A'); expect(inspector.getContainerFromCodec('opus')).toBe('OPUS'); expect(inspector.getContainerFromCodec('unknown')).toBe('unknown'); expect(inspector.getContainerFromCodec(null)).toBe('unknown'); }); test('checkLoopSuitability should work correctly', () => { expect(inspector.checkLoopSuitability({ duration: 15 })).toBe(true); expect(inspector.checkLoopSuitability({ duration: 45 })).toBe(false); expect(inspector.checkLoopSuitability({})).toBe(false); }); test('recommendCompressionFormat should provide appropriate recommendations', () => { expect(inspector.recommendCompressionFormat({ lossless: true }, 1000000)) .toContain('High Quality'); expect(inspector.recommendCompressionFormat({ bitsPerSample: 24 }, 1000000)) .toContain('High Quality'); expect(inspector.recommendCompressionFormat({ duration: 5 }, 500000)) .toContain('Uncompressed'); expect(inspector.recommendCompressionFormat({ duration: 60, lossless: false }, 2000000)) .toContain('Standard'); }); test('getPlatformOptimizations should provide platform-specific advice', () => { const optimizations = inspector.getPlatformOptimizations({ sampleRate: 48000, channels: 2 }); expect(optimizations).toHaveProperty('mobile'); expect(optimizations).toHaveProperty('desktop'); expect(optimizations).toHaveProperty('console'); expect(optimizations.mobile).toContain('downsampling'); expect(optimizations.desktop).toBe('Use original quality'); expect(optimizations.console).toBe('Optimized'); }); test('getGameDevNotes should provide relevant development notes', () => { const notes = inspector.getGameDevNotes( { sampleRate: 96000, bitsPerSample: 24 }, 15 * 1024 * 1024 // 15MB ); expect(notes).toContain('sample rate'); expect(notes).toContain('bit depth'); expect(notes).toContain('compression'); }); }); describe('FFprobe Integration', () => { test('analyzeWithFFprobe should handle spawn errors', async () => { // Mock spawn to simulate ffprobe not found const originalSpawn = await import('child_process').then(m => m.spawn); // This test verifies error handling rather than actual FFprobe functionality // since FFprobe might not be available in test environment const mockMetadata = { format: { container: 'test', codec: 'test', duration: 0, bitrate: 0, sampleRate: 0, numberOfChannels: 0, bitsPerSample: 0 }, common: {} }; // Test that the method exists and returns expected structure expect(typeof inspector.analyzeWithFFprobe).toBe('function'); }); }); describe('Edge Cases and Stress Tests', () => { test('should handle files with unusual sample rates', async () => { const mockBasicInfo = { format: { sampleRate: 8000, // Very low sample rate channels: 1, duration: 10 }, file: { size: 80000 } }; const gameAnalysis = await inspector.analyzeForGameAudio(mockBasicInfo, 'low-quality.wav'); expect(gameAnalysis).toBeDefined(); expect(gameAnalysis.platformOptimizations.mobile).toBe('Optimized'); }); test('should handle files with many channels', async () => { const mockBasicInfo = { format: { sampleRate: 44100, channels: 8, // Surround sound duration: 60 }, file: { size: 10000000 } }; const gameAnalysis = await inspector.analyzeForGameAudio(mockBasicInfo, 'surround.wav'); expect(gameAnalysis.platformOptimizations.console).toContain('downmix'); }); test('should handle very short audio files', async () => { const mockBasicInfo = { format: { duration: 0.1, // 100ms sampleRate: 44100, channels: 2 }, file: { size: 8820 } }; const gameAnalysis = await inspector.analyzeForGameAudio(mockBasicInfo, 'very-short.wav'); expect(gameAnalysis.suitableForLoop).toBe(true); expect(gameAnalysis.estimatedMemoryUsage).toBeGreaterThan(0); }); test('should handle very large audio files', async () => { const mockBasicInfo = { format: { duration: 3600, // 1 hour sampleRate: 96000, channels: 2, bitsPerSample: 24 }, file: { size: 500 * 1024 * 1024 } // 500MB }; const gameAnalysis = await inspector.analyzeForGameAudio(mockBasicInfo, 'large-file.wav'); expect(gameAnalysis.gameDevNotes).toContain('compression'); expect(gameAnalysis.estimatedMemoryUsage).toBeGreaterThan(1000000); }); }); describe('Input Validation', () => { test('should validate file paths', async () => { const invalidPaths = ['', null, undefined, 123, {}]; for (const invalidPath of invalidPaths) { const result = await inspector.analyzeFile(invalidPath); expect(result).toHaveProperty('error', true); } }); test('should handle malformed metadata objects', () => { const malformedInputs = [null, undefined, 'string', 123, []]; for (const input of malformedInputs) { expect(() => inspector.extractFormatInfo(input)).not.toThrow(); expect(() => inspector.extractTags(input)).not.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/DeveloperZo/mcp-audio-inspector'

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